Update btop

This commit is contained in:
Raven Scott
2026-04-05 17:07:57 -04:00
parent 7c6b77ed62
commit f3c8ef1fc5
33 changed files with 9771 additions and 1090 deletions
+7 -1
View File
@@ -2,4 +2,10 @@
## Unreleased
- **`baretop` / `btop`**: Scrollable overview tab with pinned HUD (activity sparklines, session summary, delegate histogram); compact **`metrics_live`** panels (replication, kernel counters with delta mode, warm cache, IPC, boot budget, swarm lifecycle, initd readiness, worker hints, protomux collaboration); capped flatten and delegate inflight table; health score breakdown (**`BARE_TOP_HEALTH_DETAIL`**); optional lite snapshot batch (**`BARE_TOP_SNAPSHOT_LITE`**); export metadata + **`BARE_TOP_EXPORT_REDACT`**; process tab **TIME** column, **F6**/**S** sort, **V** tree view, state-based row color, optional mouse wheel/click; many layout/telemetry env toggles documented in **`src/baretop.js`** and **`man baretop`**.
- **`baretop` / `btop`**: **`BARE_TOP_FETCH_EWMA=1`** lowers parallel **`/proc`** batch concurrency when the previous fetch-time EWMA is high; **`BARE_TOP_SCROLL_REGION=1`** sets an experimental DECSTBM region around the process list (reset each frame). Sample fixture **`baretop-sample-metrics.json`** includes **`netSummaryNestedExample`** for nested net UI tests.
- **`baretop` / `btop`**: Network tab formats nested **`net_summary.json`** ( **`replicationQueue`**, **`peerFirewallStats`**, **`bsdSocketGuestBridge`** ) without **`[object Object]`**; optional **`BARE_TOP_NET_FILTER`** applies **`/`** on that tab; wide two-column merge when **`BARE_TOP_LAYOUT=even`** and cols ≥ 100; net byte-delta spark from iface counters. Overview gains **nethealth**, **diskstats**, **syscalls** summary, **delegate_red**, **security_posture**, **boot_budget_summary**, **peer health**, **capabilities diff**, **extensions**, **git** deltas (with **`d`** + prev snap), larger kernel counter sample, **IPC** backpressure line from proc JSON, **session wall** spark, **worker/sandbox** hints, **readErrPaths** listing, **MemUsed** bar + swap line + logical CPU count in HUD; process columns **NI**/**PRI**/**CPU%** when present, **`BARE_TOP_CMD_ELLIPSIS_MIDDLE`**, extended sort keys, tree ancestor dimming + ASCII **`+--`**, **x** toggles initd raw JSON, features flattened table, catalog version line, pear pack depth **8**, host-tab worker/sandbox one-liners, mouse click-scroll on list tabs **29**. Snapshot batch adds **`security_posture.json`**; **`readErrPaths`** on failed **`readFile`** paths.
- **`baretop` / `btop`**: Health score no longer penalizes operational **`replicationLive.stallHint`** values (**`no_peers`**, **`length_unavailable`**, **`ok`**); delegate inflight penalties use active (count>0) kinds with a combined cap; optional **`replicationStall: true`** forces the stall penalty. **`bareOsReadBareTopSnapshot`** may return **`metricsLiveText`** (same payload as **`metrics_live.json`**) to avoid an extra read when the booter builds both. **`BARE_TOP_INCREMENTAL=2`** experimental CSI line-diff redraws; **`BARE_TOP_LAYOUT_AUTO`**, **F2** setup overlay, **F3**/**F4** process row jump, **F10** quit; overview **limits** / **p2p** sections, network tab interface sort, tab intro lines, filter-match row highlight, **`layoutVersion` `baretop-ui-3`**. Default **`BARE_TOP_FETCH_CONCURRENCY`** **8**. QA script **`scripts/qa-baretop-health.mjs`**.
- **`baretop` / `btop`** (prior): Scrollable overview tab with pinned HUD (activity sparklines, session summary, delegate histogram); compact **`metrics_live`** panels (replication, kernel counters with delta mode, warm cache, IPC, boot budget, swarm lifecycle, initd readiness, worker hints, protomux collaboration); capped flatten and delegate inflight table; health score breakdown (**`BARE_TOP_HEALTH_DETAIL`**); optional lite snapshot batch (**`BARE_TOP_SNAPSHOT_LITE`**); export metadata + **`BARE_TOP_EXPORT_REDACT`**; process tab **TIME** column, **F6**/**S** sort, **V** tree view, state-based row color, optional mouse wheel/click; many layout/telemetry env toggles documented in **`src/baretop.js`** and **`man baretop`**.
+1 -1
View File
@@ -39,7 +39,7 @@ Or `node packages/bare-os-coreutils/build.mjs`.
**`edit`** is a full-screen TTY buffer editor (syntax highlighting, search, save). **`nano`** is built from the same **`src/edit.js`** with the same **`lib/edit-*.js`** preamble; **`/bin/nano`** exists for familiarity, and the stock shell alias **`nano``edit`** routes **`nano`** to that utility (see **`packages/bare-os-booter/lib/shell.js`**). Both require a real TTY (**`stdout.isTTY`**).
**`baretop`** is the stock **`top`** alias target: a multi-tab TTY dashboard over **`/proc/bare_os/*`** (session metrics, Pear, replication, initd, scrollable overview, process tree/sort, optional mouse, …). **`btop`** is the same bundle as **`/bin/baretop`** (short name); the stock shell alias **`btop``baretop`** matches **`nano``edit`**. The booter may expose **`ctx.bareOsReadBareTopSnapshot`** (optional **`{ lite: true }`**) to batch-read the same mirrors **`baretop`** would **`vfs.readFile`** individually. Rebuild **`kernel/bin/baretop`** and **`kernel/bin/btop`** with **`node packages/bare-os-coreutils/build.mjs`** after editing **`lib/baretop-snapshot.js`**, **`lib/baretop-ui-helpers.js`**, or **`lib/baretop-tui.js`**.
**`baretop`** is the stock **`top`** alias target: a multi-tab TTY dashboard over **`/proc/bare_os/*`** (session metrics, Pear, replication, initd, scrollable overview, process tree/sort, optional mouse, …). **`btop`** is the same bundle as **`/bin/baretop`** (short name); the stock shell alias **`btop``baretop`** matches **`nano``edit`**. The booter may expose **`ctx.bareOsReadBareTopSnapshot`** (optional **`{ lite: true }`**) to batch-read the same mirrors **`baretop`** would **`vfs.readFile`** individually; the return value can include **`metricsLiveText`** so **`metrics_live.json`** need not be read twice. **`BARE_TOP_INCREMENTAL=2`** enables experimental line-diff redraws; **`BARE_TOP_LAYOUT_AUTO=1`** picks a wide split on large terminals. Rebuild **`kernel/bin/baretop`** and **`kernel/bin/btop`** with **`node packages/bare-os-coreutils/build.mjs`** after editing **`lib/baretop-snapshot.js`**, **`lib/baretop-ui-helpers.js`**, or **`lib/baretop-tui.js`**.
**`ls`** prepends **[`bare-os-lscolors`](../bare-os-lscolors/bare-os-lscolors.js)** for **`LS_COLORS`** / dircolors parsing. **`dircolors`** and **`theme`** integrate with the booters **`bare-os-theme-presets.js`** (see [docs/themes/README.md](../../docs/themes/README.md)).
@@ -1,5 +1,8 @@
/** Fetch /proc and ctx snapshots for /bin/baretop (preamble; no import). */
/** EWMA of last fetch wall time (ms); used when `BARE_TOP_FETCH_EWMA=1`. */
var bareTopFetchEwmaMs = -1
/**
* Keep in sync with `bareOsReadBareTopSnapshot` path list in packages/bare-os-booter/index.js
* @type {readonly [string, string][]}
@@ -49,7 +52,8 @@ var BARE_TOP_SNAPSHOT_PROC_ENTRIES = [
['processTable', '/proc/bare_os/process_table.json'],
['syscalls', '/proc/bare_os/syscalls.json'],
['metricsProm', '/proc/bare_os/metrics.prom'],
['protomuxWire', '/proc/bare_os/protomux.json']
['protomuxWire', '/proc/bare_os/protomux.json'],
['securityPosture', '/proc/bare_os/security_posture.json']
]
/** Subset for BARE_TOP_SNAPSHOT_LITE=1 — keep keys subset of full list above. */
@@ -115,15 +119,32 @@ async function bareTopReadProc(ctx, path) {
* @param {number} concurrency
* @returns {Promise<Record<string, string>>}
*/
async function bareTopReadProcBatch(ctx, entries, concurrency) {
/**
* @param {Record<string, unknown>} ctx
* @param {readonly [string, string][]} entries
* @param {number} concurrency
* @param {string[] | null} [errPaths] push failed paths (optional)
*/
async function bareTopReadProcBatch(ctx, entries, concurrency, errPaths) {
/** @type {Record<string, string>} */
const out = {}
const vfs = ctx.vfs
const conc = Math.max(1, Math.min(16, concurrency | 0) || 6)
for (let i = 0; i < entries.length; i += conc) {
const slice = entries.slice(i, i + conc)
await Promise.all(
slice.map(async ([key, path]) => {
out[key] = await bareTopReadProc(ctx, path)
if (!vfs || typeof vfs.readFile !== 'function') {
out[key] = ''
return
}
try {
const buf = await vfs.readFile(path)
out[key] = bareTopBufToString(buf, ctx.b4a)
} catch {
out[key] = ''
if (errPaths) errPaths.push(path)
}
})
)
}
@@ -172,9 +193,16 @@ function bareTopHealthScore(m, peers, readErr) {
}
/**
* Subtractive score from 100: healthy sessions read ~95100; issues pull it down.
* Weights: read/metrics gaps, delegate inflight keys + sum, replication stall,
* boot budgets, initd failures, peer sanity / mismatch.
* Subtractive score from 100 (penalty table):
* - read_err: -28
* - no_metrics: -22
* - delegate_inflight: capped combined from active keys (count>0) + total inflight sum; max -12
* - replication stall: -20 when stallHint is a fault token OR replicationStall===true;
* benign hints no_peers | length_unavailable | ok do not penalize (operational, not fault)
* - boot_cold exceeded: -12; boot_stdlib exceeded: -12
* - initd_readiness.failed: up to -18; kernel initd.unit_failed_final: up to -10
* - peer_mismatch (replication.peerCount vs metrics peers): -8 when both finite and |Δ|>2
* - peers_invalid (peers<0): -5
* @param {Record<string, unknown> | null} m
* @param {number} peers
* @param {string | null | undefined} readErr
@@ -205,20 +233,28 @@ function bareTopHealthScoreDetail(m, peers, readErr) {
if (delI != null && typeof delI === 'object') {
const o = /** @type {Record<string, unknown>} */ (delI)
const keys = Object.keys(o)
const n = keys.length
const penK = Math.min(14, n * 2)
if (penK > 0) {
score -= penK
parts.push('inflight_k_' + n + '=-' + penK)
}
let nActive = 0
let sum = 0
for (let i = 0; i < keys.length; i++) {
sum += Math.max(0, Number(o[keys[i]]) || 0)
const v = Math.max(0, Number(o[keys[i]]) || 0)
sum += v
if (v > 0) nActive++
}
const penS = Math.min(28, sum * 3)
if (penS > 0) {
score -= penS
parts.push('inflight_sum_' + sum + '=-' + penS)
const penK = Math.min(10, nActive * 2)
const penS = Math.min(20, sum * 2)
const inflightCap = 12
const inflightTotal = Math.min(inflightCap, penK + penS)
if (inflightTotal > 0) {
score -= inflightTotal
parts.push(
'inflight_-' +
inflightTotal +
'(activeKinds=' +
nActive +
',sum=' +
sum +
')'
)
}
}
@@ -226,21 +262,31 @@ function bareTopHealthScoreDetail(m, peers, readErr) {
live.replicationLive && typeof live.replicationLive === 'object'
? /** @type {Record<string, unknown>} */ (live.replicationLive)
: null
const stallExplicit =
rep && rep.replicationStall === true ? true : false
const hintRaw = rep && rep.stallHint
if (hintRaw != null) {
const benignStallHints = new Set([
'',
'ok',
'no_peers',
'length_unavailable',
'0',
'false',
'none',
'off',
'null',
'undefined'
])
if (stallExplicit) {
const pen = 20
score -= pen
parts.push('replication_stall_flag=-' + pen)
} else if (hintRaw != null) {
const hs = String(hintRaw).trim().toLowerCase()
if (
hs &&
hs !== '0' &&
hs !== 'false' &&
hs !== 'none' &&
hs !== 'off' &&
hs !== 'null' &&
hs !== 'undefined'
) {
if (hs && !benignStallHints.has(hs)) {
const pen = 20
score -= pen
parts.push('stall=-' + pen)
parts.push('stall_hint=-' + pen + '(' + hs + ')')
}
}
@@ -284,7 +330,14 @@ function bareTopHealthScoreDetail(m, peers, readErr) {
}
}
if (rep && typeof rep.peerCount === 'number' && Number.isFinite(peers)) {
if (
rep &&
typeof rep.peerCount === 'number' &&
Number.isFinite(rep.peerCount) &&
rep.peerCount >= 0 &&
Number.isFinite(peers) &&
peers >= 0
) {
if (Math.abs(rep.peerCount - peers) > 2) {
score -= 8
parts.push('peer_mismatch=-8')
@@ -341,8 +394,10 @@ async function bareTopFetchSnapshot(ctx) {
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: {}
const concRaw = parseInt(env.BARE_TOP_FETCH_CONCURRENCY || '6', 10)
const concurrency = Number.isFinite(concRaw) ? concRaw : 6
const concRaw = parseInt(env.BARE_TOP_FETCH_CONCURRENCY || '8', 10)
const concurrency = Number.isFinite(concRaw) ? concRaw : 8
const fetchEwmaOn =
env.BARE_TOP_FETCH_EWMA === '1' || env.BARE_TOP_FETCH_EWMA === 'true'
const snapshotLite =
env.BARE_TOP_SNAPSHOT_LITE === '1' ||
env.BARE_TOP_SNAPSHOT_LITE === 'true'
@@ -353,6 +408,8 @@ async function bareTopFetchSnapshot(ctx) {
/** @type {Record<string, string>} */
let fileTexts = {}
let fastAtMs = 0
/** @type {Record<string, unknown> | null} */
let metricsLiveFromSnap = null
try {
if (typeof ctx.bareOsReadBareTopSnapshot === 'function') {
@@ -370,20 +427,41 @@ async function bareTopFetchSnapshot(ctx) {
}
const am = o.atMs
if (typeof am === 'number' && Number.isFinite(am)) fastAtMs = am
const mlRaw = o.metricsLiveText
if (typeof mlRaw === 'string' && mlRaw.trim()) {
const mp = bareTopJsonParse(mlRaw)
if (mp && typeof mp === 'object') {
metricsLiveFromSnap = /** @type {Record<string, unknown>} */ (mp)
}
}
}
}
} catch (e) {
readErr = (e && /** @type {{ message?: string }} */ (e).message) || String(e)
}
/** @type {string[]} */
const readErrPaths = []
if (Object.keys(fileTexts).length === 0) {
fileTexts = await bareTopReadProcBatch(ctx, procEntryList, concurrency)
let useConc = concurrency
if (fetchEwmaOn && bareTopFetchEwmaMs >= 0) {
const e = bareTopFetchEwmaMs
if (e > 900) useConc = Math.max(2, concurrency - 3)
else if (e > 500) useConc = Math.max(2, concurrency - 2)
else if (e > 280) useConc = Math.max(2, concurrency - 1)
}
fileTexts = await bareTopReadProcBatch(
ctx,
procEntryList,
useConc,
readErrPaths
)
}
const atMs = fastAtMs || Date.now()
/** @type {Record<string, unknown> | null} */
let metricsLive = null
let metricsLive = metricsLiveFromSnap
/** @type {Record<string, unknown> | null} */
let resources = null
/** @type {Record<string, unknown> | null} */
@@ -484,14 +562,31 @@ async function bareTopFetchSnapshot(ctx) {
const loadavgLine = (await bareTopReadProc(ctx, '/proc/loadavg')).split('\n')[0]?.trim() || ''
let cpuLine = ''
let cpuCoreCount = 0
const cpuRaw = await bareTopReadProc(ctx, '/proc/cpuinfo')
for (const line of cpuRaw.split('\n')) {
if (line.startsWith('model name') || line.startsWith('Model')) {
cpuLine = line.replace(/^[^:]+:\s*/, '').trim().slice(0, 72)
break
}
const L = line.trim().toLowerCase()
if (L.startsWith('processor') && L.includes(':')) cpuCoreCount++
}
if (!cpuCoreCount) {
const m = cpuRaw.match(/^processor\s*:/gim)
if (m) cpuCoreCount = m.length
}
let swapinfoLine = ''
for (const line of memRaw.split('\n')) {
const L = line.trim()
if (L.startsWith('SwapTotal:') || L.startsWith('SwapFree:')) {
swapinfoLine = swapinfoLine ? swapinfoLine + ' ' + L : L
}
}
const diskstatsRaw = await bareTopReadProc(ctx, '/proc/diskstats')
const diskstatsLine = diskstatsRaw.split('\n')[0]?.trim() || ''
const hostOs = bareTopParsedFile(fileTexts, 'hostOs')
const metaAt =
metricsLive && typeof metricsLive.atMs === 'number' ? metricsLive.atMs : atMs
@@ -514,6 +609,10 @@ async function bareTopFetchSnapshot(ctx) {
}
const fetchWallMs = Date.now() - fetchStart
bareTopFetchEwmaMs =
bareTopFetchEwmaMs < 0
? fetchWallMs
: bareTopFetchEwmaMs * 0.82 + fetchWallMs * 0.18
const peersN = Number(metricsLive && metricsLive.peers) || 0
const healthD = bareTopHealthScoreDetail(metricsLive, peersN, readErr)
@@ -524,12 +623,16 @@ async function bareTopFetchSnapshot(ctx) {
initdGraph,
netSummary,
meminfoLine,
swapinfoLine,
loadavgLine,
cpuLine,
cpuCoreCount,
diskstatsLine,
hostOs,
atMs,
metaAtMs: metaAt,
readErr,
readErrPaths,
fileTexts,
extra,
fairnessSnapshot,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -5,17 +5,25 @@
"synopsis": [
"baretop [OPTION]... [OPERAND]..."
],
"description": "Full-screen terminal dashboard (htop-inspired) over /proc/bare_os/*, metrics_live, and optional ctx fast paths. Logical process PIDs from process_table.json — not host OS PIDs. Overview scrolls with a pinned HUD (sparklines, delegates). BARE_TOP_OVERVIEW_COMPACT and BARE_TOP_OVERVIEW_SECTIONS tune sections; BARE_TOP_LAYOUT=even enables a wide split. Processes: sort pid|name|state|time (BARE_TOP_PROC_SORT, F6/S, </>), tree (V), filter (/), optional BARE_TOP_MOUSE. See src/baretop.js --help for the full environment list (BARE_TOP_SNAPSHOT_LITE, BARE_TOP_EXPORT_REDACT, BARE_TOP_HEALTH_DETAIL, …).",
"description": "Full-screen terminal dashboard (htop-inspired) over /proc/bare_os/*, metrics_live, and optional ctx fast paths. Logical process PIDs from process_table.json — not host OS PIDs. Overview scrolls with a pinned HUD (sparklines, delegates) and extra sections (network summary line, diskstats, syscalls summary, security_posture, boot_budget_summary, peer health, capabilities diff, extensions, git deltas in delta mode). BARE_TOP_OVERVIEW_COMPACT and BARE_TOP_OVERVIEW_SECTIONS tune sections. Network tab formats nested net_summary objects (replicationQueue, peerFirewallStats, bsdSocketGuestBridge). BARE_TOP_INCREMENTAL=2 builds a full text frame each tick but patches only changed terminal lines. See src/baretop.js --help for the full environment list.",
"options": [],
"environment": [
"BARE_TOP_INTERVAL_MS — refresh period (ms).",
"BARE_TOP_SNAPSHOT_LITE=1 — smaller bareOsReadBareTopSnapshot batch.",
"BARE_TOP_OVERVIEW_COMPACT=1 — hide low-priority overview sections.",
"BARE_TOP_OVERVIEW_SECTIONS — comma list of section tokens.",
"BARE_TOP_PROC_SORT — pid | name | state | time.",
"BARE_TOP_MOUSE=1 — SGR mouse (wheel; row click on processes tab).",
"BARE_TOP_PROC_SORT — pid | name | state | time | nice | pri | cpu.",
"BARE_TOP_NET_FILTER=1 — / filter applies on network tab.",
"BARE_TOP_CMD_ELLIPSIS_MIDDLE=1 — middle-ellipsis long process names.",
"BARE_TOP_MOUSE=1 — SGR mouse (wheel; row click processes; click-scroll other list tabs).",
"BARE_TOP_EXPORT_REDACT=1 — omit fileTexts from JSON export.",
"BARE_TOP_HEALTH_DETAIL=1 — show health score penalty breakdown."
"BARE_TOP_HEALTH_DETAIL=1 — show health score penalty breakdown.",
"BARE_TOP_INCREMENTAL=2 — line-diff terminal output (layout still computed each tick).",
"BARE_TOP_DEBUG_TIMINGS=1 — throttled fetch/draw timing on stderr.",
"BARE_TOP_FETCH_EWMA=1 — reduce parallel /proc batch reads when recent fetches are slow.",
"BARE_TOP_SCROLL_REGION=1 — DECSTBM scroll region around the process list (experimental).",
"BARE_TOP_LAYOUT_AUTO=1 — use even layout when terminal is wide.",
"bareOsReadBareTopSnapshot — may include metricsLiveText to skip a separate metrics_live read."
],
"keywords": [
"baretop",
+1 -1
View File
@@ -6,6 +6,6 @@
"description": "Build JS /bin utilities for bare-operating-system (concat + stage to kernel/)",
"scripts": {
"build": "node ./scripts/ensure-man-pages.mjs && node ./build.mjs",
"test": "node ./test/help-bin-list.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.test.mjs && node ./test/baretop-bundle.test.mjs && node ./test/baretop-fixture.test.mjs && node ./test/baretop-ui-helpers.test.mjs && node ./test/posix-test-int-compare.test.mjs && node ./test/posix-utils-edge.test.mjs && node ./test/posix-golden-issue7.test.mjs && node ./test/getconf-pathconf-union-mirror.test.mjs && node ./test/getconf-posix-shm.test.mjs && node ./test/awk-sed-posix-smoke.test.mjs && node ./test/xattr-acl-utils.test.mjs && node ./test/xcu-issue7-sweep.test.mjs && node ./test/expand-tab-stops.test.mjs"
"test": "node ./test/help-bin-list.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.test.mjs && node ./test/baretop-bundle.test.mjs && node ./test/baretop-fixture.test.mjs && node ./test/baretop-incremental.test.mjs && node ./test/baretop-ui-helpers.test.mjs && node ./test/posix-test-int-compare.test.mjs && node ./test/posix-utils-edge.test.mjs && node ./test/posix-golden-issue7.test.mjs && node ./test/getconf-pathconf-union-mirror.test.mjs && node ./test/getconf-posix-shm.test.mjs && node ./test/awk-sed-posix-smoke.test.mjs && node ./test/xattr-acl-utils.test.mjs && node ./test/xcu-issue7-sweep.test.mjs && node ./test/expand-tab-stops.test.mjs"
}
}
@@ -0,0 +1,16 @@
#!/usr/bin/env node
/**
* Manual QA checklist for baretop health score (run inside a Bare OS TTY session).
* 1. Run: baretop
* 2. With zero peers and idle delegates, health should read 100 (not stuck at 73).
* 3. Run with: BARE_TOP_HEALTH_DETAIL=1 baretop — overview shows penalty breakdown.
* 4. Optional: BARE_TOP_INCREMENTAL=2 baretop — stderr with BARE_TOP_DEBUG_TIMINGS=1 may show patchLen < outLen when idle.
* 5. Network tab (tab 4): nested net_summary fields render as lines/JSON — no literal “[object Object]”.
* 6. Optional: BARE_TOP_NET_FILTER=1 then / on network tab filters lines.
*/
console.log(`baretop health QA:
- Expect health 100 on single-node idle (operational stallHint no longer penalized).
- Use BARE_TOP_HEALTH_DETAIL=1 to verify breakdown text.
- Use BARE_TOP_DEBUG_TIMINGS=1 to inspect fetchMs / outLen / patchLen.
- Network tab: replicationQueue / peerFirewallStats / bsdSocketGuestBridge should show structured text.
- BARE_TOP_NET_FILTER=1 enables / filter on the network tab.`)
+13 -8
View File
@@ -11,15 +11,19 @@ async function run(ctx, argv) {
'Requires a TTY.\n' +
'\n' +
'Tabs: 1 overview 2 processes 3 initd 4 network 5 features 6 diagnostics\n' +
' 7 operator 8 pear 9 catalog [ ] host 0 keys (F1 help, F5 refresh, F9 signal)\n' +
' 7 operator 8 pear 9 catalog [ ] host 0 keys\n' +
' F1 help F2 setup F3/F4 proc row F5 refresh F9 signal F10 quit\n' +
'\n' +
'Environment:\n' +
' BARE_TOP_INTERVAL_MS Refresh period (default 1000; clamped 25010000)\n' +
' BARE_TOP_UI_MIN_MS Minimum ms between full redraws (0 = off)\n' +
' BARE_TOP_FETCH_CONCURRENCY Parallel /proc reads per batch (default 6)\n' +
' BARE_TOP_SNAPSHOT_LITE Set to 1 for smaller bareOsReadBareTopSnapshot batch\n' +
' BARE_TOP_FETCH_CONCURRENCY Parallel /proc reads per batch (default 8)\n' +
' BARE_TOP_FETCH_EWMA Set to 1 to reduce batch concurrency when recent fetches are slow\n' +
' BARE_TOP_SNAPSHOT_LITE Set to 1 for smaller bareOsReadBareTopSnapshot batch (net_summary still read)\n' +
' BARE_TOP_NET_FILTER Set to 1 so / filter applies on network tab\n' +
' BARE_TOP_CMD_ELLIPSIS_MIDDLE Set to 1 for middle-ellipsis on long process names\n' +
' BARE_TOP_NO_ALTSCREEN Any non-empty value skips alternate-screen mode\n' +
' BARE_TOP_INCREMENTAL Set to 1 to skip full 2J clear (may flicker)\n' +
' BARE_TOP_INCREMENTAL 1 = soft home (no 2J) 2 = line-diff patches (full frame each tick; terminal patched)\n' +
' BARE_TOP_NO_2J_AFTER_FIRST Set to 1 to use soft clear after first full paint\n' +
' BARE_TOP_ASCII_GRAPH Set to 1 for ASCII sparklines\n' +
' BARE_TOP_ASCII_UI Set to 1 for ASCII box/spark fallbacks\n' +
@@ -32,6 +36,7 @@ async function run(ctx, argv) {
' BARE_TOP_COLOR256 Set to 1 to force 256-color bar hints\n' +
' BARE_TOP_VI_KEYS Set to 1 for hjkl focus in vi-style mode\n' +
' BARE_TOP_LAYOUT stacked | even (wide overview split + mini jobs)\n' +
' BARE_TOP_LAYOUT_AUTO Set to 1 to pick even layout when cols >= 100\n' +
' BARE_TOP_FRAMES Set to 1 for left rule on scroll regions\n' +
' BARE_TOP_FULLSCREEN_HIDE_TABS Set to 1 to hide tab strip in fullscreen (f)\n' +
' BARE_TOP_OVERVIEW_COMPACT Set to 1 to hide low-priority overview sections\n' +
@@ -50,11 +55,11 @@ async function run(ctx, argv) {
' BARE_TOP_SCROLL_REGION Set to 1 only with compatible terminals (see help)\n' +
' NO_COLOR Disable ANSI colors\n' +
'\n' +
'Keys: q quit r F5 refresh+burst sp pause . step d delta e export f fullscreen\n' +
' t UTC [ ] tabs arrows scroll / move selection g G top/bottom h F1 help\n' +
'Keys: q F10 quit r F5 refresh+burst sp pause . step d delta e export f fullscreen\n' +
' t UTC [ ] tabs arrows scroll / move selection g G top/bottom h F1 help F2 setup\n' +
' / filter (overview sections, initd, or processes) V tree (processes)\n' +
' F6 S sort menu (processes) <> cycle sort Enter detail k F9 signal\n' +
' Esc closes help or filter\n' +
' F3 F4 next/prev process row F6 S sort menu (processes) <> cycle sort\n' +
' Enter detail k F9 signal Esc closes help, setup, or filter\n' +
'\n' +
'Contributors: /bin bundles cannot use node:crypto or node:module; use ctx.vfs and\n' +
'JSON.parse only. See holepunch-repos/docs/05-BARE-RUNTIME.md (Bare module stack).'
@@ -17,6 +17,10 @@ test('baretop-tui uses alternate screen teardown pattern', async (t) => {
(src.match(/\\x1b\[2J\\x1b\[H/g) || []).length >= 1,
'expected full clear fallback in finally'
)
t.ok(
src.includes('incrementalFrameDiff'),
'expected incremental frame diff gate in tui source'
)
})
test('shipped kernel/bin/baretop includes snapshot + TUI', async (t) => {
@@ -19,6 +19,10 @@ test('fixture metrics_live-shaped JSON parses with expected keys', async (t) =>
t.ok(typeof o.delegateInflight === 'object')
t.ok(o.replicationLive && typeof o.replicationLive === 'object')
t.ok(o.kernelCounters && typeof o.kernelCounters === 'object')
t.ok(
o.netSummaryNestedExample &&
typeof o.netSummaryNestedExample.replicationQueue === 'object'
)
})
test('baretop-snapshot preamble lists batch proc entries', async (t) => {
@@ -29,6 +33,7 @@ test('baretop-snapshot preamble lists batch proc entries', async (t) => {
t.ok(src.includes('bareTopHealthScore'))
t.ok(src.includes('BARE_TOP_SNAPSHOT_LITE_ENTRIES'))
t.ok(src.includes('bootBudgetSummary'))
t.ok(src.includes('security_posture.json'))
})
test('bareTopHealthScore penalizes stall and inflight cardinality', async (t) => {
@@ -49,6 +54,44 @@ test('bareTopHealthScore penalizes stall and inflight cardinality', async (t) =>
t.is(ctx.bareTopHealthScore(good, 2), 100)
})
test('bareTopHealthScore: no_peers + idle delegates is not stuck at 73', async (t) => {
const src = await readFile(snapPath, 'utf8')
const ctx = createContext({})
runInContext(src, ctx)
const idle = {
peers: 0,
delegateInflight: {},
replicationLive: { peerCount: 0, stallHint: 'no_peers' }
}
t.is(ctx.bareTopHealthScore(idle, 0), 100)
const lightInflight = {
peers: 0,
delegateInflight: { http: 1, dns: 0 },
replicationLive: { peerCount: 0, stallHint: 'no_peers' }
}
const s2 = ctx.bareTopHealthScore(lightInflight, 0)
t.ok(s2 >= 90)
t.not(s2, 73)
})
test('bareTopHealthScoreDetail: benign stall hints and replicationStall flag', async (t) => {
const src = await readFile(snapPath, 'utf8')
const ctx = createContext({})
runInContext(src, ctx)
const lenOk = {
peers: 0,
replicationLive: { stallHint: 'length_unavailable', peerCount: 0 }
}
t.is(ctx.bareTopHealthScore(lenOk, 0), 100)
const explicit = {
peers: 1,
replicationLive: { stallHint: 'ok', peerCount: 1, replicationStall: true }
}
t.is(ctx.bareTopHealthScore(explicit, 1), 80)
const d = ctx.bareTopHealthScoreDetail(explicit, 1, null)
t.ok(String(d.breakdown).includes('replication_stall_flag'))
})
test('process_table fixture maps to sorted PID rows via helpers', async (t) => {
const raw = await readFile(processTableFixture, 'utf8')
const pt = JSON.parse(raw)
@@ -0,0 +1,39 @@
import test from 'brittle'
/**
* Mirrors baretop-tui incrementalFrameDiff patch builder (line CUP + EL + line).
* @param {string[]} prevLines
* @param {string[]} nextLines
*/
function bareTopLineDiffPatch(prevLines, nextLines) {
let patch = '\x1b[?25l'
let nch = 0
for (let i = 0; i < nextLines.length; i++) {
if (prevLines[i] !== nextLines[i]) {
nch++
const r = Math.max(1, Math.min(Math.floor(i + 1), 9999))
const c = 1
patch += '\x1b[' + r + ';' + c + 'H' + '\x1b[K' + nextLines[i] + '\r\n'
}
}
patch += '\x1b[?25h'
return { patch, nch }
}
test('incremental line-diff patch is smaller than full frame when one line changes', async (t) => {
const n = 48
const prev = Array.from({ length: n }, (_, i) => 'row-' + i + ' ' + 'x'.repeat(40))
const next = prev.slice()
next[22] = 'row-22 ' + 'CHANGED'.repeat(6)
const full = next.join('\r\n') + '\r\n'
const { patch, nch } = bareTopLineDiffPatch(prev, next)
t.is(nch, 1)
t.ok(
patch.length < full.length,
'expected patch bytes < full frame (' +
patch.length +
' vs ' +
full.length +
')'
)
})
@@ -120,6 +120,91 @@ test('bareTopOverviewLines keeps ASCII section lines within cols', async (t) =>
}
})
test('bareTopNetTabLines sorts interfaces by rx+tx', async (t) => {
const code = await readFile(helpersPath, 'utf8')
const ctx = createContext({})
runInContext(code, ctx)
const lines = ctx.bareTopNetTabLines(
{
interfaces: [
{ name: 'lo', rxBytes: 1, txBytes: 1 },
{ name: 'eth0', rxBytes: 100, txBytes: 50 }
]
},
100
)
t.ok(lines.some((l) => l.includes('interfaces')))
t.ok(lines.some((l) => l.includes('eth0')))
})
test('bareTopNetTabLines expands nested objects (no [object Object])', async (t) => {
const code = await readFile(helpersPath, 'utf8')
const ctx = createContext({})
runInContext(code, ctx)
const raw = await readFile(
join(__dirname, 'fixtures/baretop-net-summary-nested.json'),
'utf8'
)
const net = JSON.parse(raw)
const lines = ctx.bareTopNetTabLines(net, 100, { maxLines: 200 })
const blob = lines.join('\n')
t.ok(!blob.includes('[object Object]'), 'should not stringify objects blindly')
t.ok(blob.includes('replicationQueue'), 'section for queue')
t.ok(blob.includes('peerFirewallStats'), 'section for firewall')
t.ok(blob.includes('bsdSocketGuestBridge'), 'section for bridge')
t.ok(blob.includes('acceptedSessionCount') || blob.includes('10'), 'firewall scalar')
})
test('bareTopNetTabLines respects maxLines cap', async (t) => {
const code = await readFile(helpersPath, 'utf8')
const ctx = createContext({})
runInContext(code, ctx)
const big = { a: 1 }
for (let i = 0; i < 80; i++) big['k' + i] = i
const lines = ctx.bareTopNetTabLines(big, 80, { maxLines: 12 })
t.ok(lines.length <= 12)
t.ok(lines[lines.length - 1].includes('truncated'))
})
test('bareTopSortProcessRows supports nice and cpu keys', async (t) => {
const code = await readFile(helpersPath, 'utf8')
const ctx = createContext({})
runInContext(code, ctx)
const rows = [
{ pid: 1, name: 'a', nice: 0 },
{ pid: 2, name: 'b', nice: 5 }
]
const byNice = ctx.bareTopSortProcessRows(rows, 'nice', true)
t.is(bareTopProcessPidLocal(byNice[0]), 1)
const rows2 = [
{ pid: 1, name: 'a', cpuPct: 10 },
{ pid: 2, name: 'b', cpuPct: 99 }
]
const byCpu = ctx.bareTopSortProcessRows(rows2, 'cpu', false)
t.is(bareTopProcessPidLocal(byCpu[0]), 2)
})
test('bareTopLimitsMergedLines flattens quotas and rlimits', async (t) => {
const code = await readFile(helpersPath, 'utf8')
const ctx = createContext({})
runInContext(code, ctx)
const snap = {
extra: { quotas: { pipe: 8 }, rlimits: { nofile: 1024 } }
}
const lines = ctx.bareTopLimitsMergedLines(snap)
t.ok(lines.some((l) => l.includes('pipe=8')))
t.ok(lines.some((l) => l.includes('nofile=1024')))
})
test('bareTopDelegateInflightTableLines right-aligns counts', async (t) => {
const code = await readFile(helpersPath, 'utf8')
const ctx = createContext({})
runInContext(code, ctx)
const lines = ctx.bareTopDelegateInflightTableLines({ a: 3, b: 12 }, 5, 8)
t.ok(lines[0].includes('b') && lines[0].includes('12'))
t.ok(lines[1].includes('a') && lines[1].includes('3'))
})
test('bareTopParseMeminfoMetrics derives MemTotal and MemAvailable', async (t) => {
const code = await readFile(helpersPath, 'utf8')
const ctx = createContext({})
@@ -0,0 +1,27 @@
{
"schema": 1,
"schemaVersion": 2,
"topicHex": "deadbeef",
"peerCount": 3,
"seedHandshakeError": null,
"replicationQueueDepth": 2,
"replicationQueue": {
"depth": 2,
"pending": 1,
"items": [{ "id": "a" }]
},
"peerFirewallStats": {
"acceptedSessionCount": 10,
"rejectedSessionCount": 1,
"inboundSessionCount": 4,
"outboundSessionCount": 6,
"wave9": { "x": 1 }
},
"bsdSocketGuestBridge": {
"schema": 1,
"policyEnv": "BARE_OS_BSD_SOCKET_POLICY",
"note": "test bridge",
"activeBridgedFds": 0
},
"atMs": 1700000000000
}
@@ -28,5 +28,27 @@
"swarmLifecycle": { "phase": "steady" },
"initdReadiness": { "failed": 0, "starting": 1, "active": 4 },
"workerBudgetHints": { "wallMsMax": 60000 },
"processTable": { "schema": 7, "rows": [] }
"processTable": { "schema": 7, "rows": [] },
"netSummaryNestedExample": {
"schemaVersion": 2,
"topicHex": "deadbeef",
"peerCount": 2,
"seedHandshakeError": "",
"replicationQueueDepth": 4,
"peerFirewallBlockedSessions": 0,
"peerFirewallAllowedSessions": 12,
"replicationQueue": {
"depth": 4,
"lastError": null,
"batches": [{ "id": 1, "state": "pending" }]
},
"peerFirewallStats": {
"sessions": { "blocked": 0, "allowed": 12 }
},
"bsdSocketGuestBridge": {
"schema": 1,
"policyEnv": "default",
"note": "fixture"
}
}
}