CPU and state topology is cached for 30s.
This commit is contained in:
@@ -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, {
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user