mirror of https://github.com/YGGverse/YGGstate.git
ghost
1 year ago
9 changed files with 516 additions and 0 deletions
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
.vscode |
||||
.ftpignore |
||||
|
||||
/config/app.php |
||||
/database/yggstate.mwb.bak |
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* MIT License |
||||
* |
||||
* Copyright (c) 2023 YGGverse |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
* of this software and associated documentation files (the "Software"), to deal |
||||
* in the Software without restriction, including without limitation the rights |
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
* copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in all |
||||
* copies or substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
* SOFTWARE. |
||||
* |
||||
* Default configuration file example |
||||
* Production name: app.php |
||||
* |
||||
* Project home page |
||||
* https://github.com/YGGverse/YGGstate |
||||
* |
||||
* Get support |
||||
* https://github.com/YGGverse/YGGstate/issues |
||||
*/ |
||||
|
||||
// Debug |
||||
ini_set('display_errors', '1'); |
||||
ini_set('display_startup_errors', '1'); |
||||
error_reporting(E_ALL); |
||||
|
||||
// Database |
||||
define('DB_HOST', 'localhost'); |
||||
define('DB_PORT', 3306); |
||||
define('DB_NAME', ''); |
||||
define('DB_USERNAME', ''); |
||||
define('DB_PASSWORD', ''); |
||||
|
||||
// Crawler |
||||
|
||||
/* |
||||
* Stop crawler on disk quota reached |
||||
* |
||||
*/ |
||||
define('CRAWL_STOP_DISK_QUOTA_MB_LEFT', 128); |
@ -0,0 +1,156 @@
@@ -0,0 +1,156 @@
|
||||
<?php |
||||
|
||||
// Stop crawler on cli running |
||||
$semaphore = sem_get(crc32('yggstate.cli.yggstate'), 1); |
||||
|
||||
if (false === sem_acquire($semaphore, true)) { |
||||
|
||||
exit (PHP_EOL . 'yggstate.cli.yggstate process running in another thread.' . PHP_EOL); |
||||
} |
||||
|
||||
// Lock multi-thread execution |
||||
$semaphore = sem_get(crc32('yggstate.crontab.crawler'), 1); |
||||
|
||||
if (false === sem_acquire($semaphore, true)) { |
||||
|
||||
exit (PHP_EOL . 'yggstate.crontab.crawler process locked by another thread.' . PHP_EOL); |
||||
} |
||||
|
||||
// Load system dependencies |
||||
require_once(__DIR__ . '/../config/app.php'); |
||||
require_once(__DIR__ . '/../library/yggdrasil.php'); |
||||
require_once(__DIR__ . '/../library/mysql.php'); |
||||
require_once(__DIR__ . '/../library/url.php'); |
||||
|
||||
// Check disk quota |
||||
if (CRAWL_STOP_DISK_QUOTA_MB_LEFT > disk_free_space('/') / 1000000) { |
||||
|
||||
exit (PHP_EOL . 'Disk quota reached.' . PHP_EOL); |
||||
} |
||||
|
||||
// Init Debug |
||||
$debug = [ |
||||
'time' => [ |
||||
'ISO8601' => date('c'), |
||||
'total' => microtime(true), |
||||
], |
||||
'yggdrasil' => [ |
||||
'peer' => [ |
||||
'total' => [ |
||||
'online' => 0, |
||||
'insert' => 0, |
||||
], |
||||
'remote' => [ |
||||
'total' => [ |
||||
'insert' => 0, |
||||
'update' => 0, |
||||
] |
||||
] |
||||
] |
||||
] |
||||
]; |
||||
|
||||
// Connect database |
||||
try { |
||||
|
||||
$db = new MySQL(DB_HOST, DB_PORT, DB_NAME, DB_USERNAME, DB_PASSWORD); |
||||
|
||||
} catch(Exception $e) { |
||||
|
||||
var_dump($e); |
||||
|
||||
exit; |
||||
} |
||||
|
||||
// Collect connected peers |
||||
if ($connectedPeers = Yggdrasil::getPeers()) { |
||||
|
||||
foreach ($connectedPeers as $connectedPeerAddress => $connectedPeerInfo) { |
||||
|
||||
try { |
||||
|
||||
$db->beginTransaction(); |
||||
|
||||
if ($dbPeer = $db->findPeer($connectedPeerAddress)) { |
||||
|
||||
$dbPeerId = $dbPeer->peerId; |
||||
|
||||
} else { |
||||
|
||||
$dbPeerId = $db->addPeer($connectedPeerAddress, $connectedPeerInfo->key, time()); |
||||
|
||||
if ($connectedPeerRemoteUrl = URL::parse($connectedPeerInfo->remote)) { |
||||
|
||||
if ($dbPeerRemote = $db->findPeerRemote($dbPeerId, |
||||
$connectedPeerRemoteUrl->host->scheme, |
||||
$connectedPeerRemoteUrl->host->name, |
||||
$connectedPeerRemoteUrl->host->port)) { |
||||
|
||||
// Update connection stats |
||||
if ($dbPeerRemote->received < $connectedPeerInfo->bytes_recvd) { |
||||
|
||||
$debug['yggdrasil']['peer']['remote']['total']['update'] += |
||||
$db->updatePeerRemoteReceived($dbPeerRemote->dbPeerRemoteId, $connectedPeerInfo->bytes_recvd, time()); |
||||
|
||||
} |
||||
|
||||
if ($dbPeerRemote->sent < $connectedPeerInfo->bytes_sent) { |
||||
|
||||
$debug['yggdrasil']['peer']['remote']['total']['update'] += |
||||
$db->updatePeerRemoteSent($dbPeerRemote->dbPeerRemoteId, $connectedPeerInfo->bytes_sent, time()); |
||||
} |
||||
|
||||
if ($dbPeerRemote->uptime < $connectedPeerInfo->uptime) { |
||||
|
||||
$debug['yggdrasil']['peer']['remote']['total']['update'] += |
||||
$db->updatePeerRemoteUptime($dbPeerRemote->dbPeerRemoteId, $connectedPeerInfo->uptime, time()); |
||||
} |
||||
|
||||
} else { |
||||
|
||||
$peerRemoteId = $db->addPeerRemote($dbPeerId, |
||||
$connectedPeerRemoteUrl->host->scheme, |
||||
$connectedPeerRemoteUrl->host->name, |
||||
$connectedPeerRemoteUrl->host->port, |
||||
$connectedPeerInfo->bytes_recvd, |
||||
$connectedPeerInfo->bytes_sent, |
||||
$connectedPeerInfo->uptime, |
||||
time()); |
||||
|
||||
$debug['yggdrasil']['peer']['remote']['total']['insert']++; |
||||
} |
||||
} |
||||
|
||||
$debug['yggdrasil']['peer']['total']['insert']++; |
||||
} |
||||
|
||||
$debug['yggdrasil']['peer']['total']['online']++; |
||||
|
||||
$db->commit(); |
||||
|
||||
} catch(Exception $e) { |
||||
|
||||
$db->rollBack(); |
||||
|
||||
var_dump($e); |
||||
|
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Debug output |
||||
$debug['time']['total'] = microtime(true) - $debug['time']['total']; |
||||
|
||||
print_r( |
||||
array_merge($debug, [ |
||||
'db' => [ |
||||
'total' => [ |
||||
'select' => $db->getDebug()->query->select->total, |
||||
'insert' => $db->getDebug()->query->insert->total, |
||||
'update' => $db->getDebug()->query->update->total, |
||||
'delete' => $db->getDebug()->query->delete->total, |
||||
] |
||||
] |
||||
]) |
||||
); |
Binary file not shown.
@ -0,0 +1,164 @@
@@ -0,0 +1,164 @@
|
||||
<?php |
||||
|
||||
class MySQL { |
||||
|
||||
private PDO $_db; |
||||
|
||||
private object $_debug; |
||||
|
||||
public function __construct(string $host, int $port, string $database, string $username, string $password) { |
||||
|
||||
$this->_db = new PDO('mysql:dbname=' . $database . ';host=' . $host . ';port=' . $port . ';charset=utf8', $username, $password, [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8']); |
||||
$this->_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); |
||||
$this->_db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); |
||||
$this->_db->setAttribute(PDO::ATTR_TIMEOUT, 600); |
||||
|
||||
$this->_debug = (object) |
||||
[ |
||||
'query' => (object) |
||||
[ |
||||
'select' => (object) |
||||
[ |
||||
'total' => 0 |
||||
], |
||||
'insert' => (object) |
||||
[ |
||||
'total' => 0 |
||||
], |
||||
'update' => (object) |
||||
[ |
||||
'total' => 0 |
||||
], |
||||
'delete' => (object) |
||||
[ |
||||
'total' => 0 |
||||
], |
||||
] |
||||
]; |
||||
} |
||||
|
||||
// Tools |
||||
public function beginTransaction() { |
||||
|
||||
$this->_db->beginTransaction(); |
||||
} |
||||
|
||||
public function commit() { |
||||
|
||||
$this->_db->commit(); |
||||
} |
||||
|
||||
public function rollBack() { |
||||
|
||||
$this->_db->rollBack(); |
||||
} |
||||
|
||||
public function getDebug() { |
||||
|
||||
return $this->_debug; |
||||
} |
||||
|
||||
// Peer |
||||
public function addPeer(string $address, string $key, int $timeAdded) { |
||||
|
||||
$this->_debug->query->insert->total++; |
||||
|
||||
$query = $this->_db->prepare('INSERT INTO `peer` SET `address` = ?, `key` = ?, `timeAdded` = ?'); |
||||
|
||||
$query->execute([$address, $key, $timeAdded]); |
||||
|
||||
return $this->_db->lastInsertId(); |
||||
} |
||||
|
||||
public function findPeer(string $address) { |
||||
|
||||
$this->_debug->query->select->total++; |
||||
|
||||
$query = $this->_db->prepare('SELECT * FROM `peer` WHERE `address` = ? LIMIT 1'); |
||||
|
||||
$query->execute([$address]); |
||||
|
||||
return $query->fetch(); |
||||
} |
||||
|
||||
public function getPeers() { |
||||
|
||||
$this->_debug->query->select->total++; |
||||
|
||||
$query = $this->_db->prepare('SELECT * FROM `peer`'); |
||||
|
||||
$query->execute(); |
||||
|
||||
return $query->fetchAll(); |
||||
} |
||||
|
||||
// Peer remote |
||||
public function addPeerRemote(int $peerId, string $scheme, string $host, int $port, int $received, int $sent, float $uptime, int $timeAdded) { |
||||
|
||||
$this->_debug->query->insert->total++; |
||||
|
||||
$query = $this->_db->prepare('INSERT INTO `peerRemote` SET `peerId` = ?, |
||||
`scheme` = ?, |
||||
`host` = ?, |
||||
`port` = ?, |
||||
`received` = ?, |
||||
`sent` = ?, |
||||
`uptime` = ?, |
||||
`timeAdded` = ?'); |
||||
|
||||
$query->execute([$peerId, $scheme, $host, $port, $received, $sent, $uptime, $timeAdded]); |
||||
|
||||
return $this->_db->lastInsertId(); |
||||
} |
||||
|
||||
public function updatePeerRemoteReceived(int $peerRemoteId, int $received, int $timeUpdated) { |
||||
|
||||
$this->_debug->query->update->total++; |
||||
|
||||
$query = $this->_db->prepare('UPDATE `peerRemote` SET `received` = ?, `timeUpdated` = ? WHERE `peerRemoteId` = ? LIMIT 1'); |
||||
|
||||
$query->execute([$received, $timeUpdated, $peerRemoteId]); |
||||
|
||||
return $query->rowCount(); |
||||
} |
||||
|
||||
public function updatePeerRemoteSent(int $peerRemoteId, int $sent, int $timeUpdated) { |
||||
|
||||
$this->_debug->query->update->total++; |
||||
|
||||
$query = $this->_db->prepare('UPDATE `peerRemote` SET `sent` = ?, `timeUpdated` = ? WHERE `peerRemoteId` = ? LIMIT 1'); |
||||
|
||||
$query->execute([$sent, $timeUpdated, $peerRemoteId]); |
||||
|
||||
return $query->rowCount(); |
||||
} |
||||
|
||||
public function updatePeerRemoteUptime(int $peerRemoteId, float $uptime, int $timeUpdated) { |
||||
|
||||
$this->_debug->query->update->total++; |
||||
|
||||
$query = $this->_db->prepare('UPDATE `peerRemote` SET `uptime` = ?, `timeUpdated` = ? WHERE `peerRemoteId` = ? LIMIT 1'); |
||||
|
||||
$query->execute([$uptime, $timeUpdated, $peerRemoteId]); |
||||
|
||||
return $query->rowCount(); |
||||
} |
||||
|
||||
public function findPeerRemote(int $peerId, string $scheme, string $host, int $port) { |
||||
|
||||
$this->_debug->query->select->total++; |
||||
|
||||
$query = $this->_db->prepare('SELECT * FROM `peerRemote` WHERE `peerId` = ? AND `scheme` = ? AND `host` = ? AND `port` = ? LIMIT 1'); |
||||
|
||||
$query->execute([$peerId, $scheme, $host, $port]); |
||||
|
||||
return $query->fetch(); |
||||
} |
||||
|
||||
// Other |
||||
public function optimize() { |
||||
|
||||
$this->_db->query('OPTIMIZE TABLE `peer`'); |
||||
$this->_db->query('OPTIMIZE TABLE `peerRemote`'); |
||||
} |
||||
} |
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
<?php |
||||
|
||||
class URL { |
||||
|
||||
public static function is(string $url) : bool { |
||||
|
||||
return filter_var($url, FILTER_VALIDATE_URL); |
||||
} |
||||
|
||||
public static function parse(string $url) : mixed { |
||||
|
||||
$result = (object) |
||||
[ |
||||
'host' => (object) |
||||
[ |
||||
'url' => null, |
||||
'scheme' => null, |
||||
'name' => null, |
||||
'port' => null, |
||||
], |
||||
'page' => (object) |
||||
[ |
||||
'url' => null, |
||||
'uri' => null, |
||||
'path' => null, |
||||
'query' => null, |
||||
] |
||||
]; |
||||
|
||||
// Validate URL |
||||
if (!self::is($url)) { |
||||
|
||||
return false; |
||||
} |
||||
|
||||
// Parse host |
||||
if ($scheme = parse_url($url, PHP_URL_SCHEME)) { |
||||
|
||||
$result->host->url = $scheme . '://'; |
||||
$result->host->scheme = $scheme; |
||||
|
||||
} else { |
||||
|
||||
return false; |
||||
} |
||||
|
||||
if ($host = parse_url($url, PHP_URL_HOST)) { |
||||
|
||||
$result->host->url .= $host; |
||||
$result->host->name = $host; |
||||
|
||||
} else { |
||||
|
||||
return false; |
||||
} |
||||
|
||||
if ($port = parse_url($url, PHP_URL_PORT)) { |
||||
|
||||
$result->host->url .= ':' . $port; |
||||
$result->host->port = $port; |
||||
|
||||
// port is optional |
||||
} |
||||
|
||||
// Parse page |
||||
if ($path = parse_url($url, PHP_URL_PATH)) { |
||||
|
||||
$result->page->uri = $path; |
||||
$result->page->path = $path; |
||||
} |
||||
|
||||
if ($query = parse_url($url, PHP_URL_QUERY)) { |
||||
|
||||
$result->page->uri .= '?' . $query; |
||||
$result->page->query = '?' . $query; |
||||
} |
||||
|
||||
$result->page->url = $result->host->url . $result->page->uri; |
||||
|
||||
return $result; |
||||
} |
||||
} |
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
<?php |
||||
|
||||
class Yggdrasil { |
||||
|
||||
private static function _exec(string $cmd) : mixed { |
||||
|
||||
if (false !== exec('yggdrasilctl -json getPeers', $output)) { |
||||
|
||||
$rows = []; |
||||
|
||||
foreach($output as $row){ |
||||
|
||||
$rows[] = $row; |
||||
} |
||||
|
||||
if ($result = @json_decode(implode(PHP_EOL, $rows))) { |
||||
|
||||
return $result; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
public static function getPeers() : mixed { |
||||
|
||||
if (false === $result = self::_exec('yggdrasilctl -json getPeers')) { |
||||
|
||||
return false; |
||||
} |
||||
|
||||
if (empty($result->peers)) { |
||||
|
||||
return false; |
||||
} |
||||
|
||||
foreach ((object) $result->peers as $peer) { |
||||
|
||||
switch (false) { |
||||
|
||||
case isset($peer->bytes_recvd): |
||||
case isset($peer->bytes_sent): |
||||
case isset($peer->remote): |
||||
case isset($peer->port): |
||||
case isset($peer->key): |
||||
case isset($peer->uptime): |
||||
case !empty($peer->coords): |
||||
|
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return $result->peers; |
||||
} |
||||
} |
After Width: | Height: | Size: 39 KiB |
Loading…
Reference in new issue