[object Object]

← back to Desktop Dotbar

dotbar: RAM chip shows real memory pressure, not cache-inflated used% (96%->44%) + green/amber/red bands

b05080a61ccbb81df0dc40e0f6866c3046889bf3 · 2026-09-23 09:51:33 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VyZZwbLBMfoBmdLGuxBuD

Files touched

Diff

commit b05080a61ccbb81df0dc40e0f6866c3046889bf3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 23 09:51:33 2026 -0700

    dotbar: RAM chip shows real memory pressure, not cache-inflated used% (96%->44%) + green/amber/red bands
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_017VyZZwbLBMfoBmdLGuxBuD
---
 public/index.html |  7 +++++--
 server.js         | 19 ++++++++++++++-----
 2 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/public/index.html b/public/index.html
index f683a7f..d971a5d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -119,6 +119,9 @@ let openColor = null, data = null, _miss = 0, curBarH = BAR_H, curBarW = 210, cu
 let cfg = { orientation: 'top', autohide: false, barW: 210, barLen: 0 };
 // RAM Sniper readout (RAM%/CPU% + daemon on/off + top hogs), polled from /api/ram.
 let ram = { ram_pct: '–', cpu_pct: '–', on: false, hogs: [] };
+// RAM pressure bands (Steve 2026-09-23): green < 50, amber < 75, red >= 75. Input is the
+// memory-pressure-based used% from the server, so these thresholds mean real strain, not cache.
+function ramColor(p){ p = parseFloat(p); if (isNaN(p)) return 'var(--fg)'; return p < 50 ? '#4ade80' : (p < 75 ? '#fbbf24' : '#f87171'); }
 // The four needs-Steve states pulse; green/pink stay solid (Steve 2026-09-15 dot-flash directive).
 const WAIT = new Set(['lightblue','orange','purple','yellow']);
 const ORIENT_LABEL = { top: '▲ Top', left: '◀ Left', right: '▶ Right' };
@@ -163,8 +166,8 @@ function renderBar(){
   const rchip = document.createElement('div');
   rchip.className = 'chip ramchip' + (openColor==='ram' ? ' active':'');
   rchip.title = 'RAM / CPU — click for top memory hogs';
-  rchip.innerHTML = `<span class="dot" style="background:#59d0ff"></span>`
-    + `<span class="cnt">🧠 ${ram.ram_pct}% <span style="color:var(--dim);font-weight:600">⚙ ${ram.cpu_pct}%</span></span>`;
+  rchip.innerHTML = `<span class="dot" style="background:${ramColor(ram.ram_pct)}"></span>`
+    + `<span class="cnt">🧠 <span style="color:${ramColor(ram.ram_pct)};font-weight:700">${ram.ram_pct}%</span> <span style="color:var(--dim);font-weight:600">⚙ ${ram.cpu_pct}%</span></span>`;
   rchip.onclick = () => toggle('ram');
   bar.appendChild(rchip);
   const snbtn = document.createElement('button');
diff --git a/server.js b/server.js
index c67ab1c..f81100b 100755
--- a/server.js
+++ b/server.js
@@ -137,20 +137,29 @@ async function sniperOn() {
 }
 // Zero-dep stats: system RAM%/CPU% from `top`, top hogs from `ps` (no psutil).
 async function getRam() {
-  const [{ stdout: topOut }, { stdout: memBytes }, { stdout: psOut }] = await Promise.all([
+  const [{ stdout: topOut }, { stdout: memBytes }, { stdout: psOut }, mp] = await Promise.all([
     run('top', ['-l', '1', '-n', '0']),
     run('sysctl', ['-n', 'hw.memsize']),
     run('ps', ['-axo', 'pid=,rss=,comm=']),
+    run('memory_pressure', []).then(r => r.stdout).catch(() => ''),  // resilient: never rejects the batch
   ]);
   // CPU: "CPU usage: 4.1% user, 5.2% sys, 90.6% idle" -> used = 100 - idle.
   let cpu = 0;
   const idle = /CPU usage:.*?([\d.]+)%\s*idle/i.exec(topOut);
   if (idle) cpu = Math.max(0, Math.min(100, 100 - parseFloat(idle[1])));
-  // RAM: "PhysMem: 21G used (2.0G wired), 11G unused." -> used/(used+unused).
-  const toGB = (n, u) => parseFloat(n) * ({ K: 1 / 1048576, M: 1 / 1024, G: 1, T: 1024 }[u] || 1);
+  // RAM: use macOS memory-pressure (Activity Monitor's real strain signal) so the chip reflects
+  // genuine pressure, not cache. "used" = 100 - free%. macOS keeps RAM ~90% full with reclaimable
+  // file cache, so the old `top` PhysMem used/(used+unused) read ~96% and cried wolf (Steve 2026-09-23).
   let ramPct = 0;
-  const pm = /PhysMem:\s*([\d.]+)([KMGT])\s*used.*?([\d.]+)([KMGT])\s*unused/i.exec(topOut);
-  if (pm) { const used = toGB(pm[1], pm[2]), unused = toGB(pm[3], pm[4]); if (used + unused) ramPct = used / (used + unused) * 100; }
+  const freeM = /free percentage:\s*([\d.]+)%/i.exec(mp);
+  if (freeM) {
+    ramPct = Math.max(0, Math.min(100, 100 - parseFloat(freeM[1])));
+  } else {
+    // fallback if memory_pressure is unavailable: the legacy top PhysMem used/(used+unused)
+    const toGB = (n, u) => parseFloat(n) * ({ K: 1 / 1048576, M: 1 / 1024, G: 1, T: 1024 }[u] || 1);
+    const pm = /PhysMem:\s*([\d.]+)([KMGT])\s*used.*?([\d.]+)([KMGT])\s*unused/i.exec(topOut);
+    if (pm) { const used = toGB(pm[1], pm[2]), unused = toGB(pm[3], pm[4]); if (used + unused) ramPct = used / (used + unused) * 100; }
+  }
   const totalBytes = parseInt(memBytes, 10) || 0;
   const hogs = psOut.trim().split('\n').map(l => {
     const m = /^\s*(\d+)\s+(\d+)\s+(.+)$/.exec(l);

← fa809cb dotbar: visible dotted resize grip + one-click Compact/Full  ·  back to Desktop Dotbar  ·  dotbar: merge ticket-board summary (BLOCKED/DOING/OPEN count 4ad7b46 →