CPU and state topology is cached for 30s.
CI / test (push) Has been cancelled
Release rolling / release (push) Has been cancelled

This commit is contained in:
Raven Scott
2026-07-21 13:42:34 -04:00
parent 26298fb8d4
commit 47bd72f5b6
2 changed files with 61 additions and 31 deletions
+21 -12
View File
@@ -216,6 +216,7 @@ export class CgroupsCollector extends EventEmitter {
/** @type {Array<{ id: string, title: string, path: string }>|null} */ /** @type {Array<{ id: string, title: string, path: string }>|null} */
this._cgCache = null this._cgCache = null
this._cgTs = 0 this._cgTs = 0
this._cgTick = 0
} }
start() { start() {
@@ -285,6 +286,9 @@ export class CgroupsCollector extends EventEmitter {
} }
} }
const cgList = this._cgCache const cgList = this._cgCache
const doMemDetail = this._cgTick % 5 === 0
const doPressure = this._cgTick % 3 === 0
this._cgTick++
for (const cg of cgList) { for (const cg of cgList) {
const title = resolveContainerLabel(cg.title, this._names) const title = resolveContainerLabel(cg.title, this._names)
@@ -347,7 +351,10 @@ export class CgroupsCollector extends EventEmitter {
}, },
}) })
const memStat = profile('memStat:' + cg.id.slice(0,20), () => parseMemoryStat(cg.path)) let memStat = null
if (doMemDetail) {
memStat = profile('memStat:' + cg.id.slice(0,20), () => parseMemoryStat(cg.path))
}
if (memStat) { if (memStat) {
batch.push({ batch.push({
chart: memDetailDef.id, chart: memDetailDef.id,
@@ -374,17 +381,19 @@ export class CgroupsCollector extends EventEmitter {
values: { throttled: throttlePct }, values: { throttled: throttlePct },
}) })
for (const kind of ['cpu', 'memory', 'io']) { if (doPressure) {
const some10 = parsePressureSome10(cg.path, kind) for (const kind of ['cpu', 'memory', 'io']) {
if (some10 == null) continue const some10 = parsePressureSome10(cg.path, kind)
const pressureDef = makeCgroupPressureChart(cg.id, title, kind) if (some10 == null) continue
registerChart(pressureDef) const pressureDef = makeCgroupPressureChart(cg.id, title, kind)
batch.push({ registerChart(pressureDef)
chart: pressureDef.id, batch.push({
context: 'cgroup.pressure', chart: pressureDef.id,
ts, context: 'cgroup.pressure',
values: { some10 }, ts,
}) values: { some10 },
})
}
} }
this.prev.set(cg.id, { this.prev.set(cg.id, {
+40 -19
View File
@@ -208,32 +208,53 @@ export function collectNumaNodes(batch, ts) {
*/ */
export function collectCpuidle(batch, ts, dtSec, state) { export function collectCpuidle(batch, ts, dtSec, state) {
if (os.platform() !== 'linux') return if (os.platform() !== 'linux') return
const cpuRoot = '/sys/devices/system/cpu' // cache cpuidle state paths for 30s — topology doesn't change at runtime
let dirs let cpuidlePaths = state._cpuidlePaths
try { const now = Date.now()
dirs = fs.readdirSync(cpuRoot).filter((d) => /^cpu\d+$/.test(d)) if (!cpuidlePaths || now - cpuidlePaths.ts > 30000) {
} catch { const cpuRoot = '/sys/devices/system/cpu'
return let dirs
try {
dirs = fs.readdirSync(cpuRoot).filter((d) => /^cpu\d+$/.test(d))
} catch {
return
}
/** @type {Record<string, Array<{ nameFile: string, timeFile: string }>>} */
const entries = {}
for (const d of dirs) {
const coreId = d.slice(3)
const idleDir = path.join(cpuRoot, d, 'cpuidle')
let states
try {
states = fs.readdirSync(idleDir).filter((s) => /^state\d+$/.test(s))
} catch {
continue
}
const files = []
for (const s of states) {
files.push({
nameFile: path.join(idleDir, s, 'name'),
timeFile: path.join(idleDir, s, 'time'),
})
}
if (files.length) entries[coreId] = files
}
cpuidlePaths = { ts: now, entries, cpuDirs: dirs }
state._cpuidlePaths = cpuidlePaths
} }
const { entries } = cpuidlePaths
const prevAll = state.lastIdle || {} const prevAll = state.lastIdle || {}
/** @type {Record<string, Record<string, number>>} */ /** @type {Record<string, Record<string, number>>} */
const nextAll = {} const nextAll = {}
for (const d of dirs) { for (const coreId of Object.keys(entries)) {
const coreId = d.slice(3) const files = entries[coreId]
const idleDir = path.join(cpuRoot, d, 'cpuidle')
let states
try {
states = fs.readdirSync(idleDir).filter((s) => /^state\d+$/.test(s))
} catch {
continue
}
/** @type {Record<string, number>} */ /** @type {Record<string, number>} */
const times = {} const times = {}
for (const s of states) { for (const { nameFile, timeFile } of files) {
const name = (readFile(path.join(idleDir, s, 'name')) || s).trim().toLowerCase().replace(/\s+/g, '_') const name = (readFile(nameFile) || '').trim().toLowerCase().replace(/\s+/g, '_')
const t = Number(readFile(path.join(idleDir, s, 'time')) || 0) // µs const t = Number(readFile(timeFile) || 0) // µs
times[name || s] = t times[name || 'state'] = t
} }
nextAll[coreId] = times nextAll[coreId] = times
const prev = prevAll[coreId] const prev = prevAll[coreId]