Browse Source

initial commit

yggdrasil-0.4.7
ghost 1 year ago
parent
commit
23c413cd6b
  1. 5
      .gitignore
  2. 54
      config/app.php.example
  3. 156
      crontab/crawler.php
  4. BIN
      database/yggstate.mwb
  5. 164
      library/mysql.php
  6. 82
      library/url.php
  7. 55
      library/yggdrasil.php
  8. BIN
      media/db-prototype.png
  9. 0
      public/index.html

5
.gitignore vendored

@ -0,0 +1,5 @@
.vscode
.ftpignore
/config/app.php
/database/yggstate.mwb.bak

54
config/app.php.example

@ -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

@ -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

Binary file not shown.

164
library/mysql.php

@ -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

@ -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

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

0
public/index.html

Loading…
Cancel
Save