From aec63b7abda32134d3c604695436a7c7d207eac2 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 2 Dec 2025 00:08:05 +0000 Subject: [PATCH] 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 --- .../components/Dashboard/StackedMemoryBar.tsx | 68 +++++++++++-------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/frontend-modern/src/components/Dashboard/StackedMemoryBar.tsx b/frontend-modern/src/components/Dashboard/StackedMemoryBar.tsx index d6e86f4..7c4103d 100644 --- a/frontend-modern/src/components/Dashboard/StackedMemoryBar.tsx +++ b/frontend-modern/src/components/Dashboard/StackedMemoryBar.tsx @@ -42,38 +42,46 @@ export function StackedMemoryBar(props: StackedMemoryBarProps) { if (props.total <= 0) return []; 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) - // We visualize swap as an overlay or separate segment if we want to show it relative to RAM - // But typically swap is separate. - // Let's stack RAM components (Active + Balloon) within the RAM bar. - // And maybe show Swap as a separate indicator or just in tooltip? - // The user asked for "Detailed Memory Composition". - // Let's stack Active and Balloon within the main bar. - // 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". + // Proxmox balloon semantics: + // - balloon = 0: ballooning not enabled/configured + // - balloon = total (maxmem): ballooning configured but at max (no actual ballooning) + // - balloon < total: active ballooning, guest limited to 'balloon' bytes + // + // Only show balloon segment when actual ballooning is in effect + const hasActiveBallooning = balloon > 0 && balloon < props.total; - // Let's stick to RAM composition for the bar: Active (Green), Balloon (Yellow). - // Swap usage will be shown in tooltip and maybe change the bar color if critical? - // Actually, let's try to include Swap if it's significant, but it might be confusing if it exceeds 100% of RAM. + // Used memory is what the guest is actually consuming + const usedPercent = (props.used / props.total) * 100; - // Alternative: The bar is RAM. - // Green: Active - // Yellow: Balloon + if (hasActiveBallooning) { + // With active ballooning: + // - 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 balloonPercent = (balloon / props.total) * 100; + const segs = [ + { type: 'Active', bytes: props.used, percent: usedPercent, color: MEMORY_COLORS.active }, + ]; - const segs = [ - { type: 'Active', bytes: active, percent: activePercent, color: MEMORY_COLORS.active }, - { type: 'Balloon', bytes: balloon, percent: balloonPercent, color: MEMORY_COLORS.balloon }, + // Only show balloon segment if there's room between used and balloon limit + if (balloonLimitPercent > 0 && balloon > props.used) { + 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); - - return segs; }); const swapPercent = createMemo(() => { @@ -185,15 +193,15 @@ export function StackedMemoryBar(props: StackedMemoryBarProps) { {/* RAM Breakdown */}
- Active + Used - {formatBytes(Math.max(0, props.used - (props.balloon || 0)), 0)} + {formatBytes(props.used, 0)}
- 0}> + 0 && props.balloon < props.total}>
- Balloon + Balloon Limit {formatBytes(props.balloon || 0, 0)}