mirror of
https://github.com/YGGverse/YGGstate.git
synced 2025-01-24 13:34:27 +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