v0.27.3: Use real system memory everywhere, add monitoring memory bar
Deploy page, pre-start check, and deploy validation now use actual /proc/meminfo usage instead of declared mem_request sums. New GetMemoryMB() helper for lightweight real-time memory reads. Monitoring page gains a stacked memory distribution bar showing per-container usage, OS overhead, and free memory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -354,35 +354,36 @@ func (s *Server) deployHandler(w http.ResponseWriter, r *http.Request, name stri
|
||||
// Memory info for deploy page (only for non-deployed apps)
|
||||
if !alreadyDeployed {
|
||||
memInfo := map[string]interface{}{"Available": false}
|
||||
totalMB, memErr := system.GetTotalMemoryMB()
|
||||
totalMB, usedMB, memErr := system.GetMemoryMB()
|
||||
if memErr == nil {
|
||||
reservedMB := s.cfg.System.ReservedMemoryMB
|
||||
usableMB := totalMB - reservedMB
|
||||
committedReqMB, committedLimitMB := s.stackMgr.CommittedMemory()
|
||||
newReqMB := stacks.ParseMemoryMB(meta.Resources.MemRequest)
|
||||
newLimitMB := stacks.ParseMemoryMB(meta.Resources.MemLimit)
|
||||
afterReqMB := committedReqMB + newReqMB
|
||||
afterLimitMB := committedLimitMB + newLimitMB
|
||||
afterMB := usedMB + newReqMB
|
||||
percent := 0
|
||||
if usableMB > 0 {
|
||||
percent = afterReqMB * 100 / usableMB
|
||||
percent = afterMB * 100 / usableMB
|
||||
}
|
||||
usedPercent := 0
|
||||
if usableMB > 0 {
|
||||
usedPercent = usedMB * 100 / usableMB
|
||||
}
|
||||
|
||||
committedPercent := 0
|
||||
if usableMB > 0 {
|
||||
committedPercent = committedReqMB * 100 / usableMB
|
||||
}
|
||||
// Overcommit warning still uses declared limits
|
||||
_, committedLimitMB := s.stackMgr.CommittedMemory()
|
||||
newLimitMB := stacks.ParseMemoryMB(meta.Resources.MemLimit)
|
||||
afterLimitMB := committedLimitMB + newLimitMB
|
||||
|
||||
memInfo["Available"] = true
|
||||
memInfo["TotalMB"] = totalMB
|
||||
memInfo["ReservedMB"] = reservedMB
|
||||
memInfo["UsableMB"] = usableMB
|
||||
memInfo["CommittedMB"] = committedReqMB
|
||||
memInfo["UsedMB"] = usedMB
|
||||
memInfo["NewRequestMB"] = newReqMB
|
||||
memInfo["AfterMB"] = afterReqMB
|
||||
memInfo["AfterMB"] = afterMB
|
||||
memInfo["Percent"] = percent
|
||||
memInfo["CommittedPercent"] = committedPercent
|
||||
memInfo["Blocked"] = newReqMB > 0 && afterReqMB > usableMB
|
||||
memInfo["UsedPercent"] = usedPercent
|
||||
memInfo["Blocked"] = newReqMB > 0 && afterMB > usableMB
|
||||
memInfo["OvercommitWarn"] = newLimitMB > 0 && afterLimitMB > totalMB
|
||||
}
|
||||
data["MemoryInfo"] = memInfo
|
||||
|
||||
@@ -204,15 +204,15 @@
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="memory-summary-header">
|
||||
<span class="memory-summary-label">Memória foglalás</span>
|
||||
<span class="memory-summary-label">Memória</span>
|
||||
<span class="memory-summary-value">{{.AfterMB}} MB / {{.UsableMB}} MB ({{.Percent}}%)</span>
|
||||
</div>
|
||||
<div class="memory-bar-stacked">
|
||||
<div class="memory-bar-segment memory-bar-committed" style="width:{{.CommittedPercent}}%" title="Jelenlegi foglalás: {{.CommittedMB}} MB"></div>
|
||||
<div class="memory-bar-segment memory-bar-new" style="width:{{subtract .Percent .CommittedPercent}}%" title="{{$.Meta.DisplayName}}: +{{.NewRequestMB}} MB"></div>
|
||||
<div class="memory-bar-segment memory-bar-committed" style="width:{{.UsedPercent}}%" title="Jelenlegi használat: {{.UsedMB}} MB"></div>
|
||||
<div class="memory-bar-segment memory-bar-new" style="width:{{subtract .Percent .UsedPercent}}%" title="{{$.Meta.DisplayName}}: +{{.NewRequestMB}} MB"></div>
|
||||
</div>
|
||||
<div class="memory-bar-legend">
|
||||
<span class="memory-legend-item"><span class="memory-legend-dot memory-legend-committed"></span>Jelenlegi foglalás ({{.CommittedMB}} MB)</span>
|
||||
<span class="memory-legend-item"><span class="memory-legend-dot memory-legend-committed"></span>Jelenlegi használat ({{.UsedMB}} MB)</span>
|
||||
<span class="memory-legend-item"><span class="memory-legend-dot memory-legend-new"></span>{{$.Meta.DisplayName}} (+{{.NewRequestMB}} MB)</span>
|
||||
</div>
|
||||
{{if .OvercommitWarn}}
|
||||
|
||||
@@ -163,6 +163,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section 3.5: Memory Distribution Bar -->
|
||||
<div class="monitor-card" id="memory-distribution-card" style="display:none">
|
||||
<h3>Memória eloszlás</h3>
|
||||
<div id="mem-dist-header" class="memory-dist-header"></div>
|
||||
<div class="memory-dist-bar" id="mem-dist-bar"></div>
|
||||
<div class="memory-bar-legend" id="mem-dist-legend"></div>
|
||||
</div>
|
||||
|
||||
<!-- Section 4: Container Resources -->
|
||||
<div class="monitor-card">
|
||||
<h3>Alkalmazás erőforrások</h3>
|
||||
@@ -506,11 +514,70 @@
|
||||
chartContainerMem.data.labels = containerNames;
|
||||
chartContainerMem.data.datasets[0].data = memData;
|
||||
chartContainerMem.update('none');
|
||||
|
||||
buildMemoryDistributionBar(data);
|
||||
} catch(e) {
|
||||
console.error('Failed to load container summary:', e);
|
||||
}
|
||||
}
|
||||
|
||||
var memDistPalette = ['#238636','#0088cc','#d29922','#da3633','#8b5cf6','#ec6547','#2ea043','#1f6feb','#e3b341','#f47067'];
|
||||
|
||||
async function buildMemoryDistributionBar(containers) {
|
||||
var totalMB = {{.SystemInfo.TotalMemMB}};
|
||||
if (!totalMB) return;
|
||||
|
||||
// Get real-time used memory from API
|
||||
var usedMB = 0;
|
||||
try {
|
||||
var resp = await fetch('/api/system/info');
|
||||
var json = await resp.json();
|
||||
if (json.ok && json.data) usedMB = json.data.used_mem_mb || 0;
|
||||
} catch(e) {}
|
||||
if (!usedMB) return;
|
||||
|
||||
var card = document.getElementById('memory-distribution-card');
|
||||
var bar = document.getElementById('mem-dist-bar');
|
||||
var legend = document.getElementById('mem-dist-legend');
|
||||
var header = document.getElementById('mem-dist-header');
|
||||
|
||||
// Sum container memory
|
||||
var appTotal = 0;
|
||||
containers.forEach(function(c) { appTotal += c.mem_usage_mb || 0; });
|
||||
var osMB = Math.max(0, usedMB - appTotal);
|
||||
var freeMB = Math.max(0, totalMB - usedMB);
|
||||
|
||||
function fmtMB(mb) { return mb >= 1024 ? (mb/1024).toFixed(1) + ' GB' : Math.round(mb) + ' MB'; }
|
||||
|
||||
header.textContent = 'Használt: ' + fmtMB(usedMB) + ' / ' + fmtMB(totalMB) + ' (' + Math.round(usedMB/totalMB*100) + '%)';
|
||||
|
||||
// Build bar segments
|
||||
var html = '';
|
||||
var legendHtml = '';
|
||||
containers.forEach(function(c, i) {
|
||||
var mb = c.mem_usage_mb || 0;
|
||||
if (mb < 1) return;
|
||||
var pct = (mb / totalMB * 100).toFixed(2);
|
||||
var color = memDistPalette[i % memDistPalette.length];
|
||||
html += '<div class="memory-bar-segment" style="width:' + pct + '%;background:' + color + '" title="' + c.name + ': ' + fmtMB(mb) + '"></div>';
|
||||
legendHtml += '<div class="memory-legend-item"><span class="memory-legend-dot" style="background:' + color + '"></span>' + c.name + ' (' + fmtMB(mb) + ')</div>';
|
||||
});
|
||||
|
||||
// OS / system overhead
|
||||
if (osMB > 10) {
|
||||
var osPct = (osMB / totalMB * 100).toFixed(2);
|
||||
html += '<div class="memory-bar-segment" style="width:' + osPct + '%;background:var(--text-muted);opacity:0.5" title="Rendszer: ' + fmtMB(osMB) + '"></div>';
|
||||
legendHtml += '<div class="memory-legend-item"><span class="memory-legend-dot" style="background:var(--text-muted);opacity:0.5"></span>Rendszer (' + fmtMB(osMB) + ')</div>';
|
||||
}
|
||||
|
||||
// Free space legend only
|
||||
legendHtml += '<div class="memory-legend-item"><span class="memory-legend-dot" style="background:var(--bg-secondary);border:1px solid var(--border-color)"></span>Szabad (' + fmtMB(freeMB) + ')</div>';
|
||||
|
||||
bar.innerHTML = html;
|
||||
legend.innerHTML = legendHtml;
|
||||
card.style.display = '';
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// CONTAINER DETAIL (per-container history)
|
||||
// =============================================
|
||||
|
||||
@@ -840,6 +840,27 @@ select.form-control option { background: var(--bg-secondary); color: var(--text-
|
||||
background: rgba(35, 134, 54, 0.45);
|
||||
border: 1px solid #4edf72;
|
||||
}
|
||||
.memory-dist-bar {
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
border-radius: 7px;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.memory-dist-bar .memory-bar-segment:first-child {
|
||||
border-radius: 6px 0 0 6px;
|
||||
}
|
||||
.memory-dist-bar .memory-bar-segment:last-child {
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
.memory-dist-header {
|
||||
font-size: .85rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: .5rem;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
/* Logs */
|
||||
.logs-container {
|
||||
|
||||
Reference in New Issue
Block a user