Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 117 additions & 17 deletions emhttp/plugins/dynamix/DashStats.page
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -351,8 +351,13 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout
<td>
<span class='flex flex-row flex-wrap items-center gap-4'>
<span class="head_info">
<span id='cpu-temp'></span>
<span id='cpu-total-power'><i class='fa fa-fw fa-plug'></i>_(Total)_ _(Power)_: N/A</span>
</span>
<?if (count($cpus)<2):?>
<span class="head_info">
<i class="fa fa-thermometer"></i> _(Temperature)_: <span id='cpu-temp0'>N/A</span>
</span>
<?endif;?>
<span class="switch">
_(Load)_:<span class="head_bar">
<span class='cpu_ load'>0%</span>
Expand Down Expand Up @@ -398,22 +403,29 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout
</span>
</td>
</tr>

<tr>
<?
foreach ($cpus as $pair) {
[$cpu1, $cpu2] = my_preg_split('/[,-]/',$pair);
echo "<tr class='cpu_open'>";
if ($is_intel_cpu && count($core_types) > 0)
$core_type = "({$core_types[$cpu1]})";
else
$core_type = "";

if ($cpu2)
echo "<td><span class='w26'>CPU $cpu1 $core_type - HT $cpu2 </span><span class='dashboard w36'><span class='cpu$cpu1 load resize'>0%</span><div class='usage-disk sys'><span id='cpu$cpu1'></span><span></span></div></span><span class='dashboard w36'><span class='cpu$cpu2 load resize'>0%</span><div class='usage-disk sys'><span id='cpu$cpu2'></span><span></span></div></span></td>";
else
echo "<td><span class='w26'>CPU $cpu1 $core_type</span><span class='w72'><span class='cpu$cpu1 load resize'>0%</span><div class='usage-disk sys'><span id='cpu$cpu1'></span><span></span></div></span></td>";
echo "</tr>";
}
foreach ($cpus as $cpu_index=>$package) {
if (count($cpus) > 1) {
echo "<td><span class='cpu_open w72'><i class='fa fa-plug'></i> "._("Physical")." CPU $cpu_index "._("Power").": <span id='cpu-power$cpu_index'>N/A </span> ";
if (count($cpus)>1) echo "<i class='fa fa-thermometer'></i> "._("Temperature").": <span id='cpu-temp$cpu_index'>N/A</span>";
echo "</td></span></tr>";
}
foreach ($package as $pair) {
[$cpu1, $cpu2] = my_preg_split('/[,-]/',$pair);
echo "<tr class='cpu_open'>";
if ($is_intel_cpu && count($core_types) > 0)
$core_type = "({$core_types[$cpu1]})";
else
$core_type = "";

if ($cpu2)
echo "<td><span class='w26'>CPU $cpu1 $core_type - HT $cpu2 </span><span class='dashboard w36'><span class='cpu$cpu1 load resize'>0%</span><div class='usage-disk sys'><span id='cpu$cpu1'></span><span></span></div></span><span class='dashboard w36'><span class='cpu$cpu2 load resize'>0%</span><div class='usage-disk sys'><span id='cpu$cpu2'></span><span></span></div></span></td>";
else
echo "<td><span class='w26'>CPU $cpu1 $core_type</span><span class='w72'><span class='cpu$cpu1 load resize'>0%</span><div class='usage-disk sys'><span id='cpu$cpu1'></span><span></span></div></span></td>";
echo "</tr>";
}
}
?>
<tr id='cpu_chart'>
<td>
Expand Down Expand Up @@ -1441,6 +1453,7 @@ var startup = true;
var stopgap = '<thead class="stopgap"><tr><td class="stopgap"></td></tr></thead>';
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) {
Expand Down Expand Up @@ -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 = `<i class="fa fa-fw fa-plug"></i> _(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)+`&#8201;&#176;`+tempunit;;
}
});

}
Comment on lines +1711 to +1742
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix translation markers, variable declaration, and temperature conversion logic.

Several issues in updateCPUPower():

  1. Line 1718: The translation markers _(Total)_ and _(Power)_ inside the JavaScript template literal will not be processed by PHP—they'll appear as literal text. You need to pre-translate these server-side or use a different approach for localization.
  2. Line 1734: tempdisplay is assigned without let, const, or var, creating an unintended global variable.
  3. Line 1736: temp.toFixed(0) returns a string; while JavaScript coerces it to a number in arithmetic, it's cleaner to convert the raw number: temp * 9 / 5 + 32, then round once.
  4. Line 1738: Double semicolon at end (tempunit;;) and inconsistent indentation.

Apply this diff:

   const totalEl = document.getElementById('cpu-total-power');
   const totalPower = cpupower.totalPower ?? 0;
   if (totalEl) {
-    totalEl.innerHTML = `<i class="fa fa-fw fa-plug"></i> _(Total)_ _(Power)_: ${totalPower.toFixed(2)} W`;
+    totalEl.innerHTML = `<i class="fa fa-fw fa-plug"></i> <?=_('Total')?> <?=_('Power')?>: ${totalPower.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)+`&#8201;&#176;`+tempunit;;
+    if (coreTempEl) {
+      let tempdisplay = temp;
+      if (tempunit === "F") {
+        tempdisplay = (temp * 9 / 5) + 32;
+      }
+      coreTempEl.innerHTML = Math.round(tempdisplay) + `&#8201;&#176;` + tempunit;
     }
   });


// Cache for last values to avoid unnecessary DOM updates
var lastCpuValues = {
load: -1,
Expand Down Expand Up @@ -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() {
Expand Down
17 changes: 17 additions & 0 deletions emhttp/plugins/dynamix/include/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
?>
22 changes: 22 additions & 0 deletions emhttp/plugins/dynamix/include/cpulist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
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;
}
}

// Sort groups within each package by first CPU number
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;
}
Loading