mirror of
https://github.com/YGGverse/YGGstate.git
synced 2025-02-03 18:34:20 +00:00
initial commit
This commit is contained in:
parent
4866fcd2b0
commit
23c413cd6b
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.vscode
|
||||||
|
.ftpignore
|
||||||
|
|
||||||
|
/config/app.php
|
||||||
|
/database/yggstate.mwb.bak
|
54
config/app.php.example
Normal file
54
config/app.php.example
Normal file
@ -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);
|
156
crontab/crawler.php
Normal file
156
crontab/crawler.php
Normal file
@ -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,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
);
|
BIN
database/yggstate.mwb
Normal file
BIN
database/yggstate.mwb
Normal file
Binary file not shown.
164
library/mysql.php
Normal file
164
library/mysql.php
Normal file
@ -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`');
|
||||||
|
}
|
||||||
|
}
|
82
library/url.php
Normal file
82
library/url.php
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
55
library/yggdrasil.php
Normal file
55
library/yggdrasil.php
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
BIN
media/db-prototype.png
Normal file
BIN
media/db-prototype.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
0
public/index.html
Normal file
0
public/index.html
Normal file
Loading…
x
Reference in New Issue
Block a user