mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
Update main monitor pane
This commit is contained in:
@@ -349,6 +349,9 @@ TINYMCE_API_KEY=
|
|||||||
TMUX_USE_POWERLINE=true
|
TMUX_USE_POWERLINE=true
|
||||||
TMUX_USE_NERD_FONTS=true
|
TMUX_USE_NERD_FONTS=true
|
||||||
TMUX_TERMINAL=tmux-256color
|
TMUX_TERMINAL=tmux-256color
|
||||||
|
# Main monitor loop delay and slow-statistics refresh interval, in seconds.
|
||||||
|
TMUX_MONITOR_DELAY=10
|
||||||
|
TMUX_REFRESH_INTERVAL=60
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────
|
||||||
# Docker / Sail
|
# Docker / Sail
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class TmuxMonitor extends Command
|
|||||||
|
|
||||||
// Increment iteration and sleep
|
// Increment iteration and sleep
|
||||||
$this->monitor->incrementIteration();
|
$this->monitor->incrementIteration();
|
||||||
sleep(10);
|
sleep(max(1, (int) config('tmux.monitor.delay', 10)));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info('🛑 Monitor stopped by exit flag');
|
$this->info('🛑 Monitor stopped by exit flag');
|
||||||
|
|||||||
@@ -235,14 +235,25 @@ final class AdditionalCandidateQuery
|
|||||||
*/
|
*/
|
||||||
public static function backlogCounts(): array
|
public static function backlogCounts(): array
|
||||||
{
|
{
|
||||||
$counts = ['total' => 0, 'available' => 0];
|
$query = self::baseBuilder(includeClaimed: true)
|
||||||
|
->selectRaw('COUNT(*) AS total_count');
|
||||||
|
|
||||||
foreach (self::bucketBacklog() as $backlog) {
|
if (self::supportsClaims()) {
|
||||||
$counts['total'] += $backlog['total'];
|
$query->selectRaw(
|
||||||
$counts['available'] += $backlog['available'];
|
'SUM(CASE WHEN r.'.self::CLAIMED_AT_COLUMN.' IS NULL OR r.'.self::CLAIMED_AT_COLUMN.' < ? THEN 1 ELSE 0 END) AS available_count',
|
||||||
|
[self::claimStaleBefore()],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$query->selectRaw('COUNT(*) AS available_count');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $counts;
|
/** @var object{total_count: int|string|null, available_count: int|string|null}|null $counts */
|
||||||
|
$counts = $query->toBase()->first();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total' => (int) ($counts->total_count ?? 0),
|
||||||
|
'available' => (int) ($counts->available_count ?? 0),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+42
-48
@@ -49,53 +49,51 @@ class Tmux
|
|||||||
return $runVar['connections'];
|
return $runVar['connections'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUSPConnections(string $which, mixed $connections): mixed
|
/**
|
||||||
|
* @param array<string, mixed> $connections
|
||||||
|
* @return array<string, array{active: int, total: int}>
|
||||||
|
*/
|
||||||
|
public function getUSPConnections(string $which, array $connections, ?string $socketSnapshot = null): array
|
||||||
{
|
{
|
||||||
switch ($which) {
|
[$ipKey, $portKey] = $which === 'alternate'
|
||||||
case 'alternate':
|
? ['ip_a', 'port_a']
|
||||||
$ip = 'ip_a';
|
: ['ip', 'port'];
|
||||||
$port = 'port_a';
|
|
||||||
break;
|
$ip = (string) ($connections[$ipKey] ?? '');
|
||||||
case 'primary':
|
$port = (string) ($connections[$portKey] ?? '');
|
||||||
default:
|
$needles = array_values(array_filter([
|
||||||
$ip = 'ip';
|
$ip !== '' && $port !== '' ? $ip.':'.$port : null,
|
||||||
$port = 'port';
|
$ip !== '' ? $ip.':https' : null,
|
||||||
break;
|
$port !== '' ? $port : null,
|
||||||
|
$ip !== '' ? $ip : null,
|
||||||
|
]));
|
||||||
|
$lines = preg_split('/\R/', $socketSnapshot ?? $this->getSocketSnapshot()) ?: [];
|
||||||
|
|
||||||
|
foreach ($needles as $needle) {
|
||||||
|
$matchingLines = array_filter(
|
||||||
|
$lines,
|
||||||
|
static fn (string $line): bool => str_contains($line, $needle),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($matchingLines !== []) {
|
||||||
|
return [
|
||||||
|
$which => [
|
||||||
|
'active' => count(array_filter(
|
||||||
|
$matchingLines,
|
||||||
|
static fn (string $line): bool => str_contains($line, 'ESTAB'),
|
||||||
|
)),
|
||||||
|
'total' => count($matchingLines),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize result structure
|
return [$which => ['active' => 0, 'total' => 0]];
|
||||||
$runVar['conncounts'][$which]['active'] = '0';
|
}
|
||||||
$runVar['conncounts'][$which]['total'] = '0';
|
|
||||||
|
|
||||||
// 1) Try exact host:port
|
public function getSocketSnapshot(): string
|
||||||
if (! empty($connections[$ip]) && ! empty($connections[$port])) {
|
{
|
||||||
$needle = escapeshellarg($connections[$ip].':'.$connections[$port]);
|
return (string) shell_exec('ss -nH 2>/dev/null');
|
||||||
$runVar['conncounts'][$which]['active'] = str_replace("\n", '', shell_exec("ss -n 2>/dev/null | grep -- $needle | grep -c -- ESTAB")) ?: '0';
|
|
||||||
$runVar['conncounts'][$which]['total'] = str_replace("\n", '', shell_exec("ss -n 2>/dev/null | grep -c -- $needle")) ?: '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) Fallback to host:https
|
|
||||||
if ((int) $runVar['conncounts'][$which]['active'] === 0 && (int) $runVar['conncounts'][$which]['total'] === 0 && ! empty($connections[$ip])) {
|
|
||||||
$needleHttps = escapeshellarg($connections[$ip].':https');
|
|
||||||
$runVar['conncounts'][$which]['active'] = str_replace("\n", '', shell_exec("ss -n 2>/dev/null | grep -- $needleHttps | grep -c -- ESTAB")) ?: '0';
|
|
||||||
$runVar['conncounts'][$which]['total'] = str_replace("\n", '', shell_exec("ss -n 2>/dev/null | grep -c -- $needleHttps")) ?: '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) Fallback to port only
|
|
||||||
if ((int) $runVar['conncounts'][$which]['active'] === 0 && (int) $runVar['conncounts'][$which]['total'] === 0 && ! empty($connections[$port])) {
|
|
||||||
$needlePort = escapeshellarg((string) $connections[$port]);
|
|
||||||
$runVar['conncounts'][$which]['active'] = str_replace("\n", '', shell_exec("ss -n 2>/dev/null | grep -- $needlePort | grep -c -- ESTAB")) ?: '0';
|
|
||||||
$runVar['conncounts'][$which]['total'] = str_replace("\n", '', shell_exec("ss -n 2>/dev/null | grep -c -- $needlePort")) ?: '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4) Fallback to host only
|
|
||||||
if ((int) $runVar['conncounts'][$which]['active'] === 0 && (int) $runVar['conncounts'][$which]['total'] === 0 && ! empty($connections[$ip])) {
|
|
||||||
$needleIp = escapeshellarg($connections[$ip]);
|
|
||||||
$runVar['conncounts'][$which]['active'] = str_replace("\n", '', shell_exec("ss -n 2>/dev/null | grep -- $needleIp | grep -c -- ESTAB")) ?: '0';
|
|
||||||
$runVar['conncounts'][$which]['total'] = str_replace("\n", '', shell_exec("ss -n 2>/dev/null | grep -c -- $needleIp")) ?: '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $runVar['conncounts'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -377,10 +375,6 @@ class Tmux
|
|||||||
"
|
"
|
||||||
SELECT
|
SELECT
|
||||||
(SELECT TABLE_ROWS FROM information_schema.TABLES WHERE table_name = 'predb' AND TABLE_SCHEMA = %1\$s) AS predb,
|
(SELECT TABLE_ROWS FROM information_schema.TABLES WHERE table_name = 'predb' AND TABLE_SCHEMA = %1\$s) AS predb,
|
||||||
(SELECT TABLE_ROWS FROM information_schema.TABLES WHERE table_name = 'missed_parts' AND TABLE_SCHEMA = %1\$s) AS missed_parts_table,
|
|
||||||
(SELECT TABLE_ROWS FROM information_schema.TABLES WHERE table_name = 'parts' AND TABLE_SCHEMA = %1\$s) AS parts_table,
|
|
||||||
(SELECT TABLE_ROWS FROM information_schema.TABLES WHERE table_name = 'binaries' AND TABLE_SCHEMA = %1\$s) AS binaries_table,
|
|
||||||
(SELECT TABLE_ROWS FROM information_schema.TABLES WHERE table_name = 'collections' AND TABLE_SCHEMA = %1\$s) AS releases,
|
|
||||||
(SELECT COUNT(id) FROM usenet_groups WHERE first_record IS NOT NULL AND backfill = 1
|
(SELECT COUNT(id) FROM usenet_groups WHERE first_record IS NOT NULL AND backfill = 1
|
||||||
AND (now() - INTERVAL backfill_target DAY) < first_record_postdate
|
AND (now() - INTERVAL backfill_target DAY) < first_record_postdate
|
||||||
) AS backfill_groups_days,
|
) AS backfill_groups_days,
|
||||||
@@ -449,7 +443,7 @@ class Tmux
|
|||||||
{
|
{
|
||||||
return DB::select(
|
return DB::select(
|
||||||
"
|
"
|
||||||
SELECT TABLE_NAME AS name
|
SELECT TABLE_NAME AS name, TABLE_ROWS AS row_count
|
||||||
FROM information_schema.TABLES
|
FROM information_schema.TABLES
|
||||||
WHERE TABLE_SCHEMA = (SELECT DATABASE())
|
WHERE TABLE_SCHEMA = (SELECT DATABASE())
|
||||||
AND TABLE_NAME REGEXP {escapeString('^(multigroup_)?(collections|binaries|parts|missed_parts)(_[0-9]+)?$')}
|
AND TABLE_NAME REGEXP {escapeString('^(multigroup_)?(collections|binaries|parts|missed_parts)(_[0-9]+)?$')}
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ class TmuxMonitorService
|
|||||||
|
|
||||||
protected bool $shouldContinue = true;
|
protected bool $shouldContinue = true;
|
||||||
|
|
||||||
|
protected float $lastOperationalRefreshAt = 0.0;
|
||||||
|
|
||||||
|
protected float $lastSlowRefreshAt = 0.0;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->tmux = new Tmux;
|
$this->tmux = new Tmux;
|
||||||
@@ -124,46 +128,52 @@ class TmuxMonitorService
|
|||||||
*/
|
*/
|
||||||
public function collectStatistics(): array
|
public function collectStatistics(): array
|
||||||
{
|
{
|
||||||
// Refresh settings periodically
|
$now = microtime(true);
|
||||||
$monitorDelay = (int) ($this->runVar['settings']['monitor'] ?? 60);
|
$monitorDelay = max(1, (int) ($this->runVar['settings']['monitor'] ?? 60));
|
||||||
$timeSinceLastRefresh = time() - ($this->runVar['timers']['timer2'] ?? 0);
|
$slowRefreshDelay = max($monitorDelay, (int) config('tmux.monitor.refresh_interval', 60));
|
||||||
|
$statisticsChanged = false;
|
||||||
|
|
||||||
if ($this->iterations === 1 || $timeSinceLastRefresh >= $monitorDelay) {
|
if ($this->refreshIsDue($this->lastOperationalRefreshAt, $monitorDelay, $now)) {
|
||||||
$this->refreshStatistics();
|
$this->refreshOperationalStatistics();
|
||||||
$this->runVar['timers']['timer2'] = time();
|
$this->lastOperationalRefreshAt = $now;
|
||||||
|
$this->runVar['timers']['timer2'] = (int) $now;
|
||||||
|
$statisticsChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update connection counts
|
if ($this->refreshIsDue($this->lastSlowRefreshAt, $slowRefreshDelay, $now)) {
|
||||||
$this->updateConnectionCounts();
|
$this->refreshSlowStatistics();
|
||||||
|
$this->lastSlowRefreshAt = $now;
|
||||||
|
$statisticsChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Set killswitches
|
if ($statisticsChanged) {
|
||||||
|
$this->calculateStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->updateConnectionCounts();
|
||||||
$this->setKillswitches();
|
$this->setKillswitches();
|
||||||
|
|
||||||
return $this->runVar;
|
return $this->runVar;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function refreshOperationalStatistics(): void
|
||||||
* Refresh all statistics from database
|
|
||||||
*/
|
|
||||||
protected function refreshStatistics(): void
|
|
||||||
{
|
{
|
||||||
$timer = time();
|
$timer = microtime(true);
|
||||||
|
|
||||||
// Refresh settings
|
|
||||||
$this->runVar['settings'] = $this->tmux->getMonitorSettings();
|
$this->runVar['settings'] = $this->tmux->getMonitorSettings();
|
||||||
$this->runVar['timers']['query']['tmux_time'] = time() - $timer;
|
$this->runVar['timers']['query']['tmux_time'] = microtime(true) - $timer;
|
||||||
|
|
||||||
// Get category counts
|
|
||||||
$this->getCategoryCounts();
|
|
||||||
|
|
||||||
// Get process counts
|
|
||||||
$this->getProcessCounts();
|
$this->getProcessCounts();
|
||||||
|
}
|
||||||
|
|
||||||
// Get table counts
|
protected function refreshSlowStatistics(): void
|
||||||
|
{
|
||||||
|
$this->getCategoryCounts();
|
||||||
$this->getTableCounts();
|
$this->getTableCounts();
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate diffs and percentages
|
protected function refreshIsDue(float $lastRefreshAt, int $interval, float $now): bool
|
||||||
$this->calculateStatistics();
|
{
|
||||||
|
return $lastRefreshAt === 0.0 || ($now - $lastRefreshAt) >= $interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,41 +181,46 @@ class TmuxMonitorService
|
|||||||
*/
|
*/
|
||||||
protected function getCategoryCounts(): void
|
protected function getCategoryCounts(): void
|
||||||
{
|
{
|
||||||
$timer = time();
|
$timer = microtime(true);
|
||||||
|
$bindings = [];
|
||||||
|
$aggregates = [];
|
||||||
|
|
||||||
$this->runVar['counts']['now']['tv'] = Release::query()
|
foreach ($this->categoryRanges() as $name => [$minimum, $maximum]) {
|
||||||
->whereBetween('categories_id', [Category::TV_ROOT, Category::TV_OTHER])
|
$aggregates[] = "SUM(CASE WHEN categories_id BETWEEN ? AND ? THEN 1 ELSE 0 END) AS {$name}";
|
||||||
->count('id');
|
$bindings[] = $minimum;
|
||||||
|
$bindings[] = $maximum;
|
||||||
|
}
|
||||||
|
|
||||||
$this->runVar['counts']['now']['movies'] = Release::query()
|
try {
|
||||||
->whereBetween('categories_id', [Category::MOVIE_ROOT, Category::MOVIE_OTHER])
|
$counts = Release::query()->selectRaw(implode(', ', $aggregates), $bindings)->first();
|
||||||
->count('id');
|
|
||||||
|
|
||||||
$this->runVar['counts']['now']['audio'] = Release::query()
|
if ($counts !== null) {
|
||||||
->whereBetween('categories_id', [Category::MUSIC_ROOT, Category::MUSIC_OTHER])
|
foreach (array_keys($this->categoryRanges()) as $name) {
|
||||||
->count('id');
|
$this->runVar['counts']['now'][$name] = (int) $counts->getAttribute($name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
logger()->error('Error collecting category counts: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
$this->runVar['counts']['now']['books'] = Release::query()
|
$this->runVar['timers']['query']['init_time'] = microtime(true) - $timer;
|
||||||
->whereBetween('categories_id', [Category::BOOKS_ROOT, Category::BOOKS_UNKNOWN])
|
}
|
||||||
->count('id');
|
|
||||||
|
|
||||||
$this->runVar['counts']['now']['console'] = Release::query()
|
/**
|
||||||
->whereBetween('categories_id', [Category::GAME_ROOT, Category::GAME_OTHER])
|
* @return array<string, array{int, int}>
|
||||||
->count('id');
|
*/
|
||||||
|
protected function categoryRanges(): array
|
||||||
$this->runVar['counts']['now']['pc'] = Release::query()
|
{
|
||||||
->whereBetween('categories_id', [Category::PC_ROOT, Category::PC_PHONE_ANDROID])
|
return [
|
||||||
->count('id');
|
'tv' => [Category::TV_ROOT, Category::TV_OTHER],
|
||||||
|
'movies' => [Category::MOVIE_ROOT, Category::MOVIE_OTHER],
|
||||||
$this->runVar['counts']['now']['xxx'] = Release::query()
|
'audio' => [Category::MUSIC_ROOT, Category::MUSIC_OTHER],
|
||||||
->whereBetween('categories_id', [Category::XXX_ROOT, Category::XXX_OTHER])
|
'books' => [Category::BOOKS_ROOT, Category::BOOKS_UNKNOWN],
|
||||||
->count('id');
|
'console' => [Category::GAME_ROOT, Category::GAME_OTHER],
|
||||||
|
'pc' => [Category::PC_ROOT, Category::PC_PHONE_ANDROID],
|
||||||
$this->runVar['counts']['now']['misc'] = Release::query()
|
'xxx' => [Category::XXX_ROOT, Category::XXX_OTHER],
|
||||||
->whereBetween('categories_id', [Category::OTHER_ROOT, Category::OTHER_HASHED])
|
'misc' => [Category::OTHER_ROOT, Category::OTHER_HASHED],
|
||||||
->count('id');
|
];
|
||||||
|
|
||||||
$this->runVar['timers']['query']['init_time'] = time() - $timer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,7 +228,7 @@ class TmuxMonitorService
|
|||||||
*/
|
*/
|
||||||
protected function getProcessCounts(): void
|
protected function getProcessCounts(): void
|
||||||
{
|
{
|
||||||
$timer = time();
|
$timer = microtime(true);
|
||||||
$this->runVar['counts']['now']['work'] = $this->runVar['counts']['now']['work'] ?? 0;
|
$this->runVar['counts']['now']['work'] = $this->runVar['counts']['now']['work'] ?? 0;
|
||||||
$this->runVar['counts']['now']['work_available'] = $this->runVar['counts']['now']['work_available'] ?? 0;
|
$this->runVar['counts']['now']['work_available'] = $this->runVar['counts']['now']['work_available'] ?? 0;
|
||||||
|
|
||||||
@@ -229,10 +244,10 @@ class TmuxMonitorService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->runVar['timers']['query']['proc1_time'] = time() - $timer;
|
$this->runVar['timers']['query']['proc1_time'] = microtime(true) - $timer;
|
||||||
|
|
||||||
// Process 2
|
// Process 2
|
||||||
$timer2 = time();
|
$timer2 = microtime(true);
|
||||||
$maxSize = $this->runVar['settings']['maxsize_pp'] ?? '';
|
$maxSize = $this->runVar['settings']['maxsize_pp'] ?? '';
|
||||||
$minSize = $this->runVar['settings']['minsize_pp'] ?? '';
|
$minSize = $this->runVar['settings']['minsize_pp'] ?? '';
|
||||||
|
|
||||||
@@ -253,7 +268,7 @@ class TmuxMonitorService
|
|||||||
$this->runVar['counts']['now']['work'] = $additionalBacklog['total'];
|
$this->runVar['counts']['now']['work'] = $additionalBacklog['total'];
|
||||||
$this->runVar['counts']['now']['work_available'] = $additionalBacklog['available'];
|
$this->runVar['counts']['now']['work_available'] = $additionalBacklog['available'];
|
||||||
|
|
||||||
$this->runVar['timers']['query']['proc2_time'] = time() - $timer2;
|
$this->runVar['timers']['query']['proc2_time'] = microtime(true) - $timer2;
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
logger()->error('Error collecting process counts: '.$e->getMessage());
|
logger()->error('Error collecting process counts: '.$e->getMessage());
|
||||||
@@ -265,77 +280,65 @@ class TmuxMonitorService
|
|||||||
*/
|
*/
|
||||||
protected function getTableCounts(): void
|
protected function getTableCounts(): void
|
||||||
{
|
{
|
||||||
$timer = time();
|
$timer = microtime(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->runVar['counts']['now']['collections_table'] = Collection::query()->count();
|
$this->runVar['counts']['now']['collections_table'] = Collection::query()->count();
|
||||||
|
$this->runVar['counts']['now']['releases'] = Release::query()->count();
|
||||||
|
|
||||||
// Get binaries/parts counts from information_schema or use approximation
|
foreach ($this->aggregateTableRowEstimates($this->tmux->cbpmTableQuery()) as $key => $count) {
|
||||||
$dbName = config('nntmux.db_name');
|
$this->runVar['counts']['now'][$key] = $count;
|
||||||
$tables = $this->tmux->cbpmTableQuery();
|
|
||||||
|
|
||||||
$this->runVar['counts']['now']['binaries_table'] = 0;
|
|
||||||
$this->runVar['counts']['now']['parts_table'] = 0;
|
|
||||||
$this->runVar['counts']['now']['missed_parts_table'] = 0;
|
|
||||||
|
|
||||||
foreach ($tables as $table) {
|
|
||||||
$tableName = $table->name;
|
|
||||||
$count = $this->getTableRowCount($tableName);
|
|
||||||
|
|
||||||
if (str_contains($tableName, 'binaries')) {
|
|
||||||
$this->runVar['counts']['now']['binaries_table'] += $count;
|
|
||||||
} elseif (str_contains($tableName, 'missed_parts')) {
|
|
||||||
$this->runVar['counts']['now']['missed_parts_table'] += $count;
|
|
||||||
} elseif (str_contains($tableName, 'parts')) {
|
|
||||||
$this->runVar['counts']['now']['parts_table'] += $count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->runVar['timers']['query']['tpg_time'] = time() - $timer;
|
$this->runVar['timers']['query']['tpg_time'] = microtime(true) - $timer;
|
||||||
|
|
||||||
// Get additional table counts (query 4)
|
foreach ([4, 6] as $queryNumber) {
|
||||||
$timer4 = time();
|
$result = DB::selectOne($this->tmux->proc_query($queryNumber, (string) config('nntmux.db_name'), ''));
|
||||||
$proc4Query = $this->tmux->proc_query(4, $dbName, '');
|
if ($result === null) {
|
||||||
$proc4Result = DB::selectOne($proc4Query);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($proc4Result) {
|
$target = $queryNumber === 4 ? 'counts' : 'timers';
|
||||||
foreach ((array) $proc4Result as $key => $value) {
|
$section = $queryNumber === 4 ? 'now' : 'newOld';
|
||||||
$this->runVar['counts']['now'][$key] = $value;
|
foreach ((array) $result as $key => $value) {
|
||||||
|
$this->runVar[$target][$section][$key] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get newest/oldest data (query 6)
|
|
||||||
$timer6 = time();
|
|
||||||
$proc6Query = $this->tmux->proc_query(6, $dbName, '');
|
|
||||||
$proc6Result = DB::selectOne($proc6Query);
|
|
||||||
|
|
||||||
if ($proc6Result) {
|
|
||||||
foreach ((array) $proc6Result as $key => $value) {
|
|
||||||
$this->runVar['timers']['newOld'][$key] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
logger()->error('Error collecting table counts: '.$e->getMessage());
|
logger()->error('Error collecting table counts: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get row count for a table
|
* @param array<array-key, mixed> $tables
|
||||||
|
* @return array{binaries_table: int, parts_table: int, missed_parts_table: int}
|
||||||
*/
|
*/
|
||||||
protected function getTableRowCount(string $tableName): int
|
protected function aggregateTableRowEstimates(array $tables): array
|
||||||
{
|
{
|
||||||
try {
|
$counts = [
|
||||||
$result = DB::selectOne(
|
'binaries_table' => 0,
|
||||||
'SELECT TABLE_ROWS AS count FROM information_schema.TABLES
|
'parts_table' => 0,
|
||||||
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = DATABASE()',
|
'missed_parts_table' => 0,
|
||||||
[$tableName]
|
];
|
||||||
);
|
|
||||||
|
|
||||||
return (int) ($result->count ?? 0);
|
foreach ($tables as $table) {
|
||||||
} catch (\Exception $e) {
|
if (! is_object($table)) {
|
||||||
return 0;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tableName = (string) ($table->name ?? '');
|
||||||
|
$count = (int) ($table->row_count ?? 0);
|
||||||
|
|
||||||
|
if (str_contains($tableName, 'binaries')) {
|
||||||
|
$counts['binaries_table'] += $count;
|
||||||
|
} elseif (str_contains($tableName, 'missed_parts')) {
|
||||||
|
$counts['missed_parts_table'] += $count;
|
||||||
|
} elseif (str_contains($tableName, 'parts')) {
|
||||||
|
$counts['parts_table'] += $count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -485,15 +488,18 @@ class TmuxMonitorService
|
|||||||
*/
|
*/
|
||||||
protected function updateConnectionCounts(): void
|
protected function updateConnectionCounts(): void
|
||||||
{
|
{
|
||||||
|
$socketSnapshot = $this->tmux->getSocketSnapshot();
|
||||||
$this->runVar['conncounts'] = $this->tmux->getUSPConnections(
|
$this->runVar['conncounts'] = $this->tmux->getUSPConnections(
|
||||||
'primary',
|
'primary',
|
||||||
$this->runVar['connections']
|
$this->runVar['connections'],
|
||||||
|
$socketSnapshot,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ((int) ($this->runVar['constants']['alternate_nntp'] ?? 0) === 1) {
|
if ((int) ($this->runVar['constants']['alternate_nntp'] ?? 0) === 1) {
|
||||||
$alternateConns = $this->tmux->getUSPConnections(
|
$alternateConns = $this->tmux->getUSPConnections(
|
||||||
'alternate',
|
'alternate',
|
||||||
$this->runVar['connections']
|
$this->runVar['connections'],
|
||||||
|
$socketSnapshot,
|
||||||
);
|
);
|
||||||
$this->runVar['conncounts'] = array_merge($this->runVar['conncounts'], $alternateConns);
|
$this->runVar['conncounts'] = array_merge($this->runVar['conncounts'], $alternateConns);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ class TmuxOutput extends Tmux
|
|||||||
|
|
||||||
private mixed $tmpMasks;
|
private mixed $tmpMasks;
|
||||||
|
|
||||||
|
private ?string $gitVersion = null;
|
||||||
|
|
||||||
|
private ?string $gitBranch = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TmuxOutput constructor.
|
* TmuxOutput constructor.
|
||||||
*
|
*
|
||||||
@@ -112,8 +116,11 @@ class TmuxOutput extends Tmux
|
|||||||
{
|
{
|
||||||
$buffer = '';
|
$buffer = '';
|
||||||
$state = ((int) $this->runVar['settings']['is_running'] === 1) ? 'Running' : 'Disabled';
|
$state = ((int) $this->runVar['settings']['is_running'] === 1) ? 'Running' : 'Disabled';
|
||||||
$version = str_replace(["\n", "\r"], '', Process::run('git describe --tags')->output());
|
$this->gitVersion ??= trim(Process::run('git describe --tags')->output());
|
||||||
$branch = str_replace(["\n", "\r"], '', Process::run('git branch --show-current')->output());
|
$this->gitBranch ??= trim(Process::run('git branch --show-current')->output());
|
||||||
|
|
||||||
|
$version = $this->gitVersion;
|
||||||
|
$branch = $this->gitBranch;
|
||||||
|
|
||||||
$buffer .= sprintf(
|
$buffer .= sprintf(
|
||||||
$this->tmpMasks[2],
|
$this->tmpMasks[2],
|
||||||
@@ -395,52 +402,34 @@ class TmuxOutput extends Tmux
|
|||||||
|
|
||||||
protected function _getPaths(): string
|
protected function _getPaths(): string
|
||||||
{
|
{
|
||||||
$buffer = '';
|
$paths = array_values(array_unique(array_filter([
|
||||||
|
$this->runVar['settings']['monitor_path'] ?? null,
|
||||||
|
$this->runVar['settings']['monitor_path_a'] ?? null,
|
||||||
|
$this->runVar['settings']['monitor_path_b'] ?? null,
|
||||||
|
], static fn (mixed $path): bool => is_string($path) && $path !== '' && file_exists($path))));
|
||||||
|
|
||||||
// assign timers from tmux table
|
if ($paths === []) {
|
||||||
$monitor_path = $this->runVar['settings']['monitor_path'];
|
return PHP_EOL;
|
||||||
$monitor_path_a = $this->runVar['settings']['monitor_path_a'];
|
}
|
||||||
$monitor_path_b = $this->runVar['settings']['monitor_path_b'];
|
|
||||||
|
|
||||||
if (($monitor_path !== null && file_exists($monitor_path))
|
$buffer = PHP_EOL;
|
||||||
|| ($monitor_path_a !== null && file_exists($monitor_path_a))
|
$buffer .= sprintf($this->tmpMasks[3], 'File System', 'Used', 'Free');
|
||||||
|| ($monitor_path_b !== null && file_exists($monitor_path_b))) {
|
$buffer .= $this->_getSeparator();
|
||||||
$buffer .= "\n";
|
|
||||||
$buffer .= sprintf($this->tmpMasks[3], 'File System', 'Used', 'Free');
|
|
||||||
$buffer .= $this->_getSeparator();
|
|
||||||
|
|
||||||
if (! empty($monitor_path) && file_exists($monitor_path)) {
|
foreach ($paths as $path) {
|
||||||
$disk_use = $this->decodeSize(disk_total_space($monitor_path) - disk_free_space($monitor_path));
|
$total = disk_total_space($path);
|
||||||
$disk_free = $this->decodeSize(disk_free_space($monitor_path));
|
$free = disk_free_space($path);
|
||||||
if (basename($monitor_path) === '') {
|
if ($total === false || $free === false) {
|
||||||
$show = '/';
|
continue;
|
||||||
} else {
|
|
||||||
$show = basename($monitor_path);
|
|
||||||
}
|
|
||||||
$buffer .= sprintf($this->tmpMasks[4], $show, $disk_use, $disk_free);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! empty($monitor_path_a) && file_exists($monitor_path_a)) {
|
$name = basename($path) ?: '/';
|
||||||
$disk_use = $this->decodeSize(disk_total_space($monitor_path_a) - disk_free_space($monitor_path_a));
|
$buffer .= sprintf(
|
||||||
$disk_free = $this->decodeSize(disk_free_space($monitor_path_a));
|
$this->tmpMasks[4],
|
||||||
if (basename($monitor_path_a) === '') {
|
$name,
|
||||||
$show = '/';
|
$this->decodeSize($total - $free),
|
||||||
} else {
|
$this->decodeSize($free),
|
||||||
$show = basename($monitor_path_a);
|
);
|
||||||
}
|
|
||||||
$buffer .= sprintf($this->tmpMasks[4], $show, $disk_use, $disk_free);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! empty($monitor_path_b) && file_exists($monitor_path_b)) {
|
|
||||||
$disk_use = $this->decodeSize(disk_total_space($monitor_path_b) - disk_free_space($monitor_path_b));
|
|
||||||
$disk_free = $this->decodeSize(disk_free_space($monitor_path_b));
|
|
||||||
if (basename($monitor_path_b) === '') {
|
|
||||||
$show = '/';
|
|
||||||
} else {
|
|
||||||
$show = basename($monitor_path_b);
|
|
||||||
}
|
|
||||||
$buffer .= sprintf($this->tmpMasks[4], $show, $disk_use, $disk_free);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $buffer.PHP_EOL;
|
return $buffer.PHP_EOL;
|
||||||
@@ -455,7 +444,7 @@ class TmuxOutput extends Tmux
|
|||||||
$this->tmpMasks[4],
|
$this->tmpMasks[4],
|
||||||
'Combined',
|
'Combined',
|
||||||
sprintf(
|
sprintf(
|
||||||
'%d %d %d %d %d %d %d',
|
'%.3f %.3f %.3f %.3f %.3f %.3f %.3f',
|
||||||
$this->runVar['timers']['query']['tmux_time'],
|
$this->runVar['timers']['query']['tmux_time'],
|
||||||
$this->runVar['timers']['query']['split_time'],
|
$this->runVar['timers']['query']['split_time'],
|
||||||
$this->runVar['timers']['query']['init_time'],
|
$this->runVar['timers']['query']['init_time'],
|
||||||
@@ -465,7 +454,7 @@ class TmuxOutput extends Tmux
|
|||||||
$this->runVar['timers']['query']['tpg_time']
|
$this->runVar['timers']['query']['tpg_time']
|
||||||
),
|
),
|
||||||
sprintf(
|
sprintf(
|
||||||
'%d %d %d %d %d %d %d',
|
'%.3f %.3f %.3f %.3f %.3f %.3f %.3f',
|
||||||
$this->runVar['timers']['query']['tmux_time'],
|
$this->runVar['timers']['query']['tmux_time'],
|
||||||
$this->runVar['timers']['query']['split1_time'],
|
$this->runVar['timers']['query']['split1_time'],
|
||||||
$this->runVar['timers']['query']['init1_time'],
|
$this->runVar['timers']['query']['init1_time'],
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Tests\Unit;
|
namespace Tests\Unit;
|
||||||
|
|
||||||
|
use App\Services\Tmux\Tmux;
|
||||||
use App\Services\Tmux\TmuxMonitorService;
|
use App\Services\Tmux\TmuxMonitorService;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use ReflectionMethod;
|
use ReflectionMethod;
|
||||||
@@ -47,4 +49,74 @@ class TmuxMonitorServiceTest extends TestCase
|
|||||||
$this->assertSame('0', $updatedRunVar['counts']['diff']['work_available']);
|
$this->assertSame('0', $updatedRunVar['counts']['diff']['work_available']);
|
||||||
$this->assertSame(2, $updatedRunVar['counts']['now']['total_work']);
|
$this->assertSame(2, $updatedRunVar['counts']['now']['total_work']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[DataProvider('refreshScheduleProvider')]
|
||||||
|
public function test_refresh_schedule(float $lastRefreshAt, int $interval, float $now, bool $expected): void
|
||||||
|
{
|
||||||
|
$reflection = new ReflectionClass(TmuxMonitorService::class);
|
||||||
|
$monitor = $reflection->newInstanceWithoutConstructor();
|
||||||
|
$refreshIsDue = new ReflectionMethod(TmuxMonitorService::class, 'refreshIsDue');
|
||||||
|
|
||||||
|
$this->assertSame($expected, $refreshIsDue->invoke($monitor, $lastRefreshAt, $interval, $now));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, array{float, int, float, bool}>
|
||||||
|
*/
|
||||||
|
public static function refreshScheduleProvider(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'first refresh' => [0.0, 60, 100.0, true],
|
||||||
|
'before interval' => [100.0, 60, 159.999, false],
|
||||||
|
'at interval' => [100.0, 60, 160.0, true],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_collection_table_estimates_do_not_contribute_to_release_total(): void
|
||||||
|
{
|
||||||
|
$reflection = new ReflectionClass(TmuxMonitorService::class);
|
||||||
|
$monitor = $reflection->newInstanceWithoutConstructor();
|
||||||
|
$aggregate = new ReflectionMethod(TmuxMonitorService::class, 'aggregateTableRowEstimates');
|
||||||
|
|
||||||
|
$counts = $aggregate->invoke($monitor, [
|
||||||
|
(object) ['name' => 'collections', 'row_count' => 242650],
|
||||||
|
(object) ['name' => 'binaries', 'row_count' => 120],
|
||||||
|
(object) ['name' => 'multigroup_parts_1', 'row_count' => 80],
|
||||||
|
(object) ['name' => 'missed_parts', 'row_count' => 5],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertSame([
|
||||||
|
'binaries_table' => 120,
|
||||||
|
'parts_table' => 80,
|
||||||
|
'missed_parts_table' => 5,
|
||||||
|
], $counts);
|
||||||
|
$this->assertArrayNotHasKey('releases', $counts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_connection_counts_are_derived_from_one_socket_snapshot(): void
|
||||||
|
{
|
||||||
|
$reflection = new ReflectionClass(Tmux::class);
|
||||||
|
/** @var Tmux $tmux */
|
||||||
|
$tmux = $reflection->newInstanceWithoutConstructor();
|
||||||
|
$snapshot = implode("\n", [
|
||||||
|
'ESTAB 0 0 10.0.0.2:40000 192.0.2.10:119',
|
||||||
|
'CLOSE-WAIT 0 0 10.0.0.2:40001 192.0.2.10:119',
|
||||||
|
'ESTAB 0 0 10.0.0.2:40002 192.0.2.20:563',
|
||||||
|
]);
|
||||||
|
$connections = [
|
||||||
|
'ip' => '192.0.2.10',
|
||||||
|
'port' => 119,
|
||||||
|
'ip_a' => '192.0.2.20',
|
||||||
|
'port_a' => 563,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
['primary' => ['active' => 1, 'total' => 2]],
|
||||||
|
$tmux->getUSPConnections('primary', $connections, $snapshot),
|
||||||
|
);
|
||||||
|
$this->assertSame(
|
||||||
|
['alternate' => ['active' => 1, 'total' => 1]],
|
||||||
|
$tmux->getUSPConnections('alternate', $connections, $snapshot),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user