diff --git a/emhttp/plugins/dynamix.vm.manager/VMSettings.page b/emhttp/plugins/dynamix.vm.manager/VMSettings.page index e0be715cc7..69edaf555f 100644 --- a/emhttp/plugins/dynamix.vm.manager/VMSettings.page +++ b/emhttp/plugins/dynamix.vm.manager/VMSettings.page @@ -571,10 +571,6 @@ $(function(){ $("#applyBtn").val(checked ? "_(Delete)_" : "_(Apply)_").removeAttr('disabled'); }); } - $.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:'reboot'}, function(data){ - var rebootMessage = "_(VM Settings: A reboot is required to apply changes)_"; - if (data.modified) addRebootNotice(rebootMessage); else removeRebootNotice(rebootMessage); - }); if ($.cookie('btrfs-scrub-vm')) btrfsScrub($.cookie('btrfs-scrub-vm')); }); diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php index aaf7a951dd..41cfeab5b7 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php @@ -1622,7 +1622,7 @@ function domain_get_memory_stats($domain) { function domain_get_all_domain_stats() { $tmp = libvirt_connect_get_all_domain_stats($this->conn); - return $tmp ?: $this->_set_last_error(); + return $tmp === false ? $this->_set_last_error() : $tmp; } function domain_start($dom) { diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 32b9df6ba8..466170d707 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -2742,63 +2742,124 @@ function get_vm_usage_stats($collectcpustats = true,$collectdiskstats = true,$co global $lv, $vmusagestats; $hostcpus = $lv->host_get_node_info(); - $timestamp = time(); + $timestamp = microtime(true); $allstats=$lv->domain_get_all_domain_stats(); + if ($allstats === false || !is_array($allstats)) return; foreach ($allstats as $vm => $data) { $rd=$wr=$tx=$rx=null; - $state = $data["state.state"]; + $state = (int)($data["state.state"] ?? 0); + $hasPrev = isset($vmusagestats[$vm]); + $prevTimestamp = isset($vmusagestats[$vm]["timestamp"]) ? (float)$vmusagestats[$vm]["timestamp"] : $timestamp; + $sampleSeconds = max(0.25, ($timestamp - $prevTimestamp)); # CPU Metrics $cpuTime = 0; + $guestCpuTime = 0; $cpuHostPercent = 0; $cpuGuestPercent = 0; - $cpuTimeAbs = $data["cpu.time"]; + $cpuTimeAbs = (int)($data["cpu.time"] ?? 0); + $guestCpuTimeAbs = $cpuTimeAbs; + $guestCpuSource = 'none'; if ($state == 1 && $collectcpustats == true) { - $guestcpus = $data["vcpu.current"]; - $cpuTime = $cpuTimeAbs - $vmusagestats[$vm]["cpuTimeAbs"]; - $pcentbase = ((($cpuTime) * 100.0) / ((($timestamp) - $vmusagestats[$vm]["timestamp"] ) * 1000.0 * 1000.0 * 1000.0)); - $cpuHostPercent = round($pcentbase / $hostcpus['cpus'],1); - $cpuGuestPercent = round($pcentbase / $guestcpus, 1); + $configuredGuestCpus = (int)($data["vcpu.current"] ?? ($data["vcpu.maximum"] ?? 0)); + $guestcpus = max(1, $configuredGuestCpus); + $prevCpuAbs = isset($vmusagestats[$vm]["cpuTimeAbs"]) ? (int)$vmusagestats[$vm]["cpuTimeAbs"] : $cpuTimeAbs; + $prevGuestCpuAbs = isset($vmusagestats[$vm]["guestCpuTimeAbs"]) ? (int)$vmusagestats[$vm]["guestCpuTimeAbs"] : 0; + $prevGuestCpuSource = $vmusagestats[$vm]["guestCpuSource"] ?? 'none'; + + $guestCpuTimeAbs = 0; + $foundVcpuTime = 0; + for ($i = 0; $i < max($guestcpus, 64); $i++) { + $key = "vcpu.$i.time"; + if (isset($data[$key])) { + $guestCpuTimeAbs += (int)$data[$key]; + $foundVcpuTime++; + } elseif ($i >= $guestcpus) { + break; + } + } + if ($foundVcpuTime > 0) { + $guestcpus = $foundVcpuTime; + $guestCpuSource = 'vcpu'; + } else { + $guestCpuTimeAbs = 0; + } + + if ($hasPrev) { + $cpuTime = max(0, $cpuTimeAbs - $prevCpuAbs); + $pcenthostbase = (($cpuTime * 100.0) / ($sampleSeconds * 1000.0 * 1000.0 * 1000.0)); + $cpuHostPercent = round($pcenthostbase / max(1, (int)($hostcpus['cpus'] ?? 1)),1); + if ($guestCpuSource == 'vcpu' && $prevGuestCpuSource == 'vcpu') { + $guestCpuTime = max(0, $guestCpuTimeAbs - $prevGuestCpuAbs); + $pcentguestbase = (($guestCpuTime * 100.0) / ($sampleSeconds * 1000.0 * 1000.0 * 1000.0)); + $cpuGuestPercent = round($pcentguestbase / $guestcpus, 1); + } + } $cpuHostPercent = max(0.0, min(100.0, $cpuHostPercent)); $cpuGuestPercent = max(0.0, min(100.0, $cpuGuestPercent)); } # Memory Metrics if ($state == 1 && $collectmemstats) { - $currentmem = $data["balloon.current"]; - $maximummem = $data["balloon.maximum"]; - $meminuse = min($data["balloon.rss"],$data["balloon.current"]); + $currentmem = (int)($data["balloon.current"] ?? $data["balloon.maximum"] ?? 0); + $maximummem = (int)($data["balloon.maximum"] ?? 0); + $rss = (int)($data["balloon.rss"] ?? 0); + $meminuse = $currentmem > 0 ? min($rss, $currentmem) : $rss; } else $maximummem = $currentmem = $meminuse = 0; # Disk if ($state == 1 && $collectdiskstats) { - $disknum = $data["block.count"]; + $disknum = (int)($data["block.count"] ?? 0); $rd=$wr=$i=0; for ($i = 0; $i < $disknum; $i++) { - if ($data["block.$i.name"] == "hda" || $data["block.$i.name"] == "hdb") continue; - $rd += $data["block.$i.rd.bytes"]; - $wr += $data["block.$i.wr.bytes"]; + if (($data["block.$i.name"] ?? '') == "hda" || ($data["block.$i.name"] ?? '') == "hdb") continue; + $rd += (int)($data["block.$i.rd.bytes"] ?? 0); + $wr += (int)($data["block.$i.wr.bytes"] ?? 0); } - $rdrate = ($rd - $vmusagestats[$vm]['rdp']); - $wrrate = ($wr - $vmusagestats[$vm]['wrp']); + $prevRd = isset($vmusagestats[$vm]['rdp']) ? (int)$vmusagestats[$vm]['rdp'] : $rd; + $prevWr = isset($vmusagestats[$vm]['wrp']) ? (int)$vmusagestats[$vm]['wrp'] : $wr; + $rdrate = $hasPrev ? (int)round(max(0, ($rd - $prevRd)) / $sampleSeconds, 0) : 0; + $wrrate = $hasPrev ? (int)round(max(0, ($wr - $prevWr)) / $sampleSeconds, 0) : 0; } else $rdrate=$wrrate=0; # Net if ($state == 1 && $collectnicstats) { - $nicnum = $data["net.count"]; - $rx=$tx=$i=0; + $nicnum = (int)($data["net.count"] ?? 0); + $rx=$tx=$rxPkts=$rxDrop=$i=0; for ($i = 0; $i < $nicnum; $i++) { - $rx += $data["net.$i.rx.bytes"]; - $tx += $data["net.$i.tx.bytes"]; + $rx += (int)($data["net.$i.rx.bytes"] ?? 0); + $tx += (int)($data["net.$i.tx.bytes"] ?? 0); + $rxPkts += (int)($data["net.$i.rx.pkts"] ?? 0); + $rxDrop += (int)($data["net.$i.rx.drop"] ?? 0); + } + $prevRx = isset($vmusagestats[$vm]['rxp']) ? (int)$vmusagestats[$vm]['rxp'] : $rx; + $prevTx = isset($vmusagestats[$vm]['txp']) ? (int)$vmusagestats[$vm]['txp'] : $tx; + $prevRxPkts = isset($vmusagestats[$vm]['rxpktsp']) ? (int)$vmusagestats[$vm]['rxpktsp'] : $rxPkts; + $prevRxDrop = isset($vmusagestats[$vm]['rxdropp']) ? (int)$vmusagestats[$vm]['rxdropp'] : $rxDrop; + if ($hasPrev) { + $rxBytesDelta = max(0, ($rx - $prevRx)); + $rxrate = (int)round($rxBytesDelta / $sampleSeconds, 0); + $rxPktsDelta = max(0, ($rxPkts - $prevRxPkts)); + $rxDropDelta = max(0, ($rxDrop - $prevRxDrop)); + $acceptedPkts = max(0, ($rxPktsDelta - $rxDropDelta)); + // Estimate accepted RX bytes by scaling bytes with accepted-packet ratio. + $acceptedRatio = $rxPktsDelta > 0 ? min(1, ($acceptedPkts / $rxPktsDelta)) : 1; + $rxrateAdj = (int)round(($rxBytesDelta * $acceptedRatio) / $sampleSeconds, 0); + } else { + $rxrate = 0; + $rxrateAdj = 0; } - $rxrate = round(($rx - $vmusagestats[$vm]['rxp']),0); - $txrate = round(($tx - $vmusagestats[$vm]['txp']),0); + $txrate = $hasPrev ? (int)round(max(0, ($tx - $prevTx)) / $sampleSeconds, 0) : 0; } else { - $rxrate=$txrate=0; + $rxPkts=$rxDrop=0; + $rxrate=$rxrateAdj=$txrate=0; } $vmusagestats[$vm] = [ "cpuTime" => $cpuTime, + "guestCpuTime" => $guestCpuTime, "cpuTimeAbs" => $cpuTimeAbs, + "guestCpuTimeAbs" => $guestCpuTimeAbs, + "guestCpuSource" => $guestCpuSource, "cpuhost" => $cpuHostPercent, "cpuguest" => $cpuGuestPercent, "timestamp" => $timestamp, @@ -2806,6 +2867,9 @@ function get_vm_usage_stats($collectcpustats = true,$collectdiskstats = true,$co "curmem" => $currentmem, "maxmem" => $maximummem, "rxrate" => $rxrate, + "rxrateadj" => $rxrateAdj, + "rxpktsp" => $rxPkts, + "rxdropp" => $rxDrop, "rxp" => $rx, "txrate" => $txrate, "txp" => $tx, diff --git a/emhttp/plugins/dynamix.vm.manager/nchan/vm_usage b/emhttp/plugins/dynamix.vm.manager/nchan/vm_usage index c4bb8f5a74..8a8fdfad85 100755 --- a/emhttp/plugins/dynamix.vm.manager/nchan/vm_usage +++ b/emhttp/plugins/dynamix.vm.manager/nchan/vm_usage @@ -80,8 +80,8 @@ while (true) { $echodata .= my_scale($vmdata['mem']*1024,$unit,null,null,1024)."$unit / ".my_scale($vmdata['curmem']*1024,$unit,null,null,1024)."$unit"; if ($vmdata['curmem'] === $vmdata['maxmem']) $echodata .= " "; else $echodata .= " / " .my_scale($vmdata['maxmem']*1024,$unit,null,null,1024)."$unit "; - $echodata .= _("Read").": ".my_scale($vmdata['rdrate']/$timer,$unit,null,null,1024)."$unit/s
"._("Write").": ".my_scale($vmdata['wrrate']/$timer,$unit,null,null,1024)."$unit/s"; - $echodata .= _("RX").": ".my_scale($vmdata['rxrate']/$timer,$unit,null,null,1024)."$unit/s
"._("TX").": ".my_scale($vmdata['txrate']/$timer,$unit,null,null,1024)."$unit/s"; + $echodata .= _("Read").": ".my_scale($vmdata['rdrate'],$unit,null,null,1024)."$unit/s
"._("Write").": ".my_scale($vmdata['wrrate'],$unit,null,null,1024)."$unit/s"; + $echodata .= _("RX").": ".my_scale($vmdata['rxrate'],$unit,null,null,1024)."$unit/s
"._("TX").": ".my_scale($vmdata['txrate'],$unit,null,null,1024)."$unit/s"; } $echo = $echodata ; } diff --git a/emhttp/plugins/dynamix/include/Helpers.php b/emhttp/plugins/dynamix/include/Helpers.php index dfdd86e7e2..9208e17ec8 100644 --- a/emhttp/plugins/dynamix/include/Helpers.php +++ b/emhttp/plugins/dynamix/include/Helpers.php @@ -1052,6 +1052,99 @@ function getNumaInfo() { return $result; } + +/** + * Parse numeric GT/s value from sysfs/lspci-style speed text. + */ +function parsePciSpeedValue($value) +{ + if (!is_string($value) || $value === '') return null; + if (preg_match('/([0-9]+(?:\.[0-9]+)?)/', $value, $m)) { + return floatval($m[1]); + } + return null; +} + +/** + * Parse numeric lane width from strings like "x16". + */ +function parsePciWidthValue($value) +{ + if (!is_string($value) || $value === '') return null; + return intval(str_replace('x', '', trim($value))); +} + +/** + * Read PCIe link values from a sysfs PCI device directory. + */ +function readPciLinkValuesFromPath($path) +{ + $values = [ + 'current_speed' => null, + 'max_speed' => null, + 'current_width' => null, + 'max_width' => null, + ]; + + $speedMap = [ + 'current_speed' => "$path/current_link_speed", + 'max_speed' => "$path/max_link_speed", + ]; + foreach ($speedMap as $key => $file) { + if (!file_exists($file)) continue; + $parsed = parsePciSpeedValue(trim(file_get_contents($file))); + if ($parsed !== null) $values[$key] = $parsed; + } + + $widthMap = [ + 'current_width' => "$path/current_link_width", + 'max_width' => "$path/max_link_width", + ]; + foreach ($widthMap as $key => $file) { + if (!file_exists($file)) continue; + $values[$key] = parsePciWidthValue(trim(file_get_contents($file))); + } + + return $values; +} + +/** + * Walk PCI ancestors and return the best pcieport link values. + */ +function getBestPciePortAncestorLink($devicePath) +{ + $best = null; + $bestScore = -1.0; + $current = realpath($devicePath) ?: $devicePath; + $iterations = 0; + $maxIterations = 20; + + while ($iterations < $maxIterations) { + $parent = dirname($current); + if ($parent === '/sys/devices' || $parent === '/sys' || $parent === $current) { + break; + } + + $driverPath = "$parent/driver"; + if (is_link($driverPath)) { + $driver = basename((string)readlink($driverPath)); + if ($driver === 'pcieport') { + $vals = readPciLinkValuesFromPath($parent); + $score = (($vals['current_speed'] ?? 0.0) * 100.0) + (($vals['current_width'] ?? 0.0)); + if ($score > $bestScore) { + $bestScore = $score; + $best = $vals; + } + } + } + + $current = $parent; + $iterations++; + } + + return $best; +} + /** * Get PCIe link data from sysfs with generation + clean GT/s rate. * Suppresses sentinel max‑width value 255 (unreported/invalid); preserves 0 when reported. @@ -1087,7 +1180,7 @@ function getPciLinkInfo($pciAddress) return $out; } - // Read speeds + // Read endpoint values first foreach ($files as $key => $file) { if (!file_exists($file)) continue; $value = trim(file_get_contents($file)); @@ -1107,6 +1200,35 @@ function getPciLinkInfo($pciAddress) } } + // For Intel GPUs and classic misreports (Gen1 x1), prefer better upstream pcieport data. + $isIntelGpu = false; + $vendorFile = "$base/vendor"; + $classFile = "$base/class"; + if (file_exists($vendorFile) && file_exists($classFile)) { + $vendorRaw = strtolower(trim(file_get_contents($vendorFile))); + $classRaw = strtolower(trim(file_get_contents($classFile))); + $isIntelGpu = ($vendorRaw === '0x8086' && strpos($classRaw, '0x03') === 0); + } + + $maxSpeedRaw = $out['max_speed'] ?? null; + $maxWidthRaw = $out['max_width_raw'] ?? null; + $looksMisreported = ($maxSpeedRaw !== null && $maxWidthRaw !== null && $maxSpeedRaw <= 2.5 && $maxWidthRaw <= 1); + + if ($isIntelGpu || $looksMisreported) { + $ancestor = getBestPciePortAncestorLink($base); + if (is_array($ancestor)) { + $endpointScore = (($out['current_speed'] ?? 0.0) * 100.0) + (($out['current_width_raw'] ?? 0.0)); + $ancestorScore = (($ancestor['current_speed'] ?? 0.0) * 100.0) + (($ancestor['current_width'] ?? 0.0)); + + if ($looksMisreported || $ancestorScore > $endpointScore) { + if (($ancestor['current_speed'] ?? null) !== null) $out['current_speed'] = $ancestor['current_speed']; + if (($ancestor['max_speed'] ?? null) !== null) $out['max_speed'] = $ancestor['max_speed']; + if (($ancestor['current_width'] ?? null) !== null) $out['current_width_raw'] = $ancestor['current_width']; + if (($ancestor['max_width'] ?? null) !== null) $out['max_width_raw'] = $ancestor['max_width']; + } + } + } + // Apply width rules $max = $out['max_width_raw'] ?? null; $cur = $out['current_width_raw'] ?? null; diff --git a/emhttp/plugins/dynamix/nchan/vm_dashusage b/emhttp/plugins/dynamix/nchan/vm_dashusage index 26efac0258..3a568822e2 100755 --- a/emhttp/plugins/dynamix/nchan/vm_dashusage +++ b/emhttp/plugins/dynamix/nchan/vm_dashusage @@ -104,8 +104,8 @@ while (true) { $echo[$vmencode ]['mem'] = "Mem: ".my_scale($vmdata['mem']*1024,$unit,null,null,1024)."$unit / ".my_scale($vmdata['curmem']*1024,$unit,null,null,1024)."$unit"; if ($vmdata['maxmem'] == $vmdata['curmem']) $echo[$vmencode ]['mem'] .="  "; else $echo[$vmencode ]['mem'] .= " / ".my_scale($vmdata['maxmem']*1024,$unit,null,null,1024)."$unit  "; - $echo[$vmencode ]['disk'] = "Disk: "._("Rd").": ".my_scale($vmdata['rdrate']/$timer,$unit,null,null,1024)."$unit/s "._("Wr").": ".my_scale($vmdata['wrrate']/$timer,$unit,null,null,1024)."$unit/s  "; - $echo[$vmencode ]['net'] = "Net: "._("RX").": ".my_scale($vmdata['rxrate']/$timer,$unit,null,null,1024)."$unit/s "._("TX").": ".my_scale($vmdata['txrate']/$timer,$unit,null,null,1024)."$unit/s  "; + $echo[$vmencode ]['disk'] = "Disk: "._("Rd").": ".my_scale($vmdata['rdrate'],$unit,null,null,1024)."$unit/s "._("Wr").": ".my_scale($vmdata['wrrate'],$unit,null,null,1024)."$unit/s  "; + $echo[$vmencode ]['net'] = "Net: "._("RX").": ".my_scale($vmdata['rxrate'],$unit,null,null,1024)."$unit/s "._("TX").": ".my_scale($vmdata['txrate'],$unit,null,null,1024)."$unit/s  "; } }