< Back to all hacks

#42 Dashboard v6: Green Cyberpunk + RAM Breakdown

Launcher
Problem
Dashboard was red-themed, had unused tabs, didn't explain WHY RAM was high.
Solution
Green Matrix CRT theme, boot animation, RAM process breakdown reading /proc/[pid]/status for ALL processes.
Lesson
Reading /proc directly is faster and more reliable than parsing shell command output.

Context

The dashboard went through 6 major versions. Version 5 had a red cyberpunk theme, multiple unused tabs (placeholder for features that never shipped), and only showed total RAM — not which processes were consuming it. When the phone was using 850 MB out of 1 GB, there was no way to know where the RAM went without SSHing in and running ps.

Version 6 was a complete redesign with two goals: match the PocketClaw green CRT aesthetic, and show per-process RAM breakdown so optimization targets are immediately visible.

Implementation

All data is read from the /proc filesystem directly — zero shell commands, zero spawned processes:

// RAM and Swap from /proc/meminfo
const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
const total = parseInt(meminfo.match(/MemTotal:\s+(\d+)/)[1]);
const free = parseInt(meminfo.match(/MemFree:\s+(\d+)/)[1]);
const available = parseInt(meminfo.match(/MemAvailable:\s+(\d+)/)[1]);

// Per-process RSS from /proc/[pid]/status
const procs = fs.readdirSync('/proc')
  .filter(f => /^\d+$/.test(f))
  .map(pid => {
    try {
      const status = fs.readFileSync(`/proc/${pid}/status`, 'utf8');
      const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, 'utf8')
        .replace(/\0/g, ' ').trim();
      const rss = parseInt(status.match(/VmRSS:\s+(\d+)/)?.[1] || '0');
      return { pid, name: cmdline || pid, rss };
    } catch(e) { return null; }
  })
  .filter(Boolean)
  .sort((a, b) => b.rss - a.rss);

// Uptime from /proc/uptime
const uptime = parseFloat(fs.readFileSync('/proc/uptime', 'utf8').split(' ')[0]);

Process names are cleaned up for readability (Hack #43):

// Clean Android package names for display
name = name
  .replace(/^com\.android\./, "")
  .replace(/^android\.process\./, "")
  .replace(/^com\.motorola\./, "moto.")
  .replace(/^com\.google\.android\./, "goog.")
  .replace(/^com\.pocketclaw\./, "")
  .replace(/^com\.qualcomm\./, "qc.")
  .replace(/^com\.termux\.?/, "termux");

The green CRT theme uses the same color palette as the website: #000A00 background, #00FF41 green, #EE3333 red for warnings.

Verification

# Access dashboard:
curl -s http://localhost:9003/dashboard | head -20
# Expected: HTML with CRT green styling

# Check API endpoint:
curl -s http://localhost:9000/api/status | python -m json.tool | head -10
# Expected: JSON with ram, swap, uptime, processes

# Verify process list includes per-process RSS:
curl -s http://localhost:9000/api/status | grep -o '"rss":[0-9]*' | head -5
# Expected: RSS values for top processes

Gotchas

  • Reading /proc/[pid]/status can fail for kernel threads or short-lived processes — always use try/catch
  • Some processes report 0 KB VmRSS (kernel threads, zombies) — filter these out
  • /proc/meminfo values are in KB, not bytes. Convert for display
  • The per-process breakdown revealed that the WebView launcher was using 216 MB — more than the gateway itself (186 MB). This directly led to the native APK rewrite (Hack #40)

Result

MetricBefore (v5)After (v6)
ThemeRed cyberpunkGreen CRT
RAM infoTotal onlyPer-process breakdown
Data sourceShell commands/proc (zero overhead)
Tabs4 (unused)1 (focused)