diff --git a/emhttp/plugins/dynamix/DashStats.page b/emhttp/plugins/dynamix/DashStats.page
index 5522666aed..60fbc8f968 100755
--- a/emhttp/plugins/dynamix/DashStats.page
+++ b/emhttp/plugins/dynamix/DashStats.page
@@ -94,7 +94,7 @@ foreach ($devs as $disk) {
}
$array_percent = number_format(100*$array_used/($array_size ?: 1),1,$dot,'');
-exec('cat /sys/devices/system/cpu/*/topology/thread_siblings_list|sort -nu', $cpus);
+$cpus=get_cpu_packages();
$wg_up = $wireguard ? exec("wg show interfaces") : '';
$wg_up = $wg_up ? explode(' ',$wg_up) : [];
$up = count($wg_up);
@@ -351,8 +351,13 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout
-
+ _(Total)_ _(Power)_: N/A
+
+
+ _(Temperature)_: N/A
+
+
_(Load)_:
0%
@@ -398,22 +403,29 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout
|
-
+
- foreach ($cpus as $pair) {
- [$cpu1, $cpu2] = my_preg_split('/[,-]/',$pair);
- echo "
";
- if ($is_intel_cpu && count($core_types) > 0)
- $core_type = "({$core_types[$cpu1]})";
- else
- $core_type = "";
-
- if ($cpu2)
- echo "| CPU $cpu1 $core_type - HT $cpu2 0% 0%
| ";
- else
- echo "CPU $cpu1 $core_type0%
| ";
- echo "
";
- }
+ foreach ($cpus as $cpu_index=>$package) {
+ if (count($cpus) > 1) {
+ echo " "._("Physical")." CPU $cpu_index "._("Power").": N/A ";
+ if (count($cpus)>1) echo " "._("Temperature").": N/A";
+ echo " | ";
+ }
+ foreach ($package as $pair) {
+ [$cpu1, $cpu2] = my_preg_split('/[,-]/',$pair);
+ echo "";
+ if ($is_intel_cpu && count($core_types) > 0)
+ $core_type = "({$core_types[$cpu1]})";
+ else
+ $core_type = "";
+
+ if ($cpu2)
+ echo "| CPU $cpu1 $core_type - HT $cpu2 0% 0%
| ";
+ else
+ echo "CPU $cpu1 $core_type0%
| ";
+ echo "
";
+ }
+ }
?>
@@ -1441,6 +1453,7 @@ var startup = true;
var stopgap = ' | ';
var recall = null;
var recover = null;
+var tempunit="=_var($display,'unit','C');?>";
// Helper function to calculate millisPerPixel based on container width
function getMillisPerPixel(timeInSeconds, containerId) {
@@ -1695,6 +1708,39 @@ function addChartNet(rx, tx) {
txTimeSeries.append(now, Math.floor(tx / 1000));
}
+function updateCPUPower() {
+ if (!cpupower) return;
+
+ // Update total power
+ const totalEl = document.getElementById('cpu-total-power');
+ const totalPower = cpupower.totalPower ?? 0;
+ if (totalEl) {
+ totalEl.innerHTML = ` _(Total)_ _(Power)_: ${totalPower.toFixed(2)} W`;
+ }
+
+ // Update each core's span
+ const cpuspower = cpupower.power ?? [];
+ cpuspower.forEach((power, index) => {
+ const coreEl = document.getElementById(`cpu-power${index}`);
+ if (coreEl) {
+ coreEl.innerHTML = `${power.toFixed(2)} W`;
+ }
+ });
+
+ const cpustemps = cpupower.temp ?? [];
+ cpustemps.forEach((temp, index) => {
+ const coreTempEl = document.getElementById(`cpu-temp${index}`);
+ if (coreTempEl) {
+ tempdisplay = temp.toFixed(0);
+ if (tempunit === "F") {
+ tempdisplay = ((temp.toFixed(0))* 9 / 5) + 32;
+ }
+ coreTempEl.innerHTML = Math.round(tempdisplay)+` °`+tempunit;;
+ }
+ });
+
+}
+
// Cache for last values to avoid unnecessary DOM updates
var lastCpuValues = {
load: -1,
@@ -2770,6 +2816,60 @@ $(function() {
setTimeout(function() {
// Charts initialized
},500);
+
+
+
+ // Start GraphQL CPU power subscription with retry logic
+ let cpuInitPWRAttempts = 0, cpuPWRRetryMs = 100;
+ function initPwrCpuSubscription() {
+
+
+ if (window.gql && window.apolloClient) {
+ // Define the subscription query when GraphQL is available
+ // corepower has the temps currently.
+ CPU_POWER_SUBSCRIPTION = window.gql(`
+ subscription SystemMetricsCpuTelemetry {
+ systemMetricsCpuTelemetry {
+ totalPower,
+ power,
+ temp,
+ }
+ }
+ `);
+ cpuPowerSubscription = window.apolloClient.subscribe({
+ query: CPU_POWER_SUBSCRIPTION
+ }).subscribe({
+ next: (result) => {
+
+
+ if (result.data?.systemMetricsCpuTelemetry){
+ cpupower = result.data.systemMetricsCpuTelemetry;
+
+ updateCPUPower();
+ }
+ },
+ error: (err) => {
+ console.error('CPU power subscription error:', err);
+ // Try to resubscribe with capped backoff
+ if (cpuPowerSubscription) { try { cpuPowerSubscription.unsubscribe(); } catch(e){} }
+ setTimeout(initPwrCpuSubscription, Math.min(cpuPWRRetryMs *= 2, 5000));
+ }
+ });
+ } else {
+ // Retry with capped backoff if GraphQL client not ready
+ cpuInitPWRAttempts++;
+ setTimeout(initPwrCpuSubscription, Math.min(cpuPWRRetryMs *= 2, 2000));
+ }
+ }
+ initPwrCpuSubscription();
+ // Cleanup GraphQL subscription on page unload
+ $(window).on('beforeunload', function() {
+ if (cpuPowerSubscription) {
+ cpuPowerSubscription.unsubscribe();
+ }
+ });
+
+
// Cleanup GraphQL subscription on page unload
$(window).on('beforeunload', function() {
diff --git a/emhttp/plugins/dynamix/include/Helpers.php b/emhttp/plugins/dynamix/include/Helpers.php
index 61a84cab9f..15009500f1 100644
--- a/emhttp/plugins/dynamix/include/Helpers.php
+++ b/emhttp/plugins/dynamix/include/Helpers.php
@@ -856,4 +856,21 @@ function display_deprecated_filesystem_warning($deprecated_disks, $type = 'array
return $html;
}
+
+function get_cpu_packages(string $separator = ','): array {
+ $packages = [];
+ foreach (glob("/sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list") as $path) {
+ $pkg_id = (int)file_get_contents(dirname($path) . "/physical_package_id");
+ $siblings = str_replace(",", $separator, trim(file_get_contents($path)));
+ if (!in_array($siblings, $packages[$pkg_id] ?? [])) {
+ $packages[$pkg_id][] = $siblings;
+ }
+ }
+ foreach ($packages as &$list) {
+ $keys = array_map(fn($s) => (int)explode($separator, $s)[0], $list);
+ array_multisort($keys, SORT_ASC, SORT_NUMERIC, $list);
+ }
+ unset($list);
+ return $packages;
+}
?>
diff --git a/emhttp/plugins/dynamix/include/cpulist.php b/emhttp/plugins/dynamix/include/cpulist.php
new file mode 100644
index 0000000000..8d1db35733
--- /dev/null
+++ b/emhttp/plugins/dynamix/include/cpulist.php
@@ -0,0 +1,22 @@
+ (int)explode($separator, $s)[0], $list);
+ array_multisort($keys, SORT_ASC, SORT_NUMERIC, $list);
+ }
+ unset($list);
+
+ return $packages;
+}
|