Fix memory bar width showing 100% instead of actual percentage

The StackedMemoryBar component was incorrectly calculating segments when
Proxmox balloon was set to maxmem (no actual ballooning). In this case:
- balloon = total (e.g., 32GB)
- used = actual memory (e.g., 20GB)
- active = used - balloon = 20GB - 32GB = 0 (clamped)
- balloonPercent = 32GB/32GB * 100 = 100%

This caused the bar to always show 100% yellow (balloon) even when the
actual memory usage was much lower.

Fixed by only showing balloon segment when actual ballooning is in effect
(balloon > 0 && balloon < total). When there's no ballooning, the bar
now correctly shows used memory as green with the actual percentage.

Related to #788
This commit is contained in:
rcourtman 2025-12-02 00:08:05 +00:00
parent 02419827b6
commit aec63b7abd

View file

@ -42,38 +42,46 @@ export function StackedMemoryBar(props: StackedMemoryBarProps) {
if (props.total <= 0) return []; if (props.total <= 0) return [];
const balloon = props.balloon || 0; const balloon = props.balloon || 0;
// Active memory is used minus balloon (since balloon is "used" but reclaimed)
// Note: This is a simplification. Proxmox reports 'used' including balloon.
const active = Math.max(0, props.used - balloon);
// Calculate percentages relative to RAM total (swap is extra) // Proxmox balloon semantics:
// We visualize swap as an overlay or separate segment if we want to show it relative to RAM // - balloon = 0: ballooning not enabled/configured
// But typically swap is separate. // - balloon = total (maxmem): ballooning configured but at max (no actual ballooning)
// Let's stack RAM components (Active + Balloon) within the RAM bar. // - balloon < total: active ballooning, guest limited to 'balloon' bytes
// And maybe show Swap as a separate indicator or just in tooltip? //
// The user asked for "Detailed Memory Composition". // Only show balloon segment when actual ballooning is in effect
// Let's stack Active and Balloon within the main bar. const hasActiveBallooning = balloon > 0 && balloon < props.total;
// If Swap is used, we can perhaps show it as a segment that "overflows" or just a separate color if we treat total as RAM+Swap?
// Standard approach: Bar represents RAM. Segments are Active, Balloon.
// Swap is usually separate. But we can include it if we want to show "Memory Pressure".
// Let's stick to RAM composition for the bar: Active (Green), Balloon (Yellow). // Used memory is what the guest is actually consuming
// Swap usage will be shown in tooltip and maybe change the bar color if critical? const usedPercent = (props.used / props.total) * 100;
// Actually, let's try to include Swap if it's significant, but it might be confusing if it exceeds 100% of RAM.
// Alternative: The bar is RAM. if (hasActiveBallooning) {
// Green: Active // With active ballooning:
// Yellow: Balloon // - Green: actual used memory
// - Yellow: balloon limit marker (shows where the guest is capped)
// The balloon limit shows as a segment from used to balloon
const balloonLimitPercent = Math.max(0, (balloon / props.total) * 100 - usedPercent);
const activePercent = (active / props.total) * 100; const segs = [
const balloonPercent = (balloon / props.total) * 100; { type: 'Active', bytes: props.used, percent: usedPercent, color: MEMORY_COLORS.active },
];
const segs = [ // Only show balloon segment if there's room between used and balloon limit
{ type: 'Active', bytes: active, percent: activePercent, color: MEMORY_COLORS.active }, if (balloonLimitPercent > 0 && balloon > props.used) {
{ type: 'Balloon', bytes: balloon, percent: balloonPercent, color: MEMORY_COLORS.balloon }, segs.push({
type: 'Balloon',
bytes: balloon - props.used,
percent: balloonLimitPercent,
color: MEMORY_COLORS.balloon,
});
}
return segs.filter(s => s.bytes > 0);
}
// No active ballooning - just show used memory as green
return [
{ type: 'Active', bytes: props.used, percent: usedPercent, color: MEMORY_COLORS.active },
].filter(s => s.bytes > 0); ].filter(s => s.bytes > 0);
return segs;
}); });
const swapPercent = createMemo(() => { const swapPercent = createMemo(() => {
@ -185,15 +193,15 @@ export function StackedMemoryBar(props: StackedMemoryBarProps) {
{/* RAM Breakdown */} {/* RAM Breakdown */}
<div class="flex justify-between gap-3 py-0.5"> <div class="flex justify-between gap-3 py-0.5">
<span class="text-green-400">Active</span> <span class="text-green-400">Used</span>
<span class="whitespace-nowrap text-gray-300"> <span class="whitespace-nowrap text-gray-300">
{formatBytes(Math.max(0, props.used - (props.balloon || 0)), 0)} {formatBytes(props.used, 0)}
</span> </span>
</div> </div>
<Show when={(props.balloon || 0) > 0}> <Show when={(props.balloon || 0) > 0 && props.balloon < props.total}>
<div class="flex justify-between gap-3 py-0.5 border-t border-gray-700/50"> <div class="flex justify-between gap-3 py-0.5 border-t border-gray-700/50">
<span class="text-yellow-400">Balloon</span> <span class="text-yellow-400">Balloon Limit</span>
<span class="whitespace-nowrap text-gray-300"> <span class="whitespace-nowrap text-gray-300">
{formatBytes(props.balloon || 0, 0)} {formatBytes(props.balloon || 0, 0)}
</span> </span>