Update main monitor pane

This commit is contained in:
DariusIII
2026-08-04 10:47:02 +02:00
parent 0bad27d5a1
commit 14eb5c85c0
7 changed files with 285 additions and 210 deletions
+3
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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]+)?$')}
+117 -111
View File
@@ -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);
} }
+34 -45
View File
@@ -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'],
+72
View File
@@ -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),
);
}
} }