Live UI: stop the page from jumping, and put Vitals in the bookmark bar

The page rebuilt its whole header, bar and row list twice a second, so
numbers snapped, columns resized as digits changed, and rows swapped places
over a hundredth of a core.

- header values are tabular-width and tweened toward the newest sample
- machine-bar segments are keyed and reordered by flex order, so widths
  animate instead of being recreated mid-transition
- rows are keyed DOM nodes updated in place, sorted on a 0.1-core grid with
  the previous order as tiebreak, with hysteresis at the idle threshold
- tabs, banner and detail panes only re-render when their data changes
- poll every 700ms and drop repeat frames from the 2s sampler

bookmark.py puts a Vitals bookmark and a popup bookmarklet in the browser's
bookmark bar. It recomputes Chromium's bookmark checksum (MD5 over the
canonical node serialisation) and verifies it after writing, because a file
that fails that check leaves the browser showing an empty bar while the file
on disk still looks perfect. Refuses while the browser holds the profile,
keeps a timestamped backup, idempotent. install.sh runs it.

Co-developed-with: CLU
This commit is contained in:
CLU 2026-10-20 13:52:49 -05:00
parent f086afc030
commit 95c1c1d3eb
5 changed files with 754 additions and 85 deletions

View file

@ -85,6 +85,44 @@ silently orphaned process.
Options: `--port N`, `--host ADDR`, `VITALS_PORT` env var. Options: `--port N`, `--host ADDR`, `VITALS_PORT` env var.
### The bookmark
`install.sh` also puts Vitals in your browser's bookmark bar, as a bookmark and
as a popup bookmarklet that opens it in its own window. A tool you have to
remember the URL of does not get used.
Chromium keeps the bookmark model in memory and rewrites the file on its next
save, so the browser has to be closed while that write happens. If it is running,
install prints the one command to run afterwards instead of failing:
```bash
python3 bookmark.py --install # add bookmark + popup
python3 bookmark.py --install --wait 600 # wait for the browser to exit
python3 bookmark.py --status # is it there, is the file sane
python3 bookmark.py --remove # take them back out
```
The tool recomputes Chromium's bookmark checksum (MD5 over a canonical
serialisation of every node) and verifies it after writing, because a file that
fails that check leaves the browser showing an empty bar while the file on disk
still looks perfect. It keeps a timestamped backup of the file it edited.
## Live updates
The sampler runs every 2 seconds, because one sample costs about 400 ms of CPU
and a monitor that eats the machine is the joke writing itself. The page polls
faster than that and eases between frames, so the numbers move continuously
instead of snapping twice a second:
- header values are tabular-width and tweened toward the newest sample
- machine-bar segments are keyed and reordered with flex `order`, so their
widths actually animate instead of being recreated mid-transition
- rows are keyed DOM nodes updated in place, sorted on a 0.1-core grid with the
previous order as tiebreak, so a row does not jump position over a hundredth
of a core
- a row appears at 0.05 cores but only leaves after it stays under 0.02, so
nothing flickers in and out at the threshold
## Getting browser tabs ## Getting browser tabs
Tabs come from the DevTools Protocol, which a browser only exposes if it was Tabs come from the DevTools Protocol, which a browser only exposes if it was

363
bookmark.py Normal file
View file

