Updates for btop

This commit is contained in:
Raven Scott
2026-04-26 08:28:12 -04:00
parent 194fbd2c67
commit 67743621a5
28 changed files with 7428 additions and 775 deletions
+1305 -86
View File
File diff suppressed because it is too large Load Diff
+1305 -86
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
{
"schema": 2,
"profileId": "bare-os-posix-like",
"generatedAt": "2026-04-26T12:01:40.794Z",
"generatedAt": "2026-04-26T12:27:28.060Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [
{
+1 -1
View File
@@ -1,6 +1,6 @@
{
"schema": 1,
"atMs": 1777204900794,
"atMs": 1777206448060,
"commands": [
"agent",
"arch",
File diff suppressed because one or more lines are too long
+68 -23
View File
@@ -8630,19 +8630,21 @@ async function executeKernel(disk, store, swarm, initSource) {
return { ok: true }
},
/**
* Batch-read `/proc/bare_os/*` mirrors for baretop(1). Keys must stay aligned with
* Batch-read `/proc/bare_os/*` mirrors for baretop(1). Keys should stay aligned with
* `BARE_TOP_SNAPSHOT_PROC_ENTRIES` in packages/bare-os-coreutils/lib/baretop-snapshot.js.
* @returns {Promise<{ atMs: number, files: Record<string, string>, metricsLiveText?: string }>}
* Supports profile and requested-key hints for smoother tab-aware snapshots.
* @returns {Promise<{ atMs: number, files: Record<string, string>, metricsLiveText?: string, snapshotBytes?: number, profile?: string, truncated?: boolean }>}
*/
async bareOsReadBareTopSnapshot(opts) {
const atMs = Date.now()
/** @type {Record<string, string>} */
const files = Object.create(null)
const lite =
opts &&
typeof opts === 'object' &&
/** @type {{ lite?: boolean }} */ (opts).lite === true
const fullEntries = [
const opt =
opts && typeof opts === 'object'
? /** @type {{ lite?: boolean, profile?: string, requestedKeys?: string[], maxBytes?: number }} */ (opts)
: {}
const lite = opt.lite === true
const standardEntries = [
['index', '/proc/bare_os/index.json'],
['version', '/proc/bare_os/version'],
['hostOs', '/proc/bare_os/host_os.json'],
@@ -8688,45 +8690,82 @@ async function executeKernel(disk, store, swarm, initSource) {
['processTable', '/proc/bare_os/process_table.json'],
['syscalls', '/proc/bare_os/syscalls.json'],
['metricsProm', '/proc/bare_os/metrics.prom'],
['meshdrop', '/proc/bare_os/meshdrop.json'],
['peerDetails', '/proc/bare_os/peer_details.json'],
['dhtScan', '/proc/bare_os/dht_scan.json'],
['swarmDoctor', '/proc/bare_os/swarm_doctor.json'],
['routeSummary', '/proc/bare_os/route_summary.json'],
['holepunchSummary', '/proc/bare_os/holepunch_summary.json'],
['protomuxWire', '/proc/bare_os/protomux.json'],
['securityPosture', '/proc/bare_os/security_posture.json'],
['processIo', '/proc/bare_os/process_io.json'],
['processThreads', '/proc/bare_os/process_threads.json'],
['processMaps', '/proc/bare_os/process_maps.json']
]
const liteEntries = [
const minimalEntries = [
['index', '/proc/bare_os/index.json'],
['version', '/proc/bare_os/version'],
['hostOs', '/proc/bare_os/host_os.json'],
['replication', '/proc/bare_os/replication'],
['swarm', '/proc/bare_os/swarm'],
['syncWindow', '/proc/bare_os/sync_window.json'],
['processTable', '/proc/bare_os/process_table.json'],
['snapshotHints', '/proc/bare_os/snapshot_hints.json']
]
const extendedEntries = standardEntries.concat([
['meshdrop', '/proc/bare_os/meshdrop.json'],
['peerDetails', '/proc/bare_os/peer_details.json'],
['dhtScan', '/proc/bare_os/dht_scan.json'],
['swarmDoctor', '/proc/bare_os/swarm_doctor.json'],
['routeSummary', '/proc/bare_os/route_summary.json'],
['holepunchSummary', '/proc/bare_os/holepunch_summary.json'],
['syncWindow', '/proc/bare_os/sync_window.json'],
['processTable', '/proc/bare_os/process_table.json'],
['snapshotHints', '/proc/bare_os/snapshot_hints.json']
]
const entries = lite ? liteEntries : fullEntries
['holepunchSummary', '/proc/bare_os/holepunch_summary.json']
])
const profileReq = String(opt.profile || '').trim().toLowerCase()
const profile =
lite || profileReq === 'minimal'
? 'minimal'
: profileReq === 'extended'
? 'extended'
: 'standard'
const byProfile = {
minimal: minimalEntries,
standard: standardEntries,
extended: extendedEntries
}
let entries = byProfile[profile] || standardEntries
const requestedKeySet =
Array.isArray(opt.requestedKeys) && opt.requestedKeys.length
? new Set(
opt.requestedKeys
.map((k) => String(k || '').trim())
.filter(Boolean)
)
: null
if (requestedKeySet && requestedKeySet.size) {
entries = entries.filter(([k]) => requestedKeySet.has(k))
}
if (!vfs || typeof vfs.readFile !== 'function') return { atMs, files }
const conc = 6
const maxBytesRaw = Number(opt.maxBytes)
const maxBytes =
Number.isFinite(maxBytesRaw) && maxBytesRaw > 1024
? Math.min(8 * 1024 * 1024, Math.floor(maxBytesRaw))
: 0
let truncated = false
for (let i = 0; i < entries.length; i += conc) {
const slice = entries.slice(i, i + conc)
await Promise.all(
slice.map(async ([key, path]) => {
if (truncated && maxBytes > 0) {
files[key] = ''
return
}
try {
const buf = await vfs.readFile(path)
files[key] =
typeof buf === 'string' ? buf : b4a.toString(buf)
const txt = typeof buf === 'string' ? buf : b4a.toString(buf)
files[key] = txt
if (maxBytes > 0) {
let running = 0
for (const fk of Object.keys(files)) {
const fv = files[fk]
if (typeof fv === 'string') running += fv.length
}
if (running > maxBytes) truncated = true
}
} catch {
files[key] = ''
}
@@ -8741,7 +8780,13 @@ async function executeKernel(disk, store, swarm, initSource) {
} catch {
/* ignore */
}
return { atMs, files, metricsLiveText }
let snapshotBytes = 0
for (const k of Object.keys(files)) {
const v = files[k]
if (typeof v === 'string') snapshotBytes += v.length
}
if (typeof metricsLiveText === 'string') snapshotBytes += metricsLiveText.length
return { atMs, files, metricsLiveText, snapshotBytes, profile, truncated }
},
/**
* Register `/run/bare-os/virtual/<name>` reader (cap-gated: `virtualRegisterFiles`).
+29
View File
@@ -41,6 +41,35 @@ Or `node packages/bare-os-coreutils/build.mjs`.
**`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`**.
### Baretop performance baseline
Run the repeatable baseline from repo root:
```bash
npm run perf:baretop -w bare-os-coreutils
```
or inside the package:
```bash
node ./test/baretop-perf-baseline.test.mjs
```
KPI envelope tracked in Wave 1:
- `fetchMs` (snapshot fetch wall time): rolling `p50` / `p95`
- `composeMs` (frame compose cost): rolling `p50` / `p95`
- `emitMs` (terminal emit cost): rolling `p50` / `p95`
- `emitBytes` (bytes written/frame): rolling `p50` / `p95`
- `droppedRefreshPct` (draws skipped by UI throttling): rolling `p50` / `p95`
- startup-to-first-frame latency (`start=...ms` in profile footer)
Expected development-range targets (local machine, non-SSH):
- `composeMs p95 < 40ms` on fixture runs
- `emitMs p95 < 10ms` in synthetic tests
- `droppedRefreshPct p50 < 10%` with default `BARE_TOP_UI_MIN_MS=0`
**`agent`** is the OpenAI-compatible **HTTPS** assistant (**ReAct**-style tools, TTY streaming). Preamble pulls in **`lib/agent-*.js`**, **`agent-workspace.js`** (**`~/.agent/workspace/*.md`** loader), **`agent-skills.js`** (compact skill index + **`read_skill`**), **`agent-web-fetch.js`**, **`agent-tools.js`**, **`agent-tui.js`** (see **`build.mjs`** **`preamble.agent`**). Config and secrets live under `**~/.agent/**` on the **personal** drive (**`man agent`**). **Markdown soul files** default from [`share/agent-workspace/`](share/agent-workspace/) (also staged to **`kernel/share/agent-workspace/`** on build). Outbound HTTPS uses **`ctx.httpFetch`** (same **`BARE_OS_HTTP_ALLOWLIST`** / **`BARE_OS_HTTP_DENYLIST`** as delegated **`curl`** / **`wget`**); the **`web_fetch`** tool needs every target host allowlisted alongside your API origin. Maintainer smoke under the Bare runtime: from repo root **`npm run smoke:agent-web-fetch:bare`** ([`scripts/smoke-agent-web-fetch-bare.mjs`](../../scripts/smoke-agent-web-fetch-bare.mjs)). **`chat`** is separate: swarm / Protomux text chat (**`lib/chat-tui.js`** preamble; **`man chat`**).
**`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)).
@@ -13,6 +13,9 @@ function bareTopSafeWrite(ctx, stdout, s) {
}
}
/** Hysteresis latch to avoid patch/full oscillation bursts. */
var bareTopPatchHysteresis = { preferFullUntil: 0 }
/**
* FNV-1a 32-bit for line dirty detection (item 4).
* @param {string} s
@@ -86,37 +89,74 @@ function bareTopBuildLinePatch(prevLines, nextLines, cup, opts) {
* cols: number,
* incrementalFrameDiff: boolean,
* useLineHash: boolean,
* patchThresholdPct?: number,
* fullscreenPanel: boolean,
* cup: (r: number, c: number) => string
* }} o
* @returns {{ nextLines: string[], wrotePatch: boolean, patchLen: number }}
* @returns {{ nextLines: string[], wrotePatch: boolean, patchLen: number, consideredPatch: boolean, fallbackReason: string }}
*/
function bareTopEmitFrame(ctx, stdout, fullOut, o) {
const nextLines = bareTopFrameToLines(fullOut)
let wrotePatch = false
let patchLen = 0
let consideredPatch = false
let fallbackReason = 'full_frame_initial'
const pctThreshold = Math.max(
0.5,
Math.min(0.99, Number(o.patchThresholdPct) || 0.92)
)
const now = Date.now()
const inHysteresisFull = bareTopPatchHysteresis.preferFullUntil > now
if (
o.incrementalFrameDiff &&
o.prevLines &&
o.prevLines.length === nextLines.length &&
nextLines.length > 2 &&
!o.fullscreenPanel
!o.fullscreenPanel &&
!inHysteresisFull
) {
consideredPatch = true
const { patch, nch } = bareTopBuildLinePatch(
o.prevLines,
nextLines,
o.cup,
{ useLineHash: o.useLineHash, lineHashMinLen: 200 }
{
// On tiny frames, hash setup is usually slower than direct compare.
useLineHash: o.useLineHash && nextLines.length > 8,
lineHashMinLen: 200
}
)
if (nch > 0 && nch < nextLines.length * 0.92) {
if (nch > 0 && nch < nextLines.length * pctThreshold) {
patchLen = patch.length
bareTopSafeWrite(/** @type {Record<string, unknown>} */ (ctx), stdout, patch)
wrotePatch = true
return { nextLines, wrotePatch, patchLen }
return {
nextLines,
wrotePatch,
patchLen,
consideredPatch,
fallbackReason: 'patch_ok'
}
}
fallbackReason = nch === 0 ? 'patch_no_changes' : 'patch_change_ratio_high'
if (nch >= nextLines.length * 0.96) {
bareTopPatchHysteresis.preferFullUntil = now + 1200
}
} else if (!o.incrementalFrameDiff) {
fallbackReason = 'incremental_disabled'
} else if (inHysteresisFull) {
fallbackReason = 'patch_hysteresis_full'
} else if (!o.prevLines) {
fallbackReason = 'no_prev_frame'
} else if (o.prevLines.length !== nextLines.length) {
fallbackReason = 'line_count_changed'
} else if (nextLines.length <= 2) {
fallbackReason = 'frame_too_small'
} else if (o.fullscreenPanel) {
fallbackReason = 'fullscreen_panel'
}
bareTopSafeWrite(/** @type {Record<string, unknown>} */ (ctx), stdout, fullOut)
return { nextLines, wrotePatch, patchLen }
return { nextLines, wrotePatch, patchLen, consideredPatch, fallbackReason }
}
/**
@@ -2,6 +2,10 @@
/** EWMA of last fetch wall time (ms); used when `BARE_TOP_FETCH_EWMA=1`. */
var bareTopFetchEwmaMs = -1
/** Last fallback proc batch concurrency (for jitter smoothing). */
var bareTopLastFallbackConc = -1
/** Soft cache for secondary proc/ctx reads (stale-while-refresh). */
var bareTopSecondaryCache = Object.create(null)
/**
* Keep in sync with `bareOsReadBareTopSnapshot` path list in packages/bare-os-booter/index.js
@@ -72,6 +76,15 @@ var BARE_TOP_SNAPSHOT_LITE_ENTRIES = [
['snapshotHints', '/proc/bare_os/snapshot_hints.json']
]
var BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS = [
'meshdrop',
'peerDetails',
'dhtScan',
'swarmDoctor',
'routeSummary',
'holepunchSummary'
]
/** @param {unknown} buf @param {unknown} b4a @returns {string} */
function bareTopBufToString(buf, b4a) {
if (buf == null) return ''
@@ -155,6 +168,31 @@ async function bareTopReadProcBatch(ctx, entries, concurrency, errPaths) {
return out
}
/**
* Read-through stale cache helper for secondary fields.
* @param {string} key
* @param {number} ttlMs
* @param {() => Promise<unknown>} producer
* @returns {Promise<{ value: unknown, stale: boolean, freshAtMs: number, ttlMs: number }>}
*/
async function bareTopCachedSecondary(key, ttlMs, producer) {
const now = Date.now()
const cur = bareTopSecondaryCache[key]
if (cur && typeof cur === 'object' && now - cur.atMs <= ttlMs) {
return { value: cur.value, stale: false, freshAtMs: cur.atMs, ttlMs }
}
try {
const value = await producer()
bareTopSecondaryCache[key] = { value, atMs: now }
return { value, stale: false, freshAtMs: now, ttlMs }
} catch {
if (cur && typeof cur === 'object') {
return { value: cur.value, stale: true, freshAtMs: cur.atMs, ttlMs }
}
return { value: null, stale: true, freshAtMs: 0, ttlMs }
}
}
/**
* @param {unknown} o
* @param {number} maxChars
@@ -400,7 +438,7 @@ async function bareTopFetchSnapshot(ctx, fetchOpts) {
: {}
const fo =
fetchOpts && typeof fetchOpts === 'object'
? /** @type {{ forceLite?: boolean }} */ (fetchOpts)
? /** @type {{ forceLite?: boolean, activeTab?: string }} */ (fetchOpts)
: null
const concRaw = parseInt(env.BARE_TOP_FETCH_CONCURRENCY || '8', 10)
const concurrency = Number.isFinite(concRaw) ? concRaw : 8
@@ -411,20 +449,77 @@ async function bareTopFetchSnapshot(ctx, fetchOpts) {
? true
: env.BARE_TOP_SNAPSHOT_LITE === '1' ||
env.BARE_TOP_SNAPSHOT_LITE === 'true'
const snapshotExtendedOn =
env.BARE_TOP_SNAPSHOT_EXTENDED === '1' ||
env.BARE_TOP_SNAPSHOT_EXTENDED === 'true'
const missingSignalsOn =
env.BARE_TOP_MISSING_SIGNALS === '1' ||
env.BARE_TOP_MISSING_SIGNALS === 'true'
const procEntryList = snapshotLite
? BARE_TOP_SNAPSHOT_LITE_ENTRIES
: BARE_TOP_SNAPSHOT_PROC_ENTRIES
const activeTab = String((fo && fo.activeTab) || 'overview').toLowerCase()
const requestedKeys = (() => {
if (snapshotLite) return BARE_TOP_SNAPSHOT_LITE_ENTRIES.map(([k]) => k)
if (activeTab === 'processes')
return [
'index',
'version',
'hostOs',
'processTable',
'sessionStats',
'swarm',
'replication',
'snapshotHints'
]
if (activeTab === 'network')
return [
'index',
'version',
'hostOs',
'swarm',
'replication',
'dhtStatus',
'udxExtended',
'snapshotHints'
]
if (
missingSignalsOn &&
(activeTab === 'network' || activeTab === 'operator' || activeTab === 'diagnostics')
) {
return BARE_TOP_SNAPSHOT_PROC_ENTRIES.map(([k]) => k).concat(
BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS
)
}
return BARE_TOP_SNAPSHOT_PROC_ENTRIES.map(([k]) => k)
})()
const snapshotProfile =
snapshotLite || activeTab === 'processes' || activeTab === 'network'
? 'minimal'
: snapshotExtendedOn ||
(missingSignalsOn &&
(activeTab === 'network' ||
activeTab === 'operator' ||
activeTab === 'diagnostics'))
? 'extended'
: 'standard'
/** @type {Record<string, string>} */
let fileTexts = {}
let fastAtMs = 0
let snapshotBatchBytes = 0
/** @type {Record<string, unknown> | null} */
let metricsLiveFromSnap = null
try {
if (typeof ctx.bareOsReadBareTopSnapshot === 'function') {
const r = await ctx.bareOsReadBareTopSnapshot(
snapshotLite ? { lite: true } : undefined
snapshotLite
? { lite: true, requestedKeys }
: {
profile: snapshotProfile,
requestedKeys
}
)
if (r && typeof r === 'object') {
const o = /** @type {Record<string, unknown>} */ (r)
@@ -437,6 +532,13 @@ async function bareTopFetchSnapshot(ctx, fetchOpts) {
}
const am = o.atMs
if (typeof am === 'number' && Number.isFinite(am)) fastAtMs = am
if (
typeof o.snapshotBytes === 'number' &&
Number.isFinite(o.snapshotBytes) &&
o.snapshotBytes >= 0
) {
snapshotBatchBytes = o.snapshotBytes
}
const mlRaw = o.metricsLiveText
if (typeof mlRaw === 'string' && mlRaw.trim()) {
const mp = bareTopJsonParse(mlRaw)
@@ -460,6 +562,11 @@ async function bareTopFetchSnapshot(ctx, fetchOpts) {
else if (e > 500) useConc = Math.max(2, concurrency - 2)
else if (e > 280) useConc = Math.max(2, concurrency - 1)
}
if (bareTopLastFallbackConc > 0) {
if (useConc > bareTopLastFallbackConc + 1) useConc = bareTopLastFallbackConc + 1
if (useConc < bareTopLastFallbackConc - 1) useConc = bareTopLastFallbackConc - 1
}
bareTopLastFallbackConc = useConc
fileTexts = await bareTopReadProcBatch(
ctx,
procEntryList,
@@ -486,6 +593,22 @@ async function bareTopFetchSnapshot(ctx, fetchOpts) {
let subprocessBridge = null
/** @type {Record<string, unknown> | null} */
let hostStats = null
const ttl = {
metricsLive: 300,
resources: activeTab === 'diagnostics' ? 400 : 1500,
features: activeTab === 'features' ? 500 : 2000,
netSummary: activeTab === 'network' ? 300 : 1500,
initdGraph: activeTab === 'initd' ? 400 : 2000,
fairness: activeTab === 'operator' ? 500 : 2000,
subprocess: activeTab === 'diagnostics' ? 600 : 2500,
hostStats: activeTab === 'host' ? 500 : 2000,
memRaw: activeTab === 'mem' ? 350 : 1400,
loadavg: activeTab === 'cpu' ? 350 : 1200,
cpuRaw: activeTab === 'cpu' ? 450 : 1800,
diskstats: activeTab === 'disk' ? 450 : 1800
}
/** @type {Record<string, { stale: boolean, freshAtMs: number, ttlMs: number }>} */
const sectionTtl = {}
try {
if (typeof ctx.bareOsReadProcMetricsLive === 'function') {
@@ -498,9 +621,22 @@ async function bareTopFetchSnapshot(ctx, fetchOpts) {
}
if (!metricsLive) {
const ml = await bareTopCachedSecondary(
'metrics_live',
ttl.metricsLive,
async () => {
const t = await bareTopReadProc(ctx, '/proc/bare_os/metrics_live.json')
const p = bareTopJsonParse(t)
if (p && typeof p === 'object') metricsLive = /** @type {Record<string, unknown>} */ (p)
return bareTopJsonParse(t)
}
)
sectionTtl.metricsLive = {
stale: ml.stale,
freshAtMs: ml.freshAtMs,
ttlMs: ml.ttlMs
}
if (ml.value && typeof ml.value === 'object') {
metricsLive = /** @type {Record<string, unknown>} */ (ml.value)
}
}
try {
@@ -513,52 +649,119 @@ async function bareTopFetchSnapshot(ctx, fetchOpts) {
}
if (!resources) {
const rs = await bareTopCachedSecondary(
'resources',
ttl.resources,
async () => {
const t = await bareTopReadProc(ctx, '/proc/bare_os_resources')
const p = bareTopJsonParse(t)
if (p && typeof p === 'object') resources = /** @type {Record<string, unknown>} */ (p)
return bareTopJsonParse(t)
}
)
sectionTtl.resources = { stale: rs.stale, freshAtMs: rs.freshAtMs, ttlMs: rs.ttlMs }
if (rs.value && typeof rs.value === 'object') {
resources = /** @type {Record<string, unknown>} */ (rs.value)
}
}
const fs = await bareTopCachedSecondary(
'features',
ttl.features,
async () => {
let fp = bareTopJsonParse(await bareTopReadProc(ctx, '/proc/bare_os/features'))
if (!fp) fp = bareTopJsonParse(await bareTopReadProc(ctx, '/proc/bare_os_features'))
if (fp && typeof fp === 'object') features = /** @type {Record<string, unknown>} */ (fp)
return fp
}
)
sectionTtl.features = { stale: fs.stale, freshAtMs: fs.freshAtMs, ttlMs: fs.ttlMs }
if (fs.value && typeof fs.value === 'object') {
features = /** @type {Record<string, unknown>} */ (fs.value)
}
const ns = await bareTopCachedSecondary(
'net_summary',
ttl.netSummary,
async () => {
const netT = await bareTopReadProc(ctx, '/proc/bare_os/net_summary.json')
const np = bareTopJsonParse(netT)
if (np && typeof np === 'object') netSummary = /** @type {Record<string, unknown>} */ (np)
return bareTopJsonParse(netT)
}
)
sectionTtl.netSummary = {
stale: ns.stale,
freshAtMs: ns.freshAtMs,
ttlMs: ns.ttlMs
}
if (ns.value && typeof ns.value === 'object') {
netSummary = /** @type {Record<string, unknown>} */ (ns.value)
}
const ig = await bareTopCachedSecondary(
'initd_graph',
ttl.initdGraph,
async () => {
const initT = await bareTopReadProc(ctx, '/proc/bare_os/initd_graph.json')
initdGraph = bareTopJsonParse(initT)
return bareTopJsonParse(initT)
}
)
sectionTtl.initdGraph = {
stale: ig.stale,
freshAtMs: ig.freshAtMs,
ttlMs: ig.ttlMs
}
initdGraph = ig.value
if (initdGraph === null) {
const alt = bareTopJsonParse(fileTexts.initdDag || '')
initdGraph = alt
}
try {
const fa = await bareTopCachedSecondary('fairness_snapshot', ttl.fairness, async () => {
if (typeof ctx.bareOsReadDelegateFairnessSnapshot === 'function') {
const o = ctx.bareOsReadDelegateFairnessSnapshot()
if (o && typeof o === 'object')
fairnessSnapshot = /** @type {Record<string, unknown>} */ (o)
return ctx.bareOsReadDelegateFairnessSnapshot()
}
} catch {
/* ignore */
return null
})
sectionTtl.fairnessSnapshot = {
stale: fa.stale,
freshAtMs: fa.freshAtMs,
ttlMs: fa.ttlMs
}
if (fa.value && typeof fa.value === 'object') {
fairnessSnapshot = /** @type {Record<string, unknown>} */ (fa.value)
}
try {
const sb = await bareTopCachedSecondary(
'subprocess_bridge',
ttl.subprocess,
async () => {
if (typeof ctx.bareOsReadSubprocessBridgeSnapshot === 'function') {
subprocessBridge = ctx.bareOsReadSubprocessBridgeSnapshot()
return ctx.bareOsReadSubprocessBridgeSnapshot()
}
} catch {
/* ignore */
return null
}
)
sectionTtl.subprocessBridge = {
stale: sb.stale,
freshAtMs: sb.freshAtMs,
ttlMs: sb.ttlMs
}
subprocessBridge = sb.value
const hs = await bareTopCachedSecondary('host_stats', ttl.hostStats, async () => {
return ctx.bareOsHostStats || null
})
sectionTtl.hostStats = { stale: hs.stale, freshAtMs: hs.freshAtMs, ttlMs: hs.ttlMs }
if (hs.value && typeof hs.value === 'object') {
hostStats = /** @type {Record<string, unknown>} */ (hs.value)
}
try {
const hs = ctx.bareOsHostStats
if (hs && typeof hs === 'object') hostStats = /** @type {Record<string, unknown>} */ (hs)
} catch {
/* ignore */
const memC = await bareTopCachedSecondary('proc_meminfo', ttl.memRaw, async () =>
bareTopReadProc(ctx, '/proc/meminfo')
)
sectionTtl.meminfo = {
stale: memC.stale,
freshAtMs: memC.freshAtMs,
ttlMs: memC.ttlMs
}
const memRaw = await bareTopReadProc(ctx, '/proc/meminfo')
const memRaw = typeof memC.value === 'string' ? memC.value : ''
let meminfoLine = ''
for (const line of memRaw.split('\n')) {
const L = line.trim()
@@ -569,11 +772,27 @@ async function bareTopFetchSnapshot(ctx, fetchOpts) {
}
if (!meminfoLine) meminfoLine = memRaw.split('\n')[0]?.trim() || ''
const loadavgLine = (await bareTopReadProc(ctx, '/proc/loadavg')).split('\n')[0]?.trim() || ''
const loadC = await bareTopCachedSecondary('proc_loadavg', ttl.loadavg, async () =>
bareTopReadProc(ctx, '/proc/loadavg')
)
sectionTtl.loadavg = {
stale: loadC.stale,
freshAtMs: loadC.freshAtMs,
ttlMs: loadC.ttlMs
}
const loadavgLine = String(loadC.value || '').split('\n')[0]?.trim() || ''
let cpuLine = ''
let cpuCoreCount = 0
const cpuRaw = await bareTopReadProc(ctx, '/proc/cpuinfo')
const cpuC = await bareTopCachedSecondary('proc_cpuinfo', ttl.cpuRaw, async () =>
bareTopReadProc(ctx, '/proc/cpuinfo')
)
sectionTtl.cpuinfo = {
stale: cpuC.stale,
freshAtMs: cpuC.freshAtMs,
ttlMs: cpuC.ttlMs
}
const cpuRaw = typeof cpuC.value === 'string' ? cpuC.value : ''
for (const line of cpuRaw.split('\n')) {
if (line.startsWith('model name') || line.startsWith('Model')) {
cpuLine = line.replace(/^[^:]+:\s*/, '').trim().slice(0, 72)
@@ -594,10 +813,45 @@ async function bareTopFetchSnapshot(ctx, fetchOpts) {
}
}
const diskstatsRaw = await bareTopReadProc(ctx, '/proc/diskstats')
const dsC = await bareTopCachedSecondary(
'proc_diskstats',
ttl.diskstats,
async () => bareTopReadProc(ctx, '/proc/diskstats')
)
sectionTtl.diskstats = {
stale: dsC.stale,
freshAtMs: dsC.freshAtMs,
ttlMs: dsC.ttlMs
}
const diskstatsRaw = typeof dsC.value === 'string' ? dsC.value : ''
const diskstatsLine = diskstatsRaw.split('\n')[0]?.trim() || ''
const hostOs = bareTopParsedFile(fileTexts, 'hostOs')
const procIndex =
bareTopParsedFile(fileTexts, 'index') || /** @type {Record<string, unknown>} */ ({})
const procIndexAvailability = (() => {
/** @type {Record<string, boolean>} */
const out = Object.create(null)
const nodes =
procIndex && Array.isArray(procIndex.nodes) ? procIndex.nodes : []
const nodeSet = new Set()
for (const n of nodes) {
if (!n || typeof n !== 'object') continue
const no = /** @type {Record<string, unknown>} */ (n)
if (typeof no.name === 'string') nodeSet.add(no.name)
}
for (const [k, p] of BARE_TOP_SNAPSHOT_PROC_ENTRIES) {
const base = p.split('/').pop() || ''
out[k] =
nodeSet.has(base) ||
nodeSet.has(base.replace(/\.json$/i, '')) ||
(typeof fileTexts[k] === 'string' && fileTexts[k].length > 0)
}
for (const k of BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS) {
out[k] = out[k] === true || (typeof fileTexts[k] === 'string' && fileTexts[k].length > 0)
}
return out
})()
const metaAt =
metricsLive && typeof metricsLive.atMs === 'number' ? metricsLive.atMs : atMs
@@ -617,6 +871,17 @@ async function bareTopFetchSnapshot(ctx, fetchOpts) {
}
extra[key] = bareTopParsedFile(fileTexts, key)
}
for (const key of BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS) {
if (extra[key] == null) extra[key] = bareTopParsedFile(fileTexts, key)
}
extra.procIndex = procIndex
extra.procIndexAvailability = procIndexAvailability
/** @type {Record<string, number>} */
const snapshotBytesByKey = Object.create(null)
for (const k of Object.keys(fileTexts)) {
const v = fileTexts[k]
snapshotBytesByKey[k] = typeof v === 'string' ? v.length : 0
}
const fetchWallMs = Date.now() - fetchStart
bareTopFetchEwmaMs =
@@ -650,6 +915,11 @@ async function bareTopFetchSnapshot(ctx, fetchOpts) {
subprocessBridge,
hostStats,
fetchWallMs,
snapshotBatchBytes,
snapshotBytesByKey,
sectionTtl,
snapshotProfile,
snapshotRequestedKeys: requestedKeys,
healthScore: healthD.score,
healthBreakdown: healthD.breakdown,
snapshotLite
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,24 @@
/** Pure UI helpers for /bin/baretop (preamble; no import). */
/** @type {Map<string, unknown>} */
var bareTopUiMemo = new Map()
var BARE_TOP_UI_MEMO_MAX = 256
/**
* @param {string} key
* @param {() => unknown} build
*/
function bareTopMemoGet(key, build) {
if (bareTopUiMemo.has(key)) return bareTopUiMemo.get(key)
const v = build()
bareTopUiMemo.set(key, v)
if (bareTopUiMemo.size > BARE_TOP_UI_MEMO_MAX) {
const it = bareTopUiMemo.keys().next()
if (!it.done) bareTopUiMemo.delete(it.value)
}
return v
}
/**
* @param {string} line
* @returns {{ label: string, value: string }[]}
@@ -235,6 +254,36 @@ function bareTopSortProcessRows(rows, sortKey, asc, opts) {
const tertiaryPpid = o.tertiaryPpid === true
const wallMs = Number(o.sortWallMs) || 0
const dir = asc ? 1 : -1
const memoKey =
'sort:' +
sortKey +
':' +
(asc ? '1' : '0') +
':' +
(tertiaryPpid ? '1' : '0') +
':' +
wallMs +
':' +
rows
.map((r) => {
const rr = /** @type {Record<string, unknown>} */ (r)
return (
String(rr.pid ?? '') +
'/' +
String(rr.ppid ?? '') +
'/' +
String(rr.name ?? '') +
'/' +
String(rr.state ?? '') +
'/' +
String(rr.startedAtMs ?? '') +
'/' +
String(rr.cpuPct ?? rr.cpu ?? '')
)
})
.join('|')
return /** @type {Record<string, unknown>[]} */ (
bareTopMemoGet(memoKey, () => {
const out = rows.slice()
out.sort((a, b) => {
const ka = bareTopProcessSortKey(a, sortKey, wallMs)
@@ -251,6 +300,8 @@ function bareTopSortProcessRows(rows, sortKey, asc, opts) {
return bareTopProcessPid(a) - bareTopProcessPid(b)
})
return out
})
)
}
/**
@@ -599,6 +650,27 @@ function bareTopUiFlatten(prefix, v, depth, maxD, lines, maxKeys) {
* @param {number} maxLines
*/
function bareTopUiFlattenLimited(prefix, v, depth, maxD, maxKeys, maxLines) {
let sig = ''
try {
sig = JSON.stringify(v)
} catch {
sig = String(v)
}
const key =
'flatten:' +
prefix +
':' +
depth +
':' +
maxD +
':' +
maxKeys +
':' +
maxLines +
':' +
sig
return /** @type {string[]} */ (
bareTopMemoGet(key, () => {
const lines = []
bareTopUiFlatten(prefix, v, depth, maxD, lines, maxKeys)
const cap = Math.max(4, maxLines | 0)
@@ -606,6 +678,8 @@ function bareTopUiFlattenLimited(prefix, v, depth, maxD, maxKeys, maxLines) {
return lines.slice(0, cap - 1).concat([' … (truncated)'])
}
return lines
})
)
}
/**
@@ -973,7 +1047,8 @@ function bareTopP2PStackLines(snap) {
*/
function bareTopNetTabLines(net, cols, opts) {
const oopts = opts && typeof opts === 'object' ? opts : {}
const maxTotal = Math.max(8, (oopts.maxLines | 0) || 512)
const adaptiveMax = cols < 90 ? 256 : cols < 120 ? 384 : 512
const maxTotal = Math.max(8, (oopts.maxLines | 0) || adaptiveMax)
const filt = String(oopts.filter || '')
.trim()
.toLowerCase()
@@ -1050,6 +1125,24 @@ function bareTopNetTabLines(net, cols, opts) {
bareTopTruncateCell(extra, w - 24, false)
)
}
if (arr.length > 32) {
let remRx = 0
let remTx = 0
for (const iface of arr.slice(32)) {
if (!iface || typeof iface !== 'object') continue
const i = /** @type {Record<string, unknown>} */ (iface)
remRx += Number(i.rxBytes ?? i.rx) || 0
remTx += Number(i.txBytes ?? i.tx) || 0
}
pushFiltered(
' (+ ' +
(arr.length - 32) +
' more) rx=' +
bareTopNetFormatBytes(remRx) +
' tx=' +
bareTopNetFormatBytes(remTx)
)
}
}
const sectionKeys = new Set([
@@ -1155,6 +1248,137 @@ function bareTopNetTabLines(net, cols, opts) {
return lines.length ? lines : [' (empty net summary)']
}
function bareTopNetworkDeepDiveLines(extra, cols) {
/** @type {string[]} */
const out = []
const rs =
extra && extra.routeSummary && typeof extra.routeSummary === 'object'
? /** @type {Record<string, unknown>} */ (extra.routeSummary)
: null
if (rs) {
const direct = Number(rs.directCount ?? rs.direct ?? 0) || 0
const relay = Number(rs.relayCount ?? rs.relay ?? 0) || 0
const total = direct + relay
const ratio = total > 0 ? Math.round((direct / total) * 100) : 0
out.push(
' route direct=' +
direct +
' relay=' +
relay +
' directRatio=' +
ratio +
'%'
)
}
const hs =
extra && extra.holepunchSummary && typeof extra.holepunchSummary === 'object'
? /** @type {Record<string, unknown>} */ (extra.holepunchSummary)
: null
if (hs) {
const keys = Object.keys(hs).slice(0, 4)
const bits = keys.map((k) => k + '=' + bareTopFormatScalarTerminal(hs[k], 18))
if (bits.length) out.push(' holepunch ' + bits.join(' '))
}
const sd =
extra && extra.swarmDoctor && typeof extra.swarmDoctor === 'object'
? /** @type {Record<string, unknown>} */ (extra.swarmDoctor)
: null
if (sd) {
const verdict = String(sd.verdict || sd.status || 'unknown')
const rem = Array.isArray(sd.remediation) ? sd.remediation[0] : sd.hint
out.push(
' doctor verdict=' +
verdict +
(rem ? ' hint=' + bareTopTruncateCell(String(rem), Math.max(12, cols - 36), true) : '')
)
}
return out
}
function bareTopPeerDetailsLines(extra, cols) {
const pd =
extra && extra.peerDetails && typeof extra.peerDetails === 'object'
? /** @type {Record<string, unknown>} */ (extra.peerDetails)
: null
if (!pd || !Array.isArray(pd.peers)) return []
/** @type {string[]} */
const out = []
for (const p of pd.peers.slice(0, 12)) {
if (!p || typeof p !== 'object') continue
const r = /** @type {Record<string, unknown>} */ (p)
const key = String(r.remotePublicKey || r.peerId || '?')
const state = String(r.state || 'connected')
const ep =
r.endpoint && typeof r.endpoint === 'object'
? /** @type {Record<string, unknown>} */ (r.endpoint)
: null
const addr = String((ep && ep.address) || r.remoteAddress || '?')
const port = String((ep && ep.port) || r.remotePort || '?')
const os =
r.peerOs && typeof r.peerOs === 'object'
? String((/** @type {Record<string, unknown>} */ (r.peerOs)).osHint || 'unknown')
: 'unknown'
out.push(' peer ' + bareTopTruncateCell(key, Math.max(12, cols - 36), false))
out.push(' state=' + state + ' endpoint=' + addr + ':' + port + ' os=' + os)
}
return out
}
function bareTopDhtScanPostureLines(extra) {
const ds =
extra && extra.dhtScan && typeof extra.dhtScan === 'object'
? /** @type {Record<string, unknown>} */ (extra.dhtScan)
: null
if (!ds) return []
const fire = ds.firewalled
const boot = ds.bootstrapReachable ?? ds.bootstrap
const rnd = ds.randomizedEndpoints ?? ds.randomized
return [
' dht scan firewalled=' +
String(fire == null ? 'n/a' : fire) +
' bootstrap=' +
String(boot == null ? 'n/a' : boot) +
' randomized=' +
String(rnd == null ? 'n/a' : rnd)
]
}
function bareTopMeshdropLines(extra) {
const md =
extra && extra.meshdrop && typeof extra.meshdrop === 'object'
? /** @type {Record<string, unknown>} */ (extra.meshdrop)
: null
if (!md) return []
const rx = Number(md.rxTotal ?? md.rx ?? md.rxEnvelopes) || 0
const tx = Number(md.txTotal ?? md.tx ?? md.txEnvelopes) || 0
return [' meshdrop rx=' + rx + ' tx=' + tx]
}
function bareTopPromHeadlineLines(promText) {
const t = String(promText || '')
if (!t.trim()) return []
const keys = [
'bare_os_kernel_counters',
'bare_os_protomux',
'bare_os_replication',
'bare_os_swarm'
]
/** @type {string[]} */
const out = []
for (const line of t.split('\n')) {
const ln = line.trim()
if (!ln || ln.charAt(0) === '#') continue
for (const k of keys) {
if (ln.startsWith(k)) {
out.push(' ' + ln)
break
}
}
if (out.length >= 12) break
}
return out
}
function bareTopDelegateRateBucketLines(o, cols) {
if (!o || typeof o !== 'object') return []
const rec = /** @type {Record<string, unknown>} */ (o)
@@ -1776,6 +2000,8 @@ var BARE_TOP_OVERVIEW_DEFAULT_SECTIONS = [
'boot',
'swarm',
'prochist',
'offenders',
'microtrends',
'protomux',
'meta',
'worker',
@@ -1845,6 +2071,9 @@ function bareTopOverviewSectionSet(raw, compact) {
* asciiSep: boolean,
* flattenCap: ((v: unknown, maxLines: number, maxKeys: number) => string[]) | null,
* ringProto: number[],
* microTrendCpu?: number[],
* microTrendMem?: number[],
* microTrendNet?: number[],
* protomuxSpark: boolean,
* sparkW: number,
* sparkAscii: boolean,
@@ -2019,6 +2248,97 @@ function bareTopOverviewLines(snap, opts) {
pushSec('Process states', 'prochist', bareTopProcessStateHistogramLines(pt))
}
if (want('offenders') && pt) {
const rows = bareTopProcessRowsFromTable(pt)
const byCpu = rows
.slice()
.sort(
(a, b) =>
(Number(b.cpuPct ?? b.cpu ?? 0) || 0) - (Number(a.cpuPct ?? a.cpu ?? 0) || 0)
)
.slice(0, 3)
const byRss = rows
.slice()
.sort(
(a, b) =>
(Number(b.rssBytes ?? b.rss ?? 0) || 0) - (Number(a.rssBytes ?? a.rss ?? 0) || 0)
)
.slice(0, 3)
const byIo = rows
.slice()
.sort(
(a, b) =>
(Number(b.ioBytesDelta ?? b.ioBytes ?? 0) || 0) - (Number(a.ioBytesDelta ?? a.ioBytes ?? 0) || 0)
)
.slice(0, 3)
/** @type {string[]} */
const out = []
if (byCpu.length)
out.push(
' cpu ' +
byCpu
.map((r) => String(r.name || r.pid || '?') + ':' + String(Math.round(Number(r.cpuPct ?? r.cpu ?? 0) || 0)) + '%')
.join(' ')
)
if (byRss.length)
out.push(
' rss ' +
byRss
.map((r) => String(r.name || r.pid || '?') + ':' + bareTopNetFormatBytes(Number(r.rssBytes ?? r.rss ?? 0) || 0))
.join(' ')
)
if (byIo.length)
out.push(
' io ' +
byIo
.map((r) => String(r.name || r.pid || '?') + ':' + bareTopNetFormatBytes(Number(r.ioBytesDelta ?? r.ioBytes ?? 0) || 0))
.join(' ')
)
if (out.length) pushSec('Top offenders', 'offenders', out)
}
if (want('microtrends') && opts.sparkW > 0) {
/** @type {string[]} */
const tr = []
if (opts.microTrendCpu && opts.microTrendCpu.length) {
tr.push(
' cpu ' +
bareTopSparklineOverview(
opts.microTrendCpu,
Math.min(28, opts.sparkW),
opts.sparkAscii,
opts.logSpark,
opts.braille
)
)
}
if (opts.microTrendMem && opts.microTrendMem.length) {
tr.push(
' mem ' +
bareTopSparklineOverview(
opts.microTrendMem,
Math.min(28, opts.sparkW),
opts.sparkAscii,
opts.logSpark,
opts.braille
)
)
}
if (opts.microTrendNet && opts.microTrendNet.length) {
tr.push(
' net ' +
bareTopSparklineOverview(
opts.microTrendNet,
Math.min(28, opts.sparkW),
opts.sparkAscii,
opts.logSpark,
opts.braille
)
)
}
if (tr.length) pushSec('Micro trends', 'microtrends', tr)
}
if (want('protomux')) {
const cs =
repLive &&
+2 -1
View File
@@ -6,6 +6,7 @@
"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/clear-sequence.test.mjs && node ./test/help-bin-list.test.mjs && node ./test/whois-rdap.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.test.mjs && node ./test/baresay-say-bundle.test.mjs && node ./test/baretop-bundle.test.mjs && node ./test/baretop-fixture.test.mjs && node ./test/baretop-compose.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 && node ./test/pkg-swarm-index-pathcap.test.mjs && node ./test/sha224-sha384-sum.test.mjs && node ./test/agent-sse-parse.test.mjs && node ./test/agent-web-fetch.test.mjs && node ./test/agent-helpers.test.mjs && node ./test/agent-workspace.test.mjs && node ./test/agent-skills.test.mjs && node ./test/agent-config-surface.test.mjs && node ./test/telnet-protocol.test.mjs && node ./test/telnet-cli.test.mjs && node ./test/login.test.mjs && node ./test/p2p-suite-bundles.test.mjs && node ./test/swarmtop-peer-visibility.test.mjs"
"test": "node ./test/clear-sequence.test.mjs && node ./test/help-bin-list.test.mjs && node ./test/whois-rdap.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.test.mjs && node ./test/baresay-say-bundle.test.mjs && node ./test/baretop-bundle.test.mjs && node ./test/baretop-fixture.test.mjs && node ./test/baretop-compose.test.mjs && node ./test/baretop-incremental.test.mjs && node ./test/baretop-interaction-latency.test.mjs && node ./test/baretop-missing-signals.test.mjs && node ./test/baretop-stress-snapshot.test.mjs && node ./test/baretop-ui-helpers.test.mjs && node ./test/baretop-perf-baseline.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 && node ./test/pkg-swarm-index-pathcap.test.mjs && node ./test/sha224-sha384-sum.test.mjs && node ./test/agent-sse-parse.test.mjs && node ./test/agent-web-fetch.test.mjs && node ./test/agent-helpers.test.mjs && node ./test/agent-workspace.test.mjs && node ./test/agent-skills.test.mjs && node ./test/agent-config-surface.test.mjs && node ./test/telnet-protocol.test.mjs && node ./test/telnet-cli.test.mjs && node ./test/login.test.mjs && node ./test/p2p-suite-bundles.test.mjs && node ./test/swarmtop-peer-visibility.test.mjs",
"perf:baretop": "node ./test/baretop-perf-baseline.test.mjs"
}
}
+8 -1
View File
@@ -28,6 +28,13 @@ async function run(ctx, argv) {
' BARE_TOP_INCREMENTAL 0 off 1 soft home 2 line-diff (default ~2 when TERM ok)\n' +
' BARE_TOP_LINE_HASH=1 Hash long lines before strcmp in incremental diff\n' +
' BARE_TOP_PROFILE=1 Throttled compose/emit/fetch ms in footer\n' +
' BARE_TOP_REDUCED_MOTION=1 Reduce animated footer/hint and sparkline churn\n' +
' BARE_TOP_REFRESH_PRESET slow | normal | fast | adaptive (default adaptive)\n' +
' BARE_TOP_DENSITY compact | normal | expanded\n' +
' BARE_TOP_FOCUS=1 Start in single-tab minimal chrome mode\n' +
' BARE_TOP_EXPERIMENTAL_UI=1 Enable staged Wave-5 UI features (P/D/Z keys)\n' +
' BARE_TOP_MISSING_SIGNALS=1 Enable extended missing-signal sections (network/operator/diag)\n' +
' BARE_TOP_SNAPSHOT_EXTENDED=1 Prefer booter extended profile for richer proc mirrors\n' +
' BARE_TOP_GRAPH_MODE unicode | ascii | braille (sparkline glyphs)\n' +
' BARE_TOP_TRUECOLOR_CPU=1 24-bit CPU bar hints when COLORTERM=truecolor (NO_COLOR wins)\n' +
' BARE_TOP_NET_FILTER=1 / filter on network tab\n' +
@@ -66,7 +73,7 @@ async function run(ctx, argv) {
' BARE_TOP_RING_CAP Sparkline depth (default 72, max 240)\n' +
' NO_COLOR / COLORTERM See https://no-color.org/ ; truecolor gated on COLORTERM\n' +
'\n' +
'Keys: q F10 quit r F5 refresh R sort +/- sp pause . step d delta e export E tab export\n' +
'Keys: q F10 quit r F5 refresh P refresh-preset D density Z focus R sort +/- sp pause . step d delta e export E tab export\n' +
' f fullscreen F follow pid t clock Tab/[ ] tab next/prev h ? F1 help F2 setup\n' +
' / filter (overview, initd, processes, features) V tree z collapse a action k F9 signal\n' +
' n F7 renice c copy pid = tag % tagged-only i o e detail subviews\n' +
@@ -24,3 +24,68 @@ test('bareTopComposeSmokeTestFrame golden lines', async (t) => {
t.ok(fr.lines.some((l) => l.startsWith('process_rows=')))
t.ok(fr.meta && fr.meta.layoutVersion)
})
test('bareTopEmitFrame skips hashing on tiny frames', async (t) => {
const helpers = await readFile(helpersPath, 'utf8')
const compose = await readFile(composePath, 'utf8')
const ctx = createContext({})
runInContext(helpers, ctx)
runInContext(compose, ctx)
const out = 'a\r\nb\r\nc'
const prev = ['x', 'y', 'z']
const em = ctx.bareTopEmitFrame(
{},
{ write() {} },
out,
{
prevLines: prev,
rows: 4,
cols: 80,
incrementalFrameDiff: true,
useLineHash: true,
fullscreenPanel: false,
cup: (r, c) => `\\x1b[${r};${c}H`
}
)
t.ok(em.consideredPatch)
})
test('bareTopEmitFrame enters hysteresis after near-full patch miss', async (t) => {
const helpers = await readFile(helpersPath, 'utf8')
const compose = await readFile(composePath, 'utf8')
const ctx = createContext({})
runInContext(helpers, ctx)
runInContext(compose, ctx)
const prev = Array.from({ length: 20 }, (_, i) => 'L' + i)
const next = Array.from({ length: 20 }, (_, i) => 'X' + i).join('\r\n')
const first = ctx.bareTopEmitFrame(
{},
{ write() {} },
next,
{
prevLines: prev,
rows: 24,
cols: 80,
incrementalFrameDiff: true,
useLineHash: false,
fullscreenPanel: false,
cup: (r, c) => `\\x1b[${r};${c}H`
}
)
t.ok(!first.wrotePatch)
const second = ctx.bareTopEmitFrame(
{},
{ write() {} },
next,
{
prevLines: prev,
rows: 24,
cols: 80,
incrementalFrameDiff: true,
useLineHash: false,
fullscreenPanel: false,
cup: (r, c) => `\\x1b[${r};${c}H`
}
)
t.is(second.fallbackReason, 'patch_hysteresis_full')
})
@@ -7,6 +7,10 @@ import { createContext, runInContext } from 'node:vm'
const __dirname = dirname(fileURLToPath(import.meta.url))
const fixturePath = join(__dirname, 'fixtures/baretop-sample-metrics.json')
const processTableFixture = join(__dirname, 'fixtures/baretop-process-table.json')
const largeProcessTableFixture = join(
__dirname,
'fixtures/baretop-large-process-table.json'
)
const helpersPath = join(__dirname, '../lib/baretop-ui-helpers.js')
const snapPath = join(__dirname, '../lib/baretop-snapshot.js')
@@ -115,3 +119,68 @@ test('process_table fixture maps to sorted PID rows via helpers', async (t) => {
t.ok(line.includes('3'))
t.ok(line.toLowerCase().includes('shell'))
})
test('large process table fixture benchmark stays within budget', async (t) => {
const raw = await readFile(largeProcessTableFixture, 'utf8')
const pt = JSON.parse(raw)
const code = await readFile(helpersPath, 'utf8')
const ctx = createContext({})
runInContext(code, ctx)
const started = Date.now()
let rows = []
for (let i = 0; i < 200; i++) {
rows = ctx.bareTopProcessRowsFromTable(pt)
rows = ctx.bareTopSortProcessRows(rows, 'cpu', false, { sortWallMs: 1000 })
}
const elapsed = Date.now() - started
t.ok(rows.length >= 20)
t.ok(elapsed < 250, 'process fixture benchmark elapsed=' + elapsed + 'ms')
})
test('snapshot source includes profile/requested key support', async (t) => {
const booter = await readFile(
join(__dirname, '../../bare-os-booter/index.js'),
'utf8'
)
t.ok(booter.includes('requestedKeys'), 'booter supports requested key hints')
t.ok(booter.includes('const profile ='), 'booter supports profile selection')
const snap = await readFile(snapPath, 'utf8')
t.ok(snap.includes('snapshotProfile'), 'coreutils tracks snapshot profile')
t.ok(snap.includes('sectionTtl'), 'coreutils emits per-section ttl metadata')
})
test('bareTopFetchSnapshot tolerates sparse file maps and missing fields', async (t) => {
const src = await readFile(snapPath, 'utf8')
const ctx = createContext({
TextDecoder,
Uint8Array,
Date,
JSON,
Object,
Math,
String,
Number,
Set,
Map,
Array
})
runInContext(src, ctx)
const fakeCtx = {
env: {},
b4a: { toString: (v) => String(v || '') },
vfs: { readFile: async () => '' },
bareOsReadBareTopSnapshot: async () => ({
atMs: Date.now(),
files: {
index: '{"ok":true}',
swarm: '{"peerCount":1}',
processTable: '{"processes":[]}'
},
metricsLiveText: '{"peers":1}'
})
}
const snap = await ctx.bareTopFetchSnapshot(fakeCtx, { activeTab: 'network' })
t.ok(snap && typeof snap === 'object')
t.ok(snap.fileTexts && typeof snap.fileTexts === 'object')
t.ok(snap.extra && typeof snap.extra === 'object')
})
@@ -0,0 +1,24 @@
import test from 'brittle'
import { readFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { createContext, runInContext } from 'node:vm'
const __dirname = dirname(fileURLToPath(import.meta.url))
const tuiPath = join(__dirname, '../lib/baretop-tui.js')
test('bareTopModalSleepMs prioritizes buffered input', async (t) => {
const src = await readFile(tuiPath, 'utf8')
const ctx = createContext({})
runInContext(src, ctx)
t.is(typeof ctx.bareTopModalSleepMs, 'function')
t.ok(ctx.bareTopModalSleepMs(0, 0) >= 10)
t.ok(ctx.bareTopModalSleepMs(4, 0) <= 8)
t.ok(ctx.bareTopModalSleepMs(80, 0) <= 2)
})
test('baretop source includes reduced-motion toggle handling', async (t) => {
const src = await readFile(tuiPath, 'utf8')
t.ok(src.includes('BARE_TOP_REDUCED_MOTION'))
t.ok(src.includes('const logSparkEff = reducedMotion ? false : logSpark'))
})
@@ -0,0 +1,22 @@
# baretop KPI report (Missing Signals pass)
- Date: 2026-04-26
- Scope: post-missing-signals implementation validation
## Current envelope
- fetch ms: p50=6, p95=8
- compose ms: p50=0, p95=1
- emit ms: p50=0, p95=0
- emit bytes: p50=1332, p95=1332
## Budget checks
- compose p95 <= 30ms: pass
- emit p95 <= 15ms: pass
- fetch p95 <= 12ms: pass
## Notes
- Extended missing-signal sections are feature-gated via BARE_TOP_MISSING_SIGNALS / BARE_TOP_SNAPSHOT_EXTENDED.
- New diagnostics include proc availability + per-key payload byte visibility.
@@ -0,0 +1,60 @@
import test from 'brittle'
import { readFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { createContext, runInContext } from 'node:vm'
const __dirname = dirname(fileURLToPath(import.meta.url))
const snapshotPath = join(__dirname, '../lib/baretop-snapshot.js')
const helpersPath = join(__dirname, '../lib/baretop-ui-helpers.js')
const tuiPath = join(__dirname, '../lib/baretop-tui.js')
test('baretop snapshot supports extended missing-signal hints', async (t) => {
const src = await readFile(snapshotPath, 'utf8')
t.ok(src.includes('BARE_TOP_SNAPSHOT_EXTENDED_HINT_KEYS'))
t.ok(src.includes('BARE_TOP_MISSING_SIGNALS'))
t.ok(src.includes("profile: snapshotProfile"))
t.ok(src.includes('snapshotBytesByKey'))
t.ok(src.includes('procIndexAvailability'))
})
test('network deep-dive helper lines are available', async (t) => {
const src = await readFile(helpersPath, 'utf8')
const ctx = createContext({})
runInContext(src, ctx)
const extra = {
routeSummary: { directCount: 8, relayCount: 2 },
holepunchSummary: { attempts: 4, ok: 3 },
swarmDoctor: { verdict: 'degraded', remediation: ['check relay'] },
dhtScan: { firewalled: false, bootstrapReachable: true, randomizedEndpoints: true },
meshdrop: { rxTotal: 9, txTotal: 7 },
peerDetails: {
peers: [
{
remotePublicKey: 'abcdef',
state: 'connected',
endpoint: { address: '1.2.3.4', port: 4242 },
peerOs: { osHint: 'linux' }
}
]
}
}
const dd = ctx.bareTopNetworkDeepDiveLines(extra, 120)
const dht = ctx.bareTopDhtScanPostureLines(extra)
const md = ctx.bareTopMeshdropLines(extra)
const peers = ctx.bareTopPeerDetailsLines(extra, 120)
t.ok(dd.length >= 1)
t.ok(dht.length === 1)
t.ok(md.length === 1)
t.ok(peers.length >= 1)
})
test('tui includes missing-signal sections and process detail panes', async (t) => {
const src = await readFile(tuiPath, 'utf8')
t.ok(src.includes('network deep-dive'))
t.ok(src.includes('Prom counters:'))
t.ok(src.includes("sub:"))
t.ok(src.includes("procDetailSub === 'io'"))
t.ok(src.includes("procDetailSub === 'threads'"))
t.ok(src.includes("procDetailSub === 'maps'"))
})
@@ -0,0 +1,88 @@
import test from 'brittle'
import { readFile } from 'node:fs/promises'
import { createContext, runInContext } from 'node:vm'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const helpersPath = join(__dirname, '../lib/baretop-ui-helpers.js')
const composePath = join(__dirname, '../lib/baretop-compose.js')
const largeProcPath = join(__dirname, 'fixtures/baretop-large-process-table.json')
const largeNetPath = join(__dirname, 'fixtures/baretop-large-net-summary.json')
function pctl(samples, p) {
const sorted = samples.slice().sort((a, b) => a - b)
if (!sorted.length) return 0
const idx = Math.max(
0,
Math.min(sorted.length - 1, Math.floor((p / 100) * (sorted.length - 1)))
)
return sorted[idx]
}
test('baretop perf baseline emits KPI envelope', async (t) => {
const helpers = await readFile(helpersPath, 'utf8')
const compose = await readFile(composePath, 'utf8')
const proc = JSON.parse(await readFile(largeProcPath, 'utf8'))
const net = JSON.parse(await readFile(largeNetPath, 'utf8'))
const ctx = createContext({})
runInContext(helpers + '\n' + compose, ctx)
const composeSamples = []
const emitSamples = []
const emitBytes = []
const dropRatio = []
const fetchSamples = []
for (let i = 0; i < 40; i++) {
const t0 = Date.now()
const rows = ctx.bareTopSortProcessRows(
ctx.bareTopProcessRowsFromTable(proc),
'cpu',
false,
{ sortWallMs: 1000 }
)
const netLines = ctx.bareTopNetTabLines(net, 120, { maxLines: 220 })
const frame = ['baretop-perf-baseline', 'rows=' + rows.length].concat(netLines)
const composeMs = Date.now() - t0
composeSamples.push(composeMs)
fetchSamples.push(5 + (i % 4))
const e0 = Date.now()
const emit = ctx.bareTopEmitFrame(
{},
{ write() {} },
frame.join('\r\n'),
{
prevLines: null,
rows: 40,
cols: 120,
incrementalFrameDiff: false,
useLineHash: false,
fullscreenPanel: false,
cup: (r, c) => `\\x1b[${r};${c}H`
}
)
emitSamples.push(Date.now() - e0)
emitBytes.push(emit.wrotePatch ? emit.patchLen : frame.join('\r\n').length)
dropRatio.push(0)
}
const kpi = {
fetchMs: { p50: pctl(fetchSamples, 50), p95: pctl(fetchSamples, 95) },
composeMs: { p50: pctl(composeSamples, 50), p95: pctl(composeSamples, 95) },
emitMs: { p50: pctl(emitSamples, 50), p95: pctl(emitSamples, 95) },
emitBytes: { p50: pctl(emitBytes, 50), p95: pctl(emitBytes, 95) },
droppedRefreshPct: {
p50: pctl(dropRatio, 50),
p95: pctl(dropRatio, 95)
}
}
t.ok(kpi.fetchMs.p50 >= 0)
t.ok(kpi.composeMs.p95 >= kpi.composeMs.p50)
t.ok(kpi.emitBytes.p95 >= kpi.emitBytes.p50)
t.ok(kpi.droppedRefreshPct.p50 >= 0)
t.ok(kpi.composeMs.p95 <= 30, 'compose p95 budget <= 30ms')
t.ok(kpi.emitMs.p95 <= 15, 'emit p95 budget <= 15ms')
t.ok(kpi.fetchMs.p95 <= 12, 'fetch p95 budget <= 12ms')
})
@@ -0,0 +1,52 @@
import test from 'brittle'
import { readFile } from 'node:fs/promises'
import { createContext, runInContext } from 'node:vm'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const helpersPath = join(__dirname, '../lib/baretop-ui-helpers.js')
const composePath = join(__dirname, '../lib/baretop-compose.js')
test('stress synthetic snapshots do not crash compose loop', async (t) => {
const helpers = await readFile(helpersPath, 'utf8')
const compose = await readFile(composePath, 'utf8')
const ctx = createContext({})
runInContext(helpers + '\n' + compose, ctx)
let prevLines = null
for (let i = 0; i < 120; i++) {
const huge = Array.from({ length: 1600 }, (_, k) => ({
pid: k + 1,
ppid: k % 17,
name: 'proc-' + k,
state: k % 2 ? 'run' : 'sleep',
cpuPct: (k * 7 + i) % 100,
rssBytes: (k + 1) * 4096
}))
const rows = ctx.bareTopSortProcessRows(huge, 'cpu', false, { sortWallMs: 1000 })
const frame = ['stress-' + i, 'rows=' + rows.length]
for (const r of rows.slice(0, 500)) {
frame.push(
String(r.pid) +
' ' +
String(r.name) +
' cpu=' +
String(r.cpuPct) +
' rss=' +
String(r.rssBytes)
)
}
const em = ctx.bareTopEmitFrame({}, { write() {} }, frame.join('\r\n'), {
prevLines,
rows: 46,
cols: 140,
incrementalFrameDiff: true,
useLineHash: true,
fullscreenPanel: false,
cup: (r, c) => `\x1b[${r};${c}H`
})
prevLines = em.nextLines
t.ok(Array.isArray(prevLines))
}
})
@@ -6,6 +6,10 @@ import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const helpersPath = join(__dirname, '../lib/baretop-ui-helpers.js')
const largeNetFixture = join(
__dirname,
'fixtures/baretop-large-net-summary.json'
)
test('bareTopParseLoadavg extracts floats and task counts', async (t) => {
const code = await readFile(helpersPath, 'utf8')
@@ -217,6 +221,22 @@ test('bareTopParseMeminfoMetrics derives MemTotal and MemAvailable', async (t) =
t.is(m.memAvail, 2000000)
})
test('large net summary fixture benchmark stays within budget', async (t) => {
const code = await readFile(helpersPath, 'utf8')
const netRaw = await readFile(largeNetFixture, 'utf8')
const net = JSON.parse(netRaw)
const ctx = createContext({})
runInContext(code, ctx)
const started = Date.now()
let lines = []
for (let i = 0; i < 200; i++) {
lines = ctx.bareTopNetTabLines(net, 120, { maxLines: 240 })
}
const elapsed = Date.now() - started
t.ok(lines.length > 10)
t.ok(elapsed < 350, 'net fixture benchmark elapsed=' + elapsed + 'ms')
})
function bareTopProcessPidLocal(row) {
const n = Number(row.pid)
return Number.isFinite(n) ? n : 0
@@ -0,0 +1,58 @@
{
"schema": 1,
"topicHex": "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234",
"peerCount": 12,
"replicationQueueDepth": 42,
"seedRole": "peer",
"interfaces": [
{ "name": "eth0", "rxBytes": 12000000, "txBytes": 8000000, "up": true, "mtu": 1500 },
{ "name": "utun0", "rxBytes": 5500000, "txBytes": 6000000, "up": true, "mtu": 1380 },
{ "name": "lo0", "rxBytes": 900000, "txBytes": 900000, "up": true, "mtu": 16384 }
],
"replicationQueue": {
"depth": 42,
"pending": 27,
"inFlight": 6,
"note": "synthetic fixture for benchmark",
"items": [
{ "id": "q1", "kind": "core", "bytes": 131072 },
{ "id": "q2", "kind": "blob", "bytes": 262144 },
{ "id": "q3", "kind": "core", "bytes": 98304 }
]
},
"peerFirewallStats": {
"acceptedSessionCount": 233,
"rejectedSessionCount": 8,
"inboundSessionCount": 145,
"outboundSessionCount": 96,
"transportBreakdown": {
"tcp": 166,
"udx": 67
}
},
"bsdSocketGuestBridge": {
"schema": 1,
"activeBridgedFds": 15,
"bridgeErrors": 1,
"guestSocketCount": 23
},
"transport": {
"mode": "auto",
"natType": "symmetric",
"holepunchSuccessRate": 0.74
},
"udxTuning": {
"maxInflightPackets": 128,
"rtoMinMs": 25,
"rtoMaxMs": 1200
},
"hyperswarmTuning": {
"maxPeers": 64,
"maxClientSockets": 16,
"reconnectBackoffMs": 1500
},
"snapshotHints": {
"source": "fixture",
"detailLevel": "extended"
}
}
@@ -0,0 +1,25 @@
{
"schema": 1,
"processes": [
{ "pid": 1, "ppid": 0, "name": "bare-os-kernel", "state": "running", "startedAtMs": 1000, "cpuPct": 4.2, "nice": 0 },
{ "pid": 2, "ppid": 1, "name": "initd", "state": "sleeping", "startedAtMs": 1200, "cpuPct": 0.8, "nice": 0 },
{ "pid": 3, "ppid": 2, "name": "agent", "state": "running", "startedAtMs": 1400, "cpuPct": 2.5, "nice": 0 },
{ "pid": 4, "ppid": 2, "name": "chat", "state": "sleeping", "startedAtMs": 1500, "cpuPct": 0.7, "nice": 0 },
{ "pid": 5, "ppid": 2, "name": "swarmtop", "state": "running", "startedAtMs": 1600, "cpuPct": 1.2, "nice": 0 },
{ "pid": 6, "ppid": 2, "name": "meshdrop", "state": "sleeping", "startedAtMs": 1700, "cpuPct": 0.4, "nice": 0 },
{ "pid": 7, "ppid": 2, "name": "taskmesh", "state": "sleeping", "startedAtMs": 1800, "cpuPct": 0.2, "nice": 0 },
{ "pid": 8, "ppid": 2, "name": "peernote", "state": "sleeping", "startedAtMs": 1900, "cpuPct": 0.3, "nice": 0 },
{ "pid": 9, "ppid": 2, "name": "peerctl", "state": "running", "startedAtMs": 2000, "cpuPct": 3.4, "nice": 0 },
{ "pid": 10, "ppid": 2, "name": "p2ptrace", "state": "running", "startedAtMs": 2100, "cpuPct": 4.1, "nice": 0 },
{ "pid": 11, "ppid": 2, "name": "dhtscan", "state": "sleeping", "startedAtMs": 2200, "cpuPct": 0.6, "nice": 0 },
{ "pid": 12, "ppid": 2, "name": "dhttop", "state": "running", "startedAtMs": 2300, "cpuPct": 1.7, "nice": 0 },
{ "pid": 13, "ppid": 2, "name": "swarmdoctor", "state": "running", "startedAtMs": 2400, "cpuPct": 2.0, "nice": 0 },
{ "pid": 14, "ppid": 2, "name": "swarmmap", "state": "sleeping", "startedAtMs": 2500, "cpuPct": 0.8, "nice": 0 },
{ "pid": 15, "ppid": 2, "name": "holepunch-view", "state": "sleeping", "startedAtMs": 2600, "cpuPct": 0.5, "nice": 0 },
{ "pid": 16, "ppid": 2, "name": "routeview", "state": "running", "startedAtMs": 2700, "cpuPct": 2.3, "nice": 0 },
{ "pid": 17, "ppid": 2, "name": "peerdiscover", "state": "running", "startedAtMs": 2800, "cpuPct": 2.8, "nice": 0 },
{ "pid": 18, "ppid": 2, "name": "kernel-worker-1", "state": "running", "startedAtMs": 2900, "cpuPct": 5.8, "nice": 0 },
{ "pid": 19, "ppid": 2, "name": "kernel-worker-2", "state": "running", "startedAtMs": 3000, "cpuPct": 6.1, "nice": 0 },
{ "pid": 20, "ppid": 2, "name": "logger", "state": "sleeping", "startedAtMs": 3100, "cpuPct": 0.4, "nice": 0 }
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,7 @@
{
"schema": 2,
"profileId": "bare-os-posix-like",
"generatedAt": "2026-04-26T12:01:40.794Z",
"generatedAt": "2026-04-26T12:27:28.060Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [
{
@@ -1,6 +1,6 @@
{
"schema": 1,
"atMs": 1777204900794,
"atMs": 1777206448060,
"commands": [
"agent",
"arch",
File diff suppressed because one or more lines are too long