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 @@ |
|||||||
|
.vscode |
||||||
|
.ftpignore |
||||||
|
|
||||||
|
/config/app.php |
||||||
|
/database/yggstate.mwb.bak |
@ -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 @@ |
|||||||
|
<?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 @@ |
|||||||
|
<?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 @@ |
|||||||
|
<?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 @@ |
|||||||
|
<?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