diff --git a/Plugin/unraid/include/partnership.php b/Plugin/unraid/include/partnership.php index 8f2d9f2..dafd069 100644 --- a/Plugin/unraid/include/partnership.php +++ b/Plugin/unraid/include/partnership.php @@ -240,6 +240,72 @@ function vv_pt_remote_system(string $ip, string $sshKey): array { ]; } +// ── Transfer volume ─────────────────────────────────────────────────────────── +// +// Reads data/db/bandwidth_history.db, one line per completed rsync: +// YYYY-MM-DD|HH:MM|profile|duration|status|bytes +// +// Rows before VV_BW_EPOCH are excluded outright. Until 2026-08-14 rsync.sh read the byte count +// from the wrong field of rsync --stats and every row was written as 0 — 1,423 of them. Summing +// those produces a confident 0.00 GB for any window that reaches back far enough, which is worse +// than an empty card: it is a measurement that looks taken. The cut is on the fix, not on a +// guess about which rows look plausible. +// +// A window whose rows are all zero reports null, not 0, so the UI can say "no transfers +// recorded" rather than assert nothing moved. Those are different claims and only one of them +// is supported by an all-zero column. +define('VV_BW_EPOCH', '2026-08-14'); + +function vv_pt_transfer_stats(): array { + $file = DATA_DIR . '/db/bandwidth_history.db'; + $out = ['epoch' => VV_BW_EPOCH, 'windows' => [], 'live' => null, 'usable_rows' => 0]; + + $windows = ['24h' => 86400, '7d' => 604800, '30d' => 2592000]; + foreach ($windows as $k => $_) $out['windows'][$k] = ['bytes' => null, 'runs' => 0, 'failed' => 0]; + + if (is_file($file)) { + $now = time(); + $epoch = strtotime(VV_BW_EPOCH . ' 00:00:00'); + $fh = fopen($file, 'r'); + if ($fh) { + while (($line = fgets($fh)) !== false) { + $p = explode('|', trim($line)); + if (count($p) < 6) continue; + $ts = strtotime($p[0] . ' ' . $p[1]); + if (!$ts || $ts < $epoch) continue; + $out['usable_rows']++; + $bytes = (int)$p[5]; + $failed = ($p[4] ?? '') !== 'success'; + foreach ($windows as $k => $span) { + if ($ts < $now - $span) continue; + $w = &$out['windows'][$k]; + $w['runs']++; + if ($failed) $w['failed']++; + if ($bytes > 0) $w['bytes'] = (int)$w['bytes'] + $bytes; + unset($w); + } + } + fclose($fh); + } + } + + // Live: an rsync this host is running right now. The lock name carries the share, which is + // the only part of "what is moving" worth showing without parsing progress output. + $running = trim((string)shell_exec("pgrep -c -f '^rsync -av' 2>/dev/null")) ; + if ((int)$running > 0) { + $share = ''; + foreach (glob('/tmp/unraid_locks/rsync_*.lock') ?: [] as $l) { + $pid = (int)preg_replace('/\D/', '', (string)@file_get_contents($l)); + if ($pid && is_dir("/proc/$pid")) { + $share = preg_replace('/^rsync_|\.lock$/', '', basename($l)); + break; + } + } + $out['live'] = ['running' => (int)$running, 'share' => $share]; + } + return $out; +} + // ── Media seed progress ─────────────────────────────────────────────────────── // // Phase 3 dispatches Rsync/media_seed.sh detached, because a first seed of a full @@ -501,6 +567,7 @@ function vv_partnership_all(): array { 'config' => vv_pt_config(), 'nodes' => vv_pt_nodes(), 'sync' => vv_pt_sync(), + 'xfer' => vv_pt_transfer_stats(), 'ts' => time(), ]; } diff --git a/Plugin/unraid/pages/partnership.php b/Plugin/unraid/pages/partnership.php index b925036..71f9aa2 100644 --- a/Plugin/unraid/pages/partnership.php +++ b/Plugin/unraid/pages/partnership.php @@ -105,16 +105,6 @@ textarea.vv-pt-set-input { resize:vertical; white-space:pre; }