928 lines
28 KiB
JavaScript
928 lines
28 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
|
||
/** 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
|
||
* @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']
|
||
]
|
||
|
||
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 ''
|
||
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
|
||
}
|
||
|
||
/**
|
||
* 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
|
||
* @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, activeTab?: string }} */ (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 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, requestedKeys }
|
||
: {
|
||
profile: snapshotProfile,
|
||
requestedKeys
|
||
}
|
||
)
|
||
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
|
||
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)
|
||
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)
|
||
}
|
||
if (bareTopLastFallbackConc > 0) {
|
||
if (useConc > bareTopLastFallbackConc + 1) useConc = bareTopLastFallbackConc + 1
|
||
if (useConc < bareTopLastFallbackConc - 1) useConc = bareTopLastFallbackConc - 1
|
||
}
|
||
bareTopLastFallbackConc = useConc
|
||
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
|
||
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') {
|
||
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 ml = await bareTopCachedSecondary(
|
||
'metrics_live',
|
||
ttl.metricsLive,
|
||
async () => {
|
||
const t = await bareTopReadProc(ctx, '/proc/bare_os/metrics_live.json')
|
||
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 {
|
||
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 rs = await bareTopCachedSecondary(
|
||
'resources',
|
||
ttl.resources,
|
||
async () => {
|
||
const t = await bareTopReadProc(ctx, '/proc/bare_os_resources')
|
||
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'))
|
||
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')
|
||
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')
|
||
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
|
||
}
|
||
|
||
const fa = await bareTopCachedSecondary('fairness_snapshot', ttl.fairness, async () => {
|
||
if (typeof ctx.bareOsReadDelegateFairnessSnapshot === 'function') {
|
||
return ctx.bareOsReadDelegateFairnessSnapshot()
|
||
}
|
||
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)
|
||
}
|
||
|
||
const sb = await bareTopCachedSecondary(
|
||
'subprocess_bridge',
|
||
ttl.subprocess,
|
||
async () => {
|
||
if (typeof ctx.bareOsReadSubprocessBridgeSnapshot === 'function') {
|
||
return ctx.bareOsReadSubprocessBridgeSnapshot()
|
||
}
|
||
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)
|
||
}
|
||
|
||
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 = typeof memC.value === 'string' ? memC.value : ''
|
||
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 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 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)
|
||
}
|
||
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 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
|
||
|
||
/** @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)
|
||
}
|
||
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 =
|
||
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,
|
||
snapshotBatchBytes,
|
||
snapshotBytesByKey,
|
||
sectionTtl,
|
||
snapshotProfile,
|
||
snapshotRequestedKeys: requestedKeys,
|
||
healthScore: healthD.score,
|
||
healthBreakdown: healthD.breakdown,
|
||
snapshotLite
|
||
}
|
||
}
|