From 28dbb803dedd8824f74a57b3430981f12d34f1e2 Mon Sep 17 00:00:00 2001 From: frytimo Date: Mon, 30 Jun 2025 18:54:13 -0300 Subject: [PATCH] Add cpu line graph (#7403) * add cpu line graph * add cpu line graph * add cpu line graph * add cpu line graph * add cpu line graph * Add FreeBSD CPU status * Add new dashboard type Line * Add missing public declaration * Allow new dashboard type Line to be selected option * Use new Line type for graphing instead of doughnut --- .../classes/bsd_system_information.php | 81 ++++++++++ .../classes/linux_system_information.php | 67 +++++++- .../classes/system_dashboard_service.php | 28 +++- .../resources/classes/system_information.php | 2 + .../resources/dashboard/system_cpu_status.php | 149 +++++++++++++++++- .../resources/dashboard/system_status.php | 2 +- core/dashboard/app_languages.php | 27 ++++ core/dashboard/dashboard_edit.php | 5 + 8 files changed, 346 insertions(+), 15 deletions(-) diff --git a/app/system/resources/classes/bsd_system_information.php b/app/system/resources/classes/bsd_system_information.php index 21e8077a83..5fc91fff39 100644 --- a/app/system/resources/classes/bsd_system_information.php +++ b/app/system/resources/classes/bsd_system_information.php @@ -52,4 +52,85 @@ class bsd_system_information extends system_information { public function get_uptime() { return shell_exec('uptime'); } + + public function get_cpu_percent_per_core(): array { + static $last = []; + $results = []; + + // Read the raw CPU time ticks from sysctl (returns flat array of cores) + $raw = trim(shell_exec('sysctl -n kern.cp_times')); + if (!$raw) + return []; + + $parts = array_map('intval', preg_split('/\s+/', $raw)); + $num_cores = count($parts) / 5; + + for ($core = 0; $core < $num_cores; $core++) { + $offset = $core * 5; + $user = $parts[$offset]; + $nice = $parts[$offset + 1]; + $sys = $parts[$offset + 2]; + $intr = $parts[$offset + 3]; + $idle = $parts[$offset + 4]; + + $total = $user + $nice + $sys + $intr + $idle; + + if (!isset($last[$core])) { + $last[$core] = ['total' => $total, 'idle' => $idle]; + $results[$core] = 0; + continue; + } + + $delta_total = $total - $last[$core]['total']; + $delta_idle = $idle - $last[$core]['idle']; + + $usage = $delta_total > 0 ? (1 - ($delta_idle / $delta_total)) * 100 : 0; + $results[$core] = round($usage, 2); + + $last[$core] = ['total' => $total, 'idle' => $idle]; + } + + return $results; + } + + /** + * + * @staticvar array $last + * @param string $interface + * @return array + * @depends FreeBSD Version 12 + */ + public function get_network_speed(string $interface = 'em0'): array { + static $last = []; + + // Run netstat for the interface + $output = shell_exec("netstat -bI {$interface} 2>/dev/null"); + if (!$output) + return ['rx_bps' => 0, 'tx_bps' => 0]; + + $lines = explode("\n", trim($output)); + if (count($lines) < 2) + return ['rx_bps' => 0, 'tx_bps' => 0]; + + $cols = preg_split('/\s+/', $lines[1]); + $rx_bytes = (int) $cols[6]; // Ibytes + $tx_bytes = (int) $cols[9]; // Obytes + $now = microtime(true); + + if (!isset($last[$interface])) { + $last[$interface] = ['rx' => $rx_bytes, 'tx' => $tx_bytes, 'time' => $now]; + return ['rx_bps' => 0, 'tx_bps' => 0]; + } + + $delta_time = $now - $last[$interface]['time']; + $delta_rx = $rx_bytes - $last[$interface]['rx']; + $delta_tx = $tx_bytes - $last[$interface]['tx']; + + $last[$interface] = ['rx' => $rx_bytes, 'tx' => $tx_bytes, 'time' => $now]; + + return [ + 'rx_bps' => $delta_rx / $delta_time, + 'tx_bps' => $delta_tx / $delta_time + ]; + } } diff --git a/app/system/resources/classes/linux_system_information.php b/app/system/resources/classes/linux_system_information.php index e0a75b803c..bff71c9b9d 100644 --- a/app/system/resources/classes/linux_system_information.php +++ b/app/system/resources/classes/linux_system_information.php @@ -50,7 +50,8 @@ class linux_system_information extends system_information { $core_count = 0; foreach ($lines1 as $i => $line1) { - if (strpos($line1, 'cpu') !== 0 || $line1 === 'cpu') continue; + if (strpos($line1, 'cpu') !== 0 || $line1 === 'cpu') + continue; $parts1 = preg_split('/\s+/', $line1); $parts2 = preg_split('/\s+/', $lines2[$i]); @@ -75,4 +76,68 @@ class linux_system_information extends system_information { public function get_uptime() { return shell_exec('uptime'); } + + public function get_cpu_percent_per_core(): array { + static $last = []; + + $lines = file('/proc/stat'); + $results = []; + + foreach ($lines as $line) { + if (preg_match('/^cpu(\d+)\s+(.+)$/', $line, $matches)) { + $core = (int) $matches[1]; + $parts = preg_split('/\s+/', trim($matches[2])); + $total = array_sum($parts); + $idle = $parts[3] ?? 0; + + if (!isset($last[$core])) { + $last[$core] = ['total' => $total, 'idle' => $idle]; + $results[$core] = 0; + } else { + $delta_total = $total - $last[$core]['total']; + $delta_idle = $idle - $last[$core]['idle']; + $usage = $delta_total > 0 ? (1 - ($delta_idle / $delta_total)) * 100 : 0; + + $results[$core] = round($usage, 2); + $last[$core] = ['total' => $total, 'idle' => $idle]; + } + } + } + + return $results; + } + + public function get_network_speed(string $interface = 'eth0'): array { + static $last = []; + + // Read network stats + $data = file('/proc/net/dev'); + foreach ($data as $line) { + if (strpos($line, $interface . ':') !== false) { + $parts = preg_split('/\s+/', trim(str_replace(':', ' ', $line))); + $rx_bytes = (int) $parts[1]; + $tx_bytes = (int) $parts[9]; + + $now = microtime(true); + + if (!isset($last[$interface])) { + $last[$interface] = ['rx' => $rx_bytes, 'tx' => $tx_bytes, 'time' => $now]; + return ['rx_bps' => 0, 'tx_bps' => 0]; + } + + $delta_time = $now - $last[$interface]['time']; + $delta_rx = $rx_bytes - $last[$interface]['rx']; + $delta_tx = $tx_bytes - $last[$interface]['tx']; + + $last[$interface] = ['rx' => $rx_bytes, 'tx' => $tx_bytes, 'time' => $now]; + + return [ + 'rx_bps' => $delta_rx / $delta_time, + 'tx_bps' => $delta_tx / $delta_time + ]; + } + } + + return ['rx_bps' => 0, 'tx_bps' => 0]; + } } diff --git a/app/system/resources/classes/system_dashboard_service.php b/app/system/resources/classes/system_dashboard_service.php index 3bc5e3b050..4290f9c59f 100644 --- a/app/system/resources/classes/system_dashboard_service.php +++ b/app/system/resources/classes/system_dashboard_service.php @@ -87,28 +87,40 @@ class system_dashboard_service extends base_websocket_system_service { } public function on_cpu_status($message = null): void { - // Get the CPU status - $cpu_percent = self::$system_information->get_cpu_percent(); + // Get total and per-core CPU usage + $cpu_percent_total = self::$system_information->get_cpu_percent(); + $cpu_percent_per_core = self::$system_information->get_cpu_percent_per_core(); - // Send the response + // Prepare response $response = new websocket_message(); $response - ->payload([self::CPU_STATUS_TOPIC => $cpu_percent]) + ->payload([ + self::CPU_STATUS_TOPIC => [ + 'total' => $cpu_percent_total, + 'per_core' => array_values($cpu_percent_per_core) + ] + ]) ->service_name(self::get_service_name()) ->topic(self::CPU_STATUS_TOPIC); - // Check if we are responding + // Include message ID if responding to a request if ($message !== null && $message instanceof websocket_message) { $response->id($message->id()); } - // Log the broadcast - $this->debug("Broadcasting CPU percent '$cpu_percent'"); + // Log for debugging + $this->debug(sprintf( + "Broadcasting CPU total %.2f%% with %d cores", + $cpu_percent_total, + count($cpu_percent_per_core) + )); - // Send to subscribers + // Send the broadcast $this->respond($response); } + + public static function get_service_name(): string { return "dashboard.system.information"; } diff --git a/app/system/resources/classes/system_information.php b/app/system/resources/classes/system_information.php index 5eb70a60a5..58a3850240 100644 --- a/app/system/resources/classes/system_information.php +++ b/app/system/resources/classes/system_information.php @@ -34,6 +34,8 @@ abstract class system_information { abstract public function get_cpu_cores(): int; abstract public function get_uptime(); abstract public function get_cpu_percent(): float; + abstract public function get_cpu_percent_per_core(): array; + abstract public function get_network_speed(string $interface = 'eth0'): array; public function get_load_average() { return sys_getloadavg(); diff --git a/app/system/resources/dashboard/system_cpu_status.php b/app/system/resources/dashboard/system_cpu_status.php index 41919d9f7a..549e231ee7 100644 --- a/app/system/resources/dashboard/system_cpu_status.php +++ b/app/system/resources/dashboard/system_cpu_status.php @@ -60,8 +60,7 @@ subscriber::save_token($token, [system_dashboard_service::get_service_name()]); - //add half doughnut chart - if (!isset($dashboard_chart_type) || $dashboard_chart_type == "doughnut"): ?> + if ($dashboard_chart_type === 'line') { ?>
+ + +
+ + - - ".round($percent_cpu)."%"; } diff --git a/app/system/resources/dashboard/system_status.php b/app/system/resources/dashboard/system_status.php index 75d7a38293..6731232b88 100644 --- a/app/system/resources/dashboard/system_status.php +++ b/app/system/resources/dashboard/system_status.php @@ -110,7 +110,7 @@ if (!progress) return; // Update progress bar - cpu_percent = Math.round(payload.cpu_status); + cpu_percent = Math.round(payload.cpu_status.total); progress.style.width = `${cpu_percent}%`; progress.innerHTML = `${cpu_percent}%`; } diff --git a/core/dashboard/app_languages.php b/core/dashboard/app_languages.php index 02d88537c3..00d6cbb348 100644 --- a/core/dashboard/app_languages.php +++ b/core/dashboard/app_languages.php @@ -648,6 +648,33 @@ $text['label-doughnut']['zh-cn'] = "油炸圈饼"; $text['label-doughnut']['ja-jp'] = "ドーナツ"; $text['label-doughnut']['ko-kr'] = "도넛"; +$text['label-line']['en-us'] = "Line"; +$text['label-line']['en-gb'] = "Line"; +$text['label-line']['ar-eg'] = "خط"; +$text['label-line']['de-at'] = "Leitung"; +$text['label-line']['de-ch'] = "Leitung"; +$text['label-line']['de-de'] = "Leitung"; +$text['label-line']['el-gr'] = "Γραμμή"; +$text['label-line']['es-cl'] = "Línea"; +$text['label-line']['es-mx'] = "Línea"; +$text['label-line']['fr-ca'] = "Ligne"; +$text['label-line']['fr-fr'] = "Ligne"; +$text['label-line']['he-il'] = "קו"; +$text['label-line']['it-it'] = "Linea"; +$text['label-line']['ka-ge'] = "ხაზი"; +$text['label-line']['nl-nl'] = "Lijn"; +$text['label-line']['pl-pl'] = "Linia"; +$text['label-line']['pt-br'] = "Linha"; +$text['label-line']['pt-pt'] = "Linha"; +$text['label-line']['ro-ro'] = "Linie"; +$text['label-line']['ru-ru'] = "Линия"; +$text['label-line']['sv-se'] = "Linje"; +$text['label-line']['uk-ua'] = "Лінія"; +$text['label-line']['tr-tr'] = "Hat"; +$text['label-line']['zh-cn'] = "线路"; +$text['label-line']['ja-jp'] = "回線"; +$text['label-line']['ko-kr'] = "회선"; + $text['label-progress_bar']['en-us'] = "Progress Bar"; $text['label-progress_bar']['en-gb'] = "Progress Bar"; $text['label-progress_bar']['ar-eg'] = "شريط التقدم"; diff --git a/core/dashboard/dashboard_edit.php b/core/dashboard/dashboard_edit.php index bfe58508e5..822196e9c2 100644 --- a/core/dashboard/dashboard_edit.php +++ b/core/dashboard/dashboard_edit.php @@ -828,6 +828,11 @@ echo "\n"; echo "