@ -0,0 +1,363 @@
#!/usr/bin/env python3
"""Put Vitals in the browser's bookmark bar.
Chromium-family browsers (Brave, Chrome, Chromium) store bookmarks as a JSON
file with an MD5 checksum over a canonical serialisation of every node. If the
checksum does not match, the browser silently falls back to Bookmarks.bak and
the edit disappears, so this tool recomputes it and refuses to write a file it
cannot verify.
Checksum algorithm (components/bookmarks/browser/bookmark_codec.cc):
for each root folder, in order bookmark_bar, other, synced:
feed the root itself as a folder node
then every descendant, depth first, in order
folder node : id + name (UTF-16LE bytes) + b"folder"
url node : id + name (UTF-16LE bytes) + b"url" + url (UTF-8)
Verified by reproducing the stored checksum of a real 13.8 MB profile file.
Safe by construction: refuses while the browser owns the profile, writes through
a temp file with an atomic rename, keeps a timestamped backup, and re-reads the
file it just wrote to confirm the checksum lands.
Usage:
python3 bookmark.py --install # add Vitals + Vitals popup
python3 bookmark.py --install --wait 600 # wait up to 10 min for browser exit
python3 bookmark.py --status # what is in the bar right now
python3 bookmark.py --remove # take them back out
python3 bookmark.py --install --profile-dir /path/to/Profile
python3 bookmark.py --install --dry-run
"""
from __future__ import annotations
import argparse
import glob
import hashlib
import json
import os
import shutil
import sys
import time
import uuid
VITALS_URL = "http://127.0.0.1:8791/"
BOOKMARKLET = (
"javascript:(function(){window.open('http://127.0.0.1:8791/',"
"'vitals','width=1500,height=950,menubar=no,toolbar=no,location=no,"
"status=no');})();void(0);"
)
ROOTS = ("bookmark_bar", "other", "synced")
WINDOWS_EPOCH_OFFSET = 11644473600 # seconds between 1601-01-01 and 1970-01-01
# ---------------------------------------------------------------- checksum
def _u16(s: str) -> bytes:
return s.encode("utf-16-le")
def _feed_url(h, node) -> None:
h.update(str(node["id"]).encode())
h.update(_u16(node.get("name") or ""))
h.update(b"url")
h.update((node.get("url") or "").encode("utf-8"))
def _feed_folder(h, node) -> None:
h.update(str(node["id"]).encode())
h.update(_u16(node.get("name") or ""))
h.update(b"folder")
def _walk(h, node) -> None:
if node.get("type") == "url":
_feed_url(h, node)
else:
_feed_folder(h, node)
for child in node.get("children", []):
_walk(h, child)
def checksum(doc) -> str:
h = hashlib.md5()
for key in ROOTS:
root = doc["roots"].get(key)
if not root:
continue
_feed_folder(h, root)
for child in root.get("children", []):
_walk(h, child)
return h.hexdigest()
# ---------------------------------------------------------------- discovery
def candidate_profiles():
home = os.path.expanduser("~")
pats = [
# Flatpak builds keep their profile inside the sandbox.
f"{home}/.var/app/com.brave.Browser/config/BraveSoftware/Brave-Browser/Default",
f"{home}/.var/app/com.google.Chrome/config/google-chrome/Default",
f"{home}/.var/app/org.chromium.Chromium/config/chromium/Default",
# Native builds.
f"{home}/.config/BraveSoftware/Brave-Browser/Default",
f"{home}/.config/google-chrome/Default",
f"{home}/.config/chromium/Default",
# Chromium-family shells.
f"{home}/.config/microsoft-edge/Default",
f"{home}/.config/vivaldi/Default",
]
out = []
for p in pats:
if os.path.isfile(os.path.join(p, "Bookmarks")):
out.append(p)
return out
def profile_in_use(profile_dir: str) -> bool:
"""True when a browser currently owns this profile.
Chromium takes a SingletonLock in the user data dir (the parent of the
profile dir). Reading the bookmarks file under a live browser is pointless:
the in-memory model overwrites it on the next save.
"""
user_data_dir = os.path.dirname(profile_dir.rstrip("/"))
for name in ("SingletonLock", "SingletonSocket", "SingletonCookie"):
if os.path.exists(os.path.join(user_data_dir, name)):
return True
# Also catch a browser pointed at this profile with an explicit flag.
needle = os.path.abspath(profile_dir)
try:
pids = [p for p in os.listdir("/proc") if p.isdigit()]
except OSError:
return False
for pid in pids:
try:
with open(f"/proc/{pid}/cmdline", "rb") as f:
cmd = f.read().replace(b"\x00", b" ").decode("utf8", "replace")
except OSError:
continue
if "--type=" in cmd:
continue
if needle in cmd and any(b in cmd for b in ("brave", "chrome", "chromium", "edge", "vivaldi")):
return True
return False
def max_id(doc) -> int:
top = 0
def walk(node):
nonlocal top
try:
top = max(top, int(node.get("id", 0)))
except (TypeError, ValueError):
pass
for child in node.get("children", []):
walk(child)
for key in ROOTS:
if doc["roots"].get(key):
walk(doc["roots"][key])
return top
def now_micros() -> str:
return str(int((time.time() + WINDOWS_EPOCH_OFFSET) * 1_000_000))
def find_ours(doc):
"""Return (vitals_node, bookmarklet_node) currently in the bar, if any."""
plain = bm = None
def walk(node):
nonlocal plain, bm
if node.get("type") == "url":
url = node.get("url") or ""
if url == VITALS_URL:
plain = node
elif url.startswith("javascript:") and "8791" in url:
bm = node
for child in node.get("children", []):
walk(child)
for key in ROOTS:
if doc["roots"].get(key):
walk(doc["roots"][key])
return plain, bm
def make_node(node_id: int, name: str, url: str) -> dict:
stamp = now_micros()
return {
"date_added": stamp,
"date_last_used": "0",
"guid": str(uuid.uuid4()),
"id": str(node_id),
"name": name,
"type": "url",
"url": url,
}
# ---------------------------------------------------------------- operations
def load(profile_dir):
path = os.path.join(profile_dir, "Bookmarks")
with open(path, encoding="utf8") as f:
raw = f.read()
return path, raw, json.loads(raw)
def save(path, doc, raw_before) -> None:
doc["checksum"] = checksum(doc)
doc.pop("checksum_sha256", None) # browser rewrites it; absent is valid
text = json.dumps(doc, indent=3, sort_keys=True, ensure_ascii=True)
stamp = time.strftime("%Y%m%d-%H%M%S")
backup = f"{path}.pre-vitals-{stamp}.bak"
shutil.copy2(path, backup)
tmp = f"{path}.vitals.tmp"
with open(tmp, "w", encoding="utf8") as f:
f.write(text)
shutil.copystat(path, tmp)
os.replace(tmp, path)
print(f" backup: {backup}")
# Verify what actually landed on disk.
with open(path, encoding="utf8") as f:
check = json.load(f)
if checksum(check) != check.get("checksum"):
raise SystemExit("REFUSING: checksum did not verify after write")
if len(raw_before) and not check["roots"].get("bookmark_bar"):
raise SystemExit("REFUSING: bookmark bar missing after write")
def install(profile_dir, dry_run=False, name="Vitals", popup_name="Vitals popup") -> int:
path, raw, doc = load(profile_dir)
plain, bm = find_ours(doc)
next_id = max_id(doc) + 1
added = []
if plain is None:
doc["roots"]["bookmark_bar"].setdefault("children", []).append(
make_node(next_id, name, VITALS_URL)
)
next_id += 1
added.append(name)
elif plain.get("name") != name:
plain["name"] = name
added.append(f"{name} (renamed)")
if bm is None:
doc["roots"]["bookmark_bar"].setdefault("children", []).append(
make_node(next_id, popup_name, BOOKMARKLET)
)
added.append(popup_name)
if not added:
print(" already installed, nothing to do")
return 0
if dry_run:
probe = json.loads(raw)
probe["checksum"] = checksum(doc)
print(f" dry run: would add {added} | new checksum {probe['checksum']}")
return 0
print(f" profile: {profile_dir}")
print(f" adding: {', '.join(added)}")
save(path, doc, raw)
print(f" checksum: {doc['checksum']} verified on disk")
return 0
def remove(profile_dir, dry_run=False) -> int:
path, raw, doc = load(profile_dir)
plain, bm = find_ours(doc)
if plain is None and bm is None:
print(" nothing to remove")
return 0
removed = []
for key in ROOTS:
root = doc["roots"].get(key)
if not root:
continue
keep = []
for child in root.get("children", []):
if child is plain or child is bm:
removed.append(child.get("name"))
else:
keep.append(child)
root["children"] = keep
if dry_run:
print(f" dry run: would remove {removed}")
return 0
save(path, doc, raw)
print(f" removed: {removed}")
return 0
def status(profile_dir) -> int:
_, _, doc = load(profile_dir)
plain, bm = find_ours(doc)
bar = doc["roots"]["bookmark_bar"]
print(f" profile : {profile_dir}")
print(f" bar : {len(bar.get('children', []))} items")
print(f" Vitals : {plain.get('name') + ' -> ' + plain['url'] if plain else 'not present'}")
print(f" popup : {bm.get('name') if bm else 'not present'}")
stored = doc.get("checksum", "")
good = stored == checksum(doc)
print(f" checksum: {'valid' if good else 'MISMATCH'}")
return 0
def main(argv=None) -> int:
ap = argparse.ArgumentParser(description="Install Vitals into the bookmark bar")
ap.add_argument("--install", action="store_true")
ap.add_argument("--remove", action="store_true")
ap.add_argument("--status", action="store_true")
ap.add_argument("--profile-dir", default=None)
ap.add_argument("--name", default="Vitals")
ap.add_argument("--dry-run", action="store_true")
ap.add_argument("--wait", type=int, default=0,
help="seconds to wait for the browser to exit before giving up")
args = ap.parse_args(argv)
if not (args.install or args.remove or args.status):
ap.print_help()
return 2
profiles = [args.profile_dir] if args.profile_dir else candidate_profiles()
if not profiles:
print("no Chromium-family profile found; pass --profile-dir")
return 1
targets = []
for p in profiles:
if not os.path.isfile(os.path.join(p, "Bookmarks")):
print(f" skip {p}: no Bookmarks file")
continue
waited = 0
while profile_in_use(p) and waited < args.wait:
if waited == 0:
print(f" browser is running on {p}; waiting up to {args.wait}s")
time.sleep(2)
waited += 2
if profile_in_use(p):
print(f" skip {p}: browser holds the profile lock. Close the browser "
f"and re-run, or pass --wait N.")
continue
targets.append(p)
if not targets:
return 1
for p in targets:
if args.status:
status(p)
elif args.remove:
remove(p, args.dry_run)
else:
install(p, args.dry_run, name=args.name)
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -115,6 +115,19 @@ input[type=search]:focus{border-color:#37424d}
button{background:var(--panel);border:1px solid var(--line);color:var(--dim);border-radius:3px; button{background:var(--panel);border:1px solid var(--line);color:var(--dim);border-radius:3px;
padding:4px 9px;font-size:11px;cursor:pointer;font-family:inherit} padding:4px 9px;font-size:11px;cursor:pointer;font-family:inherit}
button:hover{border-color:#37424d;color:var(--fg)} button:hover{border-color:#37424d;color:var(--fg)}
/* --- live-update stability: nothing moves unless its own value moved --- */
.stat b{font-variant-numeric:tabular-nums}
.stat .v{display:inline-block;text-align:right}
.stat.busy .v{min-width:2.2ch}
.stat.cores .v{min-width:4.4ch}
.machine div{transition:width .9s linear}
.legend span{white-space:nowrap;font-variant-numeric:tabular-nums}
.legend span em{font-style:normal;display:inline-block;min-width:3.1em;text-align:right}
.rows{display:flex;flex-direction:column}
.row .bar i{transition:width .9s linear}
.num{font-variant-numeric:tabular-nums}
.stat, .legend span, .tabs .tr{will-change:auto}
</style> </style>
</head> </head>
<body> <body>
@ -126,9 +139,9 @@ button:hover{border-color:#37424d;color:var(--fg)}
</header> </header>
<div class="hero"> <div class="hero">
<div class="stat"><b id="busy">–</b><i>of machine busy</i></div> <div class="stat busy"><b id="busy"><span class="v">–</span><small>%</small></b><i>of machine busy</i></div>
<div class="stat"><b id="cores">–</b><i>cores in use</i></div> <div class="stat cores"><b id="cores"><span class="v">–</span><small id="coresmax"> / –</small></b><i>cores in use</i></div>
<div class="stat"><b id="topname" style="font-size:22px">–</b><i>largest workload</i></div> <div class="stat"><b id="topname" style="font-size:22px">–</b><i id="topsub">largest workload</i></div>
</div> </div>
<div class="machine" id="machine"></div> <div class="machine" id="machine"></div>
@ -174,21 +187,50 @@ function colorFor(key){
let h=0; for(const c of key) h=(h*31+c.charCodeAt(0))>>>0; let h=0; for(const c of key) h=(h*31+c.charCodeAt(0))>>>0;
return colorCache[key]=PALETTE[h%PALETTE.length]; return colorCache[key]=PALETTE[h%PALETTE.length];
} }
const state = {snap:null, open:new Set(), details:{}, sort:"cpu", hideIdle:true, q:""}; const state = {snap:null, open:new Set(), details:{}, sort:"cpu", hideIdle:true, q:"",
rows:new Map(), dets:new Map(), segs:new Map(), leg:new Map(),
rank:new Map(), low:new Map(), lastT:0, lastTabs:"", lastBanner:""};
const ease = {busy:0, busyT:0, cores:0, coresT:0};
const REDUCED = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const elBusy = $("#busy .v"), elCores = $("#cores .v");
/* write only when the value actually changed */
function setText(el,txt){ if(el && el.__t!==txt){ el.textContent=txt; el.__t=txt; } }
function setHTML(el,html){ if(el && el.__h!==html){ el.innerHTML=html; el.__h=html; } }
function setW(el,pct){ const v=pct.toFixed(2)+"%"; if(el && el.__w!==v){ el.style.width=v; el.__w=v; } }
function keyed(parent,map,key,tag,cls){
let el=map.get(key);
if(!el){ el=document.createElement(tag); if(cls) el.className=cls; parent.appendChild(el); map.set(key,el); }
return el;
}
function prune(map,keep){ for(const [k,el] of map){ if(!keep.has(k)){ el.remove(); map.delete(k); } } }
/* one rAF loop eases the hero numbers toward the newest sample instead of snapping */
function loop(){
const step=(cur,tgt)=> {
const d=tgt-cur;
if(REDUCED || Math.abs(d)<0.02) return tgt;
return cur + d*0.16;
};
ease.busy = step(ease.busy, ease.busyT);
ease.cores = step(ease.cores, ease.coresT);
setText(elBusy, String(Math.round(ease.busy)));
setText(elCores, ease.cores.toFixed(2));
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
function fmtAge(h){ function fmtAge(h){
if(h<1) return Math.round(h*60)+"m"; if(h<1) return Math.round(h*60)+"m";
if(h<48) return h.toFixed(1)+"h"; if(h<48) return h.toFixed(1)+"h";
return (h/24).toFixed(1)+"d"; return (h/24).toFixed(1)+"d";
} }
function spark(series, color){ function sparkPath(series){
const w=92,h=22; const w=92,h=22;
if(!series||series.length<2) return `<svg class="spark" viewBox="0 0 ${w} ${h}"></svg>`; if(!series||series.length<2) return "";
const max=Math.max(...series,0.1); const max=Math.max(...series,0.1);
const pts=series.map((v,i)=>[i/(series.length-1)*w, h-2-(v/max)*(h-4)]); return series.map((v,i)=>(i?"L":"M")+(i/(series.length-1)*w).toFixed(1)+" "+
const d=pts.map((p,i)=>(i?"L":"M")+p[0].toFixed(1)+" "+p[1].toFixed(1)).join(" "); (h-2-(v/max)*(h-4)).toFixed(1)).join(" ");
return `<svg class="spark" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none">
<path d="${d}" fill="none" stroke="${color}" stroke-width="1.4" stroke-linejoin="round"/></svg>`;
} }
function flagChips(w){ function flagChips(w){
return (w.flags||[]).map(f=>`<span class="chip">${f}</span>`).join(""); return (w.flags||[]).map(f=>`<span class="chip">${f}</span>`).join("");
@ -197,37 +239,50 @@ function flagChips(w){
function render(){ function render(){
const s=state.snap; if(!s) return; const s=state.snap; if(!s) return;
const cores=s.cores; const cores=s.cores;
$("#meta").textContent = `sampled ${new Date(s.t*1000).toLocaleTimeString()} · ${s.sample_ms}ms per sample`; setText($("#meta"), `live · ${new Date(s.t*1000).toLocaleTimeString()} · ${s.sample_ms}ms sample`);
if(s.host) $("#hostlabel").textContent = "workload map · " + s.host; if(s.host) setText($("#hostlabel"), "workload map · " + s.host);
$("#busy").innerHTML = `${s.total_pct}<small>%</small>`; ease.busyT = s.total_pct; ease.coresT = s.total_busy;
$("#cores").innerHTML = `${s.total_busy.toFixed(2)}<small> / ${cores}</small>`; setText($("#coresmax"), " / " + cores);
const top=s.workloads[0]; const top=s.workloads[0];
$("#topname").textContent = top? top.name : "—"; setText($("#topname"), top? top.name : "—");
$("#topname").parentElement.querySelector("i").textContent = setText($("#topsub"), top ? `largest workload · ${top.cpu.toFixed(2)} cores` : "largest workload");
top ? `largest workload · ${top.cpu.toFixed(2)} cores` : "largest workload"; setText($("#selfcost"), `${s.self_cost.toFixed(2)} cores`);
$("#selfcost").textContent = `${s.self_cost.toFixed(2)} cores`;
// machine bar: top 8 workloads + rest, then free // machine bar: top 8 workloads + rest. Segments are keyed and reordered by
// flex order, so widths actually animate instead of being recreated each tick.
const top8=s.workloads.slice(0,8); const top8=s.workloads.slice(0,8);
const used=top8.reduce((a,w)=>a+w.cpu,0); const used=top8.reduce((a,w)=>a+w.cpu,0);
const rest=Math.max(0, s.total_busy-used); const rest=Math.max(0, s.total_busy-used);
let bar="", leg=""; const segs=[];
top8.forEach(w=>{ top8.forEach((w,i)=>segs.push({key:w.key,name:w.name,cpu:w.cpu,color:colorFor(w.key)}));
bar+=`<div style="width:${(w.cpu/cores*100).toFixed(2)}%;background:${colorFor(w.key)}"></div>`; if(rest>0.02) segs.push({key:"__rest",name:"rest",cpu:rest,color:"#3a434c"});
leg+=`<span><i style="background:${colorFor(w.key)}"></i>${w.name} ${w.cpu.toFixed(2)}</span>`; const keepSeg=new Set();
}); segs.forEach((sg,i)=>{
if(rest>0.02){ keepSeg.add(sg.key);
bar+=`<div style="width:${(rest/cores*100).toFixed(2)}%;background:#3a434c"></div>`; const el=keyed($("#machine"),state.segs,sg.key,"div");
leg+=`<span><i style="background:#3a434c"></i>rest ${rest.toFixed(2)}</span>`; setW(el, Math.min(100, sg.cpu/cores*100));
if(el.__bg!==sg.color){ el.style.background=sg.color; el.__bg=sg.color; }
el.style.order=i;
const lg=keyed($("#legend"),state.leg,sg.key,"span");
lg.style.order=i;
const sig=sg.name+"|"+sg.cpu.toFixed(2)+"|"+sg.color;
if(lg.__s!==sig){
lg.innerHTML=`<i style="background:${sg.color}"></i>${esc(sg.name)} <em>${sg.cpu.toFixed(2)}</em>`;
lg.__s=sig;
} }
$("#machine").innerHTML=bar; $("#legend").innerHTML=leg; });
prune(state.segs,keepSeg); prune(state.leg,keepSeg);
$("#flags").innerHTML = (s.flags||[]).map(f=>{ setHTML($("#flags"), (s.flags||[]).map(f=>{
const cls = f.flag==="leaked" ? "hot" : "warn"; const cls = f.flag==="leaked" ? "hot" : "warn";
return `<span class="flag ${cls}">${f.name}: ${f.flag}${f.flag==="leaked"?` · ${f.cpu.toFixed(2)} cores`:""}</span>`; return `<span class="flag ${cls}">${f.name}: ${f.flag}${f.flag==="leaked"?` · ${f.cpu.toFixed(2)} cores`:""}</span>`;
}).join(""); }).join(""));
$("#flags").style.display = (s.flags||[]).length ? "flex" : "none";
// browser tabs // browser tabs: rebuilt only when the tab data itself changes
const tabSig=JSON.stringify(s.tabs||[]);
if(state.lastTabs!==tabSig){
state.lastTabs=tabSig;
const tabRows = (s.tabs||[]).map(t=>{ const tabRows = (s.tabs||[]).map(t=>{
const meta = [shortUrl(t.url)]; const meta = [shortUrl(t.url)];
if(t.port) meta.push("port "+t.port); if(t.port) meta.push("port "+t.port);
@ -240,53 +295,130 @@ function render(){
<div class="num dim">${t.heap_mb} MB</div> <div class="num dim">${t.heap_mb} MB</div>
</div>`;}).join(""); </div>`;}).join("");
$("#tabs").innerHTML = tabRows || `<div class="empty">no tab data yet</div>`; $("#tabs").innerHTML = tabRows || `<div class="empty">no tab data yet</div>`;
}
const brave = (s.workloads||[]).find(w=>/brave/i.test(w.name) && w.cpu > 0.05); const brave = (s.workloads||[]).find(w=>/brave/i.test(w.name) && w.cpu > 0.05);
$("#banner").innerHTML = const banner = (!s.brave_cdp && brave) ? `<div class="banner">
(!s.brave_cdp && brave) ? `<div class="banner">
<b>Brave is not speaking CDP, so its tabs are invisible.</b> Restart it once with the debug port and <b>Brave is not speaking CDP, so its tabs are invisible.</b> Restart it once with the debug port and
every tab shows up here with its own page-work number, title, and URL. Tabs restore from session. every tab shows up here with its own page-work number, title, and URL. Tabs restore from session.
<br><code>flatpak kill com.brave.Browser && flatpak run com.brave.Browser --remote-debugging-port=9222</code> <br><code>flatpak kill com.brave.Browser && flatpak run com.brave.Browser --remote-debugging-port=9222</code>
</div>` : ""; </div>` : "";
if(state.lastBanner!==banner){ state.lastBanner=banner; $("#banner").innerHTML=banner; }
// rows // rows: keyed DOM, hysteresis on appearance, quantised sort so rows do not
// swap places over a hundredth of a core
let list=s.workloads.slice(); let list=s.workloads.slice();
if(state.hideIdle) list=list.filter(w=>w.cpu>=0.05); const q=state.q.toLowerCase();
if(state.q) list=list.filter(w=>(w.name+" "+w.key).toLowerCase().includes(state.q.toLowerCase())); if(q) list=list.filter(w=>(w.name+" "+w.key).toLowerCase().includes(q));
const by=state.sort;
list.sort((a,b)=> by==="cpu"? b.cpu-a.cpu : by==="ram"? b.rss_mb-a.rss_mb : b.age_h-a.age_h);
const maxCpu=Math.max(0.2,...list.map(w=>w.cpu));
let html=""; const visible=[];
list.forEach(w=>{ for(const w of list){
const c=colorFor(w.key); if(!state.hideIdle){ state.low.delete(w.key); visible.push(w); continue; }
const open=state.open.has(w.key); if(w.cpu>=0.05){ state.low.delete(w.key); visible.push(w); continue; }
const d=state.details[w.key]; // already on screen and still above 0.02: keep it for two more samples
html+=`<div class="row ${open?"open":""}" data-key="${w.key}"> const n=(state.low.get(w.key)||0);
<div class="nm"><span class="dot" style="background:${c}"></span> if(state.rows.has(w.key) && w.cpu>=0.02 && n<2){ state.low.set(w.key,n+1); visible.push(w); }
<div class="txt"><div class="t1">${w.name}${flagChips(w)}</div> }
<div class="t2">${meta2(w)}</div></div></div>
<div class="bar"><i style="width:${(w.cpu/maxCpu*100).toFixed(1)}%;background:${c}"></i></div> const by=state.sort;
<div class="num">${w.pct.toFixed(1)}%</div> const qz=x=>Math.round(x*10)/10;
<div class="num mono">${w.cpu.toFixed(2)}</div> const prevRank=state.rank;
<div class="num dim">${fmtRam(w.rss_mb)}</div> visible.sort((a,b)=>{
<div class="num dim">${fmtAge(w.age_h)}</div> let d;
<div>${spark(w.series || (d&&d.series), c)}</div> if(by==="cpu") d=qz(b.cpu)-qz(a.cpu);
<div class="chev">${open?"▲":"▼"}</div> else if(by==="ram") d=qz(b.rss_mb/100)-qz(a.rss_mb/100);
</div>`; else d=qz(b.age_h*10)-qz(a.age_h*10);
if(open){ if(Math.abs(d)>0.0001) return d;
html+=`<div class="detail" data-detail="${w.key}">${detailHTML(w,d)}</div>`; return (prevRank.get(a.key)??1e9)-(prevRank.get(b.key)??1e9);
});
state.rank=new Map(visible.map((w,i)=>[w.key,i]));
const maxCpu=Math.max(0.2,...visible.map(w=>w.cpu));
const host=$("#rows");
const keepRows=new Set();
visible.forEach((w,i)=>{
keepRows.add(w.key);
let el=state.rows.get(w.key);
if(!el){ el=buildRow(w.key); host.appendChild(el); state.rows.set(w.key,el); }
el.style.order = i*2;
updateRow(el,w,maxCpu);
if(state.open.has(w.key)){
let det=state.dets.get(w.key);
if(!det){ det=document.createElement("div"); det.className="detail";
det.dataset.detail=w.key; host.appendChild(det); state.dets.set(w.key,det); }
det.style.order = i*2+1;
updateDetail(det,w,state.details[w.key]);
} }
}); });
$("#rows").innerHTML = html || `<div class="empty">nothing matches</div>`;
document.querySelectorAll(".row").forEach(el=>{ for(const [k,el] of state.rows){
if(!keepRows.has(k)){ el.remove(); state.rows.delete(k); state.rank.delete(k); }
}
for(const [k,el] of state.dets){
if(!keepRows.has(k) || !state.open.has(k)){ el.remove(); state.dets.delete(k); }
}
let empty=host.querySelector(".empty");
if(!visible.length){
if(!empty){ empty=document.createElement("div"); empty.className="empty"; host.appendChild(empty); }
empty.textContent = q ? "nothing matches" : "nothing above the idle line";
} else if(empty){ empty.remove(); }
}
function buildRow(key){
const el=document.createElement("div");
el.className="row"; el.dataset.key=key;
el.innerHTML=`<div class="nm"><span class="dot"></span>
<div class="txt"><div class="t1"></div><div class="t2"></div></div></div>
<div class="bar"><i></i></div>
<div class="num pct"></div>
<div class="num mono ccores"></div>
<div class="num dim ram"></div>
<div class="num dim age"></div>
<div class="sp"><svg class="spark" viewBox="0 0 92 22" preserveAspectRatio="none">
<path fill="none" stroke="#666" stroke-width="1.4" stroke-linejoin="round"/></svg></div>
<div class="chev">▼</div>`;
el.onclick=()=>{ el.onclick=()=>{
const k=el.dataset.key; const k=el.dataset.key;
if(state.open.has(k)){ state.open.delete(k); } if(state.open.has(k)) state.open.delete(k);
else { state.open.add(k); loadDetail(k); } else { state.open.add(k); loadDetail(k); }
render(); render();
}; };
}); return el;
}
function updateRow(el,w,maxCpu){
const c=colorFor(w.key);
const dot=el.querySelector(".dot");
if(dot.__c!==c){ dot.style.background=c; dot.__c=c; }
const t1=el.querySelector(".t1");
const sig=w.name+"|"+(w.flags||[]).join(",");
if(t1.__s!==sig){ t1.innerHTML=esc(w.name)+flagChips(w); t1.__s=sig; }
setText(el.querySelector(".t2"), meta2(w));
const bar=el.querySelector(".bar i");
if(bar.__c!==c){ bar.style.background=c; bar.__c=c; }
setW(bar, Math.max(0,Math.min(100, w.cpu/maxCpu*100)));
setText(el.querySelector(".pct"), w.pct.toFixed(1)+"%");
setText(el.querySelector(".ccores"), w.cpu.toFixed(2));
setText(el.querySelector(".ram"), fmtRam(w.rss_mb));
setText(el.querySelector(".age"), fmtAge(w.age_h));
const d=state.details[w.key];
const path=el.querySelector(".sp path");
const pd=sparkPath(w.series || (d && d.series));
if(path.__d!==pd){ path.setAttribute("d", pd); path.__d=pd; }
if(path.__c!==c){ path.setAttribute("stroke", c); path.__c=c; }
const open=state.open.has(w.key);
if(el.__open!==open){
el.classList.toggle("open", open);
el.querySelector(".chev").textContent = open ? "▲" : "▼";
el.__open=open;
}
}
function updateDetail(det,w,d){
const sig=(d? JSON.stringify(d) : "pending")+w.name;
if(det.__s===sig) return;
det.__s=sig;
det.innerHTML=detailHTML(w,d);
} }
function meta2(w){ function meta2(w){
@ -363,12 +495,17 @@ async function loadDetail(key){
} }
async function tick(){ async function tick(){
try{ try{
const r=await fetch("/api/snapshot"); const r=await fetch("/api/snapshot",{cache:"no-store"});
state.snap=await r.json(); const s=await r.json();
// top up series for expanded rows // The sampler produces a frame every 2s. Poll faster than that so a new
for(const k of state.open) if(state.details[k]) loadDetail(k); // frame is picked up within ~0.7s, and ignore repeats so the DOM is never
// rewritten with numbers the user already sees.
if(s.t===state.lastT) return;
state.lastT=s.t;
state.snap=s;
render(); render();
}catch(e){ $("#meta").textContent="server unreachable"; } for(const k of state.open) if(state.details[k]) loadDetail(k);
}catch(e){ setText($("#meta"),"server unreachable"); }
} }
$("#q").oninput=e=>{state.q=e.target.value;render();}; $("#q").oninput=e=>{state.q=e.target.value;render();};
$("#sortbtn").onclick=e=>{ $("#sortbtn").onclick=e=>{
@ -381,7 +518,8 @@ $("#idlebtn").onclick=e=>{
e.target.textContent = state.hideIdle?"show idle":"hide idle"; e.target.textContent = state.hideIdle?"show idle":"hide idle";
render(); render();
}; };
tick(); setInterval(tick, 2000); tick();
setInterval(tick, 700);
</script> </script>
</body> </body>
</html> </html>

View file

@ -25,3 +25,18 @@ else
echo "Vitals failed to start. Check: journalctl --user -u vitals -n 40" echo "Vitals failed to start. Check: journalctl --user -u vitals -n 40"
exit 1 exit 1
fi fi
# --- bookmark bar -------------------------------------------------------
# A tool you have to remember the URL of does not get used. Put it behind the
# bar, where a bookmark belongs. The browser must be closed while we write,
# because Chromium keeps the bookmark model in memory and overwrites the file
# on its next save. Running install.sh while the browser is up is not a
# failure: it prints the one command to run afterwards.
if [ "${1:-}" = "--no-bookmark" ]; then
echo
echo "Bookmark skipped. Add it later with:"
echo " python3 $HERE/bookmark.py --install"
else
echo
python3 "$HERE/bookmark.py" --install ${VITALS_BOOKMARK_WAIT:+--wait "$VITALS_BOOKMARK_WAIT"} || true
fi

115
tests/test_bookmark.py Normal file
View file

@ -0,0 +1,115 @@
"""Unit tests for the bookmark installer. No browser, no real profile needed."""
import json
import os
import shutil
import sys
import tempfile
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import bookmark # noqa: E402
def minimal_doc():
"""The smallest document Chromium would write: three roots, no children."""
return {
"checksum": "",
"roots": {
"bookmark_bar": {"children": [], "date_added": "0", "date_last_used": "0",
"date_modified": "0", "guid": "0" * 32, "id": "1",
"name": "Bookmarks bar", "type": "folder"},
"other": {"children": [], "date_added": "0", "date_last_used": "0",
"date_modified": "0", "guid": "1" * 32, "id": "2",
"name": "Other bookmarks", "type": "folder"},
"synced": {"children": [], "date_added": "0", "date_last_used": "0",
"date_modified": "0", "guid": "2" * 32, "id": "3",
"name": "Mobile bookmarks", "type": "folder"},
},
"sync_metadata": "",
"version": 1,
}
class TestChecksum(unittest.TestCase):
def test_checksum_is_stable_and_order_sensitive(self):
doc = minimal_doc()
base = bookmark.checksum(doc)
self.assertEqual(base, bookmark.checksum(minimal_doc()))
doc["roots"]["bookmark_bar"]["children"].append(
{"id": "4", "name": "x", "type": "url", "url": "http://a/"})
self.assertNotEqual(base, bookmark.checksum(doc))
def test_title_bytes_are_utf16(self):
# A folder named "A" and a folder named "\u0041" are the same string;
# but a two byte title must not hash like its UTF-8 encoding.
a = minimal_doc()
a["roots"]["other"]["name"] = "A"
b = minimal_doc()
b["roots"]["other"]["name"] = "A\u0000"
self.assertNotEqual(bookmark.checksum(a), bookmark.checksum(b))
class TestInstall(unittest.TestCase):
def setUp(self):
self.dir = tempfile.mkdtemp(prefix="vitals-bm-")
self.profile = os.path.join(self.dir, "Default")
os.makedirs(self.profile)
doc = minimal_doc()
doc["checksum"] = bookmark.checksum(doc)
with open(os.path.join(self.profile, "Bookmarks"), "w", encoding="utf8") as f:
json.dump(doc, f, indent=3, sort_keys=True)
def tearDown(self):
shutil.rmtree(self.dir, ignore_errors=True)
def read(self):
with open(os.path.join(self.profile, "Bookmarks"), encoding="utf8") as f:
return json.load(f)
def test_install_adds_both_entries_and_valid_checksum(self):
bookmark.install(self.profile)
doc = self.read()
self.assertEqual(doc["checksum"], bookmark.checksum(doc))
urls = [c["url"] for c in doc["roots"]["bookmark_bar"]["children"]]
self.assertIn(bookmark.VITALS_URL, urls)
self.assertTrue(any(u.startswith("javascript:") for u in urls))
def test_install_is_idempotent(self):
bookmark.install(self.profile)
bookmark.install(self.profile)
bar = self.read()["roots"]["bookmark_bar"]["children"]
self.assertEqual(len(bar), 2)
def test_ids_do_not_collide_with_existing_nodes(self):
doc = self.read()
doc["roots"]["bookmark_bar"]["children"].append(
{"id": "99", "name": "existing", "type": "url", "url": "http://b/"})
doc["checksum"] = bookmark.checksum(doc)
with open(os.path.join(self.profile, "Bookmarks"), "w", encoding="utf8") as f:
json.dump(doc, f, indent=3, sort_keys=True)
bookmark.install(self.profile)
ids = [c["id"] for c in self.read()["roots"]["bookmark_bar"]["children"]]
self.assertEqual(len(ids), len(set(ids)))
def test_remove_takes_only_its_own_entries(self):
doc = self.read()
doc["roots"]["bookmark_bar"]["children"].append(
{"id": "50", "name": "keep me", "type": "url", "url": "http://keep/"})
doc["checksum"] = bookmark.checksum(doc)
with open(os.path.join(self.profile, "Bookmarks"), "w", encoding="utf8") as f:
json.dump(doc, f, indent=3, sort_keys=True)
bookmark.install(self.profile)
bookmark.remove(self.profile)
bar = self.read()["roots"]["bookmark_bar"]["children"]
self.assertEqual([c["name"] for c in bar], ["keep me"])
def test_backup_is_written_before_touching_the_file(self):
bookmark.install(self.profile)
backups = [f for f in os.listdir(self.profile) if f.startswith("Bookmarks.pre-vitals-")]
self.assertEqual(len(backups), 1)
if __name__ == "__main__":
unittest.main()