Skip to content
Open
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
4 changes: 0 additions & 4 deletions emhttp/plugins/dynamix.vm.manager/VMSettings.page
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
});
</script>
2 changes: 1 addition & 1 deletion emhttp/plugins/dynamix.vm.manager/include/libvirt.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
112 changes: 88 additions & 24 deletions emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -2742,70 +2742,134 @@ 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,
"mem" => $meminuse,
"curmem" => $currentmem,
"maxmem" => $maximummem,
"rxrate" => $rxrate,
"rxrateadj" => $rxrateAdj,
"rxpktsp" => $rxPkts,
"rxdropp" => $rxDrop,
"rxp" => $rx,
"txrate" => $txrate,
"txp" => $tx,
Expand Down
4 changes: 2 additions & 2 deletions emhttp/plugins/dynamix.vm.manager/nchan/vm_usage
Original file line number Diff line number Diff line change
Expand Up @@ -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 .= " </td><td>";
else $echodata .= " / " .my_scale($vmdata['maxmem']*1024,$unit,null,null,1024)."$unit </td><td>";
$echodata .= _("Read").": ".my_scale($vmdata['rdrate']/$timer,$unit,null,null,1024)."$unit/s<br>"._("Write").": ".my_scale($vmdata['wrrate']/$timer,$unit,null,null,1024)."$unit/s</td><td>";
$echodata .= _("RX").": ".my_scale($vmdata['rxrate']/$timer,$unit,null,null,1024)."$unit/s<br>"._("TX").": ".my_scale($vmdata['txrate']/$timer,$unit,null,null,1024)."$unit/s</td></tr>";
$echodata .= _("Read").": ".my_scale($vmdata['rdrate'],$unit,null,null,1024)."$unit/s<br>"._("Write").": ".my_scale($vmdata['wrrate'],$unit,null,null,1024)."$unit/s</td><td>";
$echodata .= _("RX").": ".my_scale($vmdata['rxrate'],$unit,null,null,1024)."$unit/s<br>"._("TX").": ".my_scale($vmdata['txrate'],$unit,null,null,1024)."$unit/s</td></tr>";
}
$echo = $echodata ;
}
Expand Down
124 changes: 123 additions & 1 deletion emhttp/plugins/dynamix/include/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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));
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions emhttp/plugins/dynamix/nchan/vm_dashusage
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ while (true) {
$echo[$vmencode ]['mem'] = "<span>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'] .="&nbsp&nbsp</span>";
else $echo[$vmencode ]['mem'] .= " / ".my_scale($vmdata['maxmem']*1024,$unit,null,null,1024)."$unit&nbsp&nbsp</span>";
$echo[$vmencode ]['disk'] = "<span>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&nbsp&nbsp</span>";
$echo[$vmencode ]['net'] = "<span>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&nbsp&nbsp</span>";
$echo[$vmencode ]['disk'] = "<span>Disk: "._("Rd").": ".my_scale($vmdata['rdrate'],$unit,null,null,1024)."$unit/s "._("Wr").": ".my_scale($vmdata['wrrate'],$unit,null,null,1024)."$unit/s&nbsp&nbsp</span>";
$echo[$vmencode ]['net'] = "<span>Net: "._("RX").": ".my_scale($vmdata['rxrate'],$unit,null,null,1024)."$unit/s "._("TX").": ".my_scale($vmdata['txrate'],$unit,null,null,1024)."$unit/s&nbsp&nbsp</span>";
}
}

Expand Down
Loading