658 lines
20 KiB
JavaScript
658 lines
20 KiB
JavaScript
/** 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][]}
|
||
*/
|
||
var BARE_TOP_SNAPSHOT_PROC_ENTRIES = [
|
||
['index', '/proc/bare_os/index.json'],
|
||
['version', '/proc/bare_os/version'],
|
||
['hostOs', '/proc/bare_os/host_os.json'],
|
||
['debug', '/proc/bare_os/debug.json'],
|
||
['sessionStats', '/proc/bare_os/session_stats'],
|
||
['quotas', '/proc/bare_os/quotas'],
|
||
['capabilitiesJson', '/proc/bare_os/capabilities.json'],
|
||
['capabilitiesNode', '/proc/bare_os/capabilities'],
|
||
['seedHandshake', '/proc/bare_os/seed_handshake'],
|
||
['bootstrap', '/proc/bare_os/bootstrap'],
|
||
['provenance', '/proc/bare_os/provenance'],
|
||
['snapshotHints', '/proc/bare_os/snapshot_hints.json'],
|
||
['stagingSlot', '/proc/bare_os/staging_slot'],
|
||
['peerHealth', '/proc/bare_os/peer_health'],
|
||
['pearIpc', '/proc/bare_os/pear_ipc.json'],
|
||
['pearIpcHealth', '/proc/bare_os/pear_ipc_health.json'],
|
||
['pearTrust', '/proc/bare_os/pear_trust.json'],
|
||
['replication', '/proc/bare_os/replication'],
|
||
['replicationBackpressure', '/proc/bare_os/replication_backpressure.json'],
|
||
['swarm', '/proc/bare_os/swarm'],
|
||
['syncWindow', '/proc/bare_os/sync_window.json'],
|
||
['clock', '/proc/bare_os/clock.json'],
|
||
['openssh', '/proc/bare_os/openssh.json'],
|
||
['hdmsHealth', '/proc/bare_os/hdms_health.json'],
|
||
['hdmsHints', '/proc/bare_os/hdms_hints.json'],
|
||
['dhtStatus', '/proc/bare_os/dht_status.json'],
|
||
['udxExtended', '/proc/bare_os/udx_extended.json'],
|
||
['ipcBackpressure', '/proc/bare_os/ipc_backpressure.json'],
|
||
['delegateRed', '/proc/bare_os/delegate_red.json'],
|
||
['gitDelegateStats', '/proc/bare_os/git_delegate_stats.json'],
|
||
['kernelProgram', '/proc/bare_os/kernel_program.json'],
|
||
['gitLfsPointerStats', '/proc/bare_os/git_lfs_pointer_stats.json'],
|
||
['workerBudget', '/proc/bare_os/worker_budget.json'],
|
||
['sandboxProfile', '/proc/bare_os/sandbox_profile.json'],
|
||
['rlimits', '/proc/bare_os/rlimits.json'],
|
||
['extensions', '/proc/bare_os/extensions.json'],
|
||
['initdDag', '/proc/bare_os/initd_dag.json'],
|
||
['bootGraph', '/proc/bare_os/boot_graph.json'],
|
||
[
|
||
'bootBudgetSummary',
|
||
'/proc/bare_os/boot_budget_summary.json'
|
||
],
|
||
['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'],
|
||
['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']
|
||
]
|
||
|
||
/** Subset for BARE_TOP_SNAPSHOT_LITE=1 — keep keys subset of full list above. */
|
||
var BARE_TOP_SNAPSHOT_LITE_ENTRIES = [
|
||
['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']
|
||
]
|
||
|
||
/** @param {unknown} buf @param {unknown} b4a @returns {string} */
|
||
function bareTopBufToString(buf, b4a) {
|
||
if (buf == null) return ''
|
||
if (typeof buf === 'string') return buf
|
||
try {
|
||
if (b4a && typeof b4a.toString === 'function') return b4a.toString(buf)
|
||
} catch {
|
||
/* fall through */
|
||
}
|
||
try {
|
||
return new TextDecoder().decode(
|
||
buf instanceof Uint8Array ? buf : new Uint8Array(/** @type {ArrayLike<number>} */ (buf))
|
||
)
|
||
} catch {
|
||
return ''
|
||
}
|
||
}
|
||
|
||
/** @param {string} s @returns {unknown} */
|
||
function bareTopJsonParse(s) {
|
||
const t = String(s || '').trim()
|
||
if (!t) return null
|
||
try {
|
||
return JSON.parse(t)
|
||
} catch {
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {Record<string, unknown>} ctx
|
||
* @param {string} path
|
||
* @returns {Promise<string>}
|
||
*/
|
||
async function bareTopReadProc(ctx, path) {
|
||
const vfs = ctx.vfs
|
||
if (!vfs || typeof vfs.readFile !== 'function') return ''
|
||
try {
|
||
const buf = await vfs.readFile(path)
|
||
return bareTopBufToString(buf, ctx.b4a)
|
||
} catch {
|
||
return ''
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {Record<string, unknown>} ctx
|
||
* @param {readonly [string, string][]} entries
|
||
* @param {number} concurrency
|
||
* @returns {Promise<Record<string, string>>}
|
||
*/
|
||
/**
|
||
* @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]) => {
|
||
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)
|
||
}
|
||
})
|
||
)
|
||
}
|
||
return out
|
||
}
|
||
|
||
/**
|
||
* @param {unknown} o
|
||
* @param {number} maxChars
|
||
* @returns {string}
|
||
*/
|
||
function bareTopTruncateJsonPretty(o, maxChars) {
|
||
const raw = JSON.stringify(o, null, 2)
|
||
if (raw.length <= maxChars) return raw
|
||
return raw.slice(0, Math.max(0, maxChars - 24)) + '\n… (truncated) …\n'
|
||
}
|
||
|
||
/** @param {number} n @returns {string} */
|
||
function bareTopFormatBytes(n) {
|
||
const x = Number(n)
|
||
if (!Number.isFinite(x) || x < 0) return '—'
|
||
if (x < 1024) return String(Math.floor(x)) + ' B'
|
||
if (x < 1048576) return (x / 1024).toFixed(1) + ' KiB'
|
||
if (x < 1073741824) return (x / 1048576).toFixed(1) + ' MiB'
|
||
return (x / 1073741824).toFixed(2) + ' GiB'
|
||
}
|
||
|
||
/** @param {number} ms @returns {string} */
|
||
function bareTopFormatDuration(ms) {
|
||
const x = Number(ms)
|
||
if (!Number.isFinite(x) || x < 0) return '—'
|
||
if (x < 1000) return String(Math.floor(x)) + ' ms'
|
||
if (x < 60000) return (x / 1000).toFixed(1) + ' s'
|
||
if (x < 3600000) return Math.floor(x / 60000) + ' m'
|
||
return (x / 3600000).toFixed(1) + ' h'
|
||
}
|
||
|
||
/**
|
||
* @param {Record<string, unknown> | null} m metrics live
|
||
* @param {number} peers
|
||
* @param {string | null | undefined} readErr optional snapshot read failure
|
||
* @returns {number} 0–100
|
||
*/
|
||
function bareTopHealthScore(m, peers, readErr) {
|
||
return bareTopHealthScoreDetail(m, peers, readErr).score
|
||
}
|
||
|
||
/**
|
||
* 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
|
||
* @returns {{ score: number, breakdown: string }}
|
||
*/
|
||
function bareTopHealthScoreDetail(m, peers, readErr) {
|
||
let score = 100
|
||
/** @type {string[]} */
|
||
const parts = ['base=100']
|
||
|
||
const errS = readErr != null ? String(readErr).trim() : ''
|
||
if (errS) {
|
||
const pen = 28
|
||
score -= pen
|
||
parts.push('read_err=-' + pen)
|
||
}
|
||
|
||
const live =
|
||
m && typeof m === 'object'
|
||
? /** @type {Record<string, unknown>} */ (m)
|
||
: null
|
||
if (!live) {
|
||
const pen = 22
|
||
score -= pen
|
||
parts.push('no_metrics=-' + pen)
|
||
} else {
|
||
const delI = live.delegateInflight
|
||
if (delI != null && typeof delI === 'object') {
|
||
const o = /** @type {Record<string, unknown>} */ (delI)
|
||
const keys = Object.keys(o)
|
||
let nActive = 0
|
||
let sum = 0
|
||
for (let i = 0; i < keys.length; i++) {
|
||
const v = Math.max(0, Number(o[keys[i]]) || 0)
|
||
sum += v
|
||
if (v > 0) nActive++
|
||
}
|
||
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 +
|
||
')'
|
||
)
|
||
}
|
||
}
|
||
|
||
const rep =
|
||
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
|
||
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 && !benignStallHints.has(hs)) {
|
||
const pen = 20
|
||
score -= pen
|
||
parts.push('stall_hint=-' + pen + '(' + hs + ')')
|
||
}
|
||
}
|
||
|
||
const cold =
|
||
live.bootBudgetCold && typeof live.bootBudgetCold === 'object'
|
||
? /** @type {Record<string, unknown>} */ (live.bootBudgetCold)
|
||
: null
|
||
if (cold && cold.exceeded === true) {
|
||
score -= 12
|
||
parts.push('boot_cold=-12')
|
||
}
|
||
const std =
|
||
live.bootBudgetBareStdlib &&
|
||
typeof live.bootBudgetBareStdlib === 'object'
|
||
? /** @type {Record<string, unknown>} */ (live.bootBudgetBareStdlib)
|
||
: null
|
||
if (std && std.exceeded === true) {
|
||
score -= 12
|
||
parts.push('boot_stdlib=-12')
|
||
}
|
||
|
||
const ir = live.initdReadiness
|
||
if (ir && typeof ir === 'object') {
|
||
const failed = Number(/** @type {Record<string, unknown>} */ (ir).failed)
|
||
if (Number.isFinite(failed) && failed > 0) {
|
||
const pen = Math.min(18, failed * 6)
|
||
score -= pen
|
||
parts.push('initd_failed_' + failed + '=-' + pen)
|
||
}
|
||
}
|
||
|
||
const kc = live.kernelCounters
|
||
if (kc && typeof kc === 'object') {
|
||
const kf = Number(
|
||
/** @type {Record<string, unknown>} */ (kc)['initd.unit_failed_final']
|
||
)
|
||
if (Number.isFinite(kf) && kf > 0) {
|
||
const pen = Math.min(10, kf * 2)
|
||
score -= pen
|
||
parts.push('initd_kc_' + kf + '=-' + pen)
|
||
}
|
||
}
|
||
|
||
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')
|
||
}
|
||
}
|
||
}
|
||
|
||
if (peers < 0) {
|
||
score -= 5
|
||
parts.push('peers_invalid=-5')
|
||
}
|
||
|
||
if (score < 0) score = 0
|
||
if (score > 100) score = 100
|
||
parts.push('=>' + score)
|
||
return { score, breakdown: parts.join(' ') }
|
||
}
|
||
|
||
/**
|
||
* @param {number} prev
|
||
* @param {number} value
|
||
* @param {number} alpha 0–1
|
||
* @returns {number}
|
||
*/
|
||
function bareTopEma(prev, value, alpha) {
|
||
const a = Math.min(1, Math.max(0, alpha))
|
||
return prev * (1 - a) + value * a
|
||
}
|
||
|
||
/**
|
||
* @param {Record<string, unknown>} ctx
|
||
* @param {Record<string, string>} files
|
||
* @param {string} key
|
||
* @returns {Record<string, unknown> | null}
|
||
*/
|
||
function bareTopParsedFile(files, key) {
|
||
const t = files[key]
|
||
if (!t) return null
|
||
const p = bareTopJsonParse(t)
|
||
return p && typeof p === 'object'
|
||
? /** @type {Record<string, unknown>} */ (p)
|
||
: null
|
||
}
|
||
|
||
/**
|
||
* @param {Record<string, unknown>} ctx
|
||
* @returns {Promise<Record<string, unknown>>}
|
||
*/
|
||
/** One coalesced operator frame; keep VFS read fan-out bounded (batch size + concurrency). */
|
||
async function bareTopFetchSnapshot(ctx, fetchOpts) {
|
||
const fetchStart = Date.now()
|
||
let readErr = null
|
||
const env =
|
||
ctx.env && typeof ctx.env === 'object'
|
||
? /** @type {Record<string, string>} */ (ctx.env)
|
||
: {}
|
||
const fo =
|
||
fetchOpts && typeof fetchOpts === 'object'
|
||
? /** @type {{ forceLite?: boolean }} */ (fetchOpts)
|
||
: null
|
||
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 =
|
||
fo && fo.forceLite === true
|
||
? true
|
||
: env.BARE_TOP_SNAPSHOT_LITE === '1' ||
|
||
env.BARE_TOP_SNAPSHOT_LITE === 'true'
|
||
const procEntryList = snapshotLite
|
||
? BARE_TOP_SNAPSHOT_LITE_ENTRIES
|
||
: BARE_TOP_SNAPSHOT_PROC_ENTRIES
|
||
|
||
/** @type {Record<string, string>} */
|
||
let fileTexts = {}
|
||
let fastAtMs = 0
|
||
/** @type {Record<string, unknown> | null} */
|
||
let metricsLiveFromSnap = null
|
||
|
||
try {
|
||
if (typeof ctx.bareOsReadBareTopSnapshot === 'function') {
|
||
const r = await ctx.bareOsReadBareTopSnapshot(
|
||
snapshotLite ? { lite: true } : undefined
|
||
)
|
||
if (r && typeof r === 'object') {
|
||
const o = /** @type {Record<string, unknown>} */ (r)
|
||
const files = o.files
|
||
if (files && typeof files === 'object') {
|
||
for (const k of Object.keys(files)) {
|
||
const v = files[k]
|
||
if (typeof v === 'string') fileTexts[k] = v
|
||
}
|
||
}
|
||
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) {
|
||
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 = metricsLiveFromSnap
|
||
/** @type {Record<string, unknown> | null} */
|
||
let resources = null
|
||
/** @type {Record<string, unknown> | null} */
|
||
let features = null
|
||
/** @type {Record<string, unknown> | null} */
|
||
let netSummary = null
|
||
/** @type {unknown} */
|
||
let initdGraph = null
|
||
/** @type {Record<string, unknown> | null} */
|
||
let fairnessSnapshot = null
|
||
/** @type {unknown} */
|
||
let subprocessBridge = null
|
||
/** @type {Record<string, unknown> | null} */
|
||
let hostStats = null
|
||
|
||
try {
|
||
if (typeof ctx.bareOsReadProcMetricsLive === 'function') {
|
||
const o = ctx.bareOsReadProcMetricsLive()
|
||
if (o && typeof o === 'object') metricsLive = /** @type {Record<string, unknown>} */ (o)
|
||
}
|
||
} catch (e) {
|
||
if (!readErr)
|
||
readErr = (e && /** @type {{ message?: string }} */ (e).message) || String(e)
|
||
}
|
||
|
||
if (!metricsLive) {
|
||
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)
|
||
}
|
||
|
||
try {
|
||
if (typeof ctx.bareOsGetResourceStatus === 'function') {
|
||
const o = ctx.bareOsGetResourceStatus()
|
||
if (o && typeof o === 'object') resources = /** @type {Record<string, unknown>} */ (o)
|
||
}
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
|
||
if (!resources) {
|
||
const t = await bareTopReadProc(ctx, '/proc/bare_os_resources')
|
||
const p = bareTopJsonParse(t)
|
||
if (p && typeof p === 'object') resources = /** @type {Record<string, unknown>} */ (p)
|
||
}
|
||
|
||
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)
|
||
|
||
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)
|
||
|
||
const initT = await bareTopReadProc(ctx, '/proc/bare_os/initd_graph.json')
|
||
initdGraph = bareTopJsonParse(initT)
|
||
if (initdGraph === null) {
|
||
const alt = bareTopJsonParse(fileTexts.initdDag || '')
|
||
initdGraph = alt
|
||
}
|
||
|
||
try {
|
||
if (typeof ctx.bareOsReadDelegateFairnessSnapshot === 'function') {
|
||
const o = ctx.bareOsReadDelegateFairnessSnapshot()
|
||
if (o && typeof o === 'object')
|
||
fairnessSnapshot = /** @type {Record<string, unknown>} */ (o)
|
||
}
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
|
||
try {
|
||
if (typeof ctx.bareOsReadSubprocessBridgeSnapshot === 'function') {
|
||
subprocessBridge = ctx.bareOsReadSubprocessBridgeSnapshot()
|
||
}
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
|
||
try {
|
||
const hs = ctx.bareOsHostStats
|
||
if (hs && typeof hs === 'object') hostStats = /** @type {Record<string, unknown>} */ (hs)
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
|
||
const memRaw = await bareTopReadProc(ctx, '/proc/meminfo')
|
||
let meminfoLine = ''
|
||
for (const line of memRaw.split('\n')) {
|
||
const L = line.trim()
|
||
if (L.startsWith('MemTotal:') || L.startsWith('MemAvailable:')) {
|
||
meminfoLine = L
|
||
break
|
||
}
|
||
}
|
||
if (!meminfoLine) meminfoLine = memRaw.split('\n')[0]?.trim() || ''
|
||
|
||
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)
|
||
}
|
||
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
|
||
|
||
/** @type {Record<string, Record<string, unknown> | null>} */
|
||
const extra = {}
|
||
for (const [key] of procEntryList) {
|
||
if (
|
||
key === 'processTable' &&
|
||
metricsLive &&
|
||
metricsLive.processTable &&
|
||
typeof metricsLive.processTable === 'object'
|
||
) {
|
||
extra[key] = /** @type {Record<string, unknown>} */ (
|
||
metricsLive.processTable
|
||
)
|
||
continue
|
||
}
|
||
extra[key] = bareTopParsedFile(fileTexts, key)
|
||
}
|
||
|
||
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)
|
||
|
||
return {
|
||
metricsLive,
|
||
resources,
|
||
features,
|
||
initdGraph,
|
||
netSummary,
|
||
meminfoLine,
|
||
swapinfoLine,
|
||
loadavgLine,
|
||
cpuLine,
|
||
cpuCoreCount,
|
||
diskstatsLine,
|
||
diskstatsRaw,
|
||
hostOs,
|
||
atMs,
|
||
metaAtMs: metaAt,
|
||
readErr,
|
||
readErrPaths,
|
||
fileTexts,
|
||
extra,
|
||
fairnessSnapshot,
|
||
subprocessBridge,
|
||
hostStats,
|
||
fetchWallMs,
|
||
healthScore: healthD.score,
|
||
healthBreakdown: healthD.breakdown,
|
||
snapshotLite
|
||
}
|
||
}
|