Further CPU Experimental Changes
CI / test (push) Failing after 4s
Release rolling / release (push) Successful in 7m14s

This commit is contained in:
Raven Scott
2026-07-21 12:17:06 -04:00
parent 1d902a89b0
commit ac4718cb66
11 changed files with 87 additions and 85 deletions
+3 -5
View File
@@ -6,6 +6,7 @@ import fs from 'fs'
import path from 'path' import path from 'path'
import { CollectorPlugin } from './plugin.js' import { CollectorPlugin } from './plugin.js'
import { registerChart } from '../../../shared/metrics.js' import { registerChart } from '../../../shared/metrics.js'
import { readFileBuf } from '../../utils/fd-cache.js'
export function isBcacheEnabled() { export function isBcacheEnabled() {
const v = process.env.PEARDATA_BCACHE const v = process.env.PEARDATA_BCACHE
@@ -15,11 +16,8 @@ export function isBcacheEnabled() {
} }
function readNum(p) { function readNum(p) {
try { const raw = readFileBuf(p)
return Number(fs.readFileSync(p, 'utf8').trim()) || 0 return raw ? Number(raw.trim()) || 0 : 0
} catch {
return 0
}
} }
export class BcacheCollector extends CollectorPlugin { export class BcacheCollector extends CollectorPlugin {
+2 -5
View File
@@ -20,6 +20,7 @@ import {
} from '../../../shared/metrics.js' } from '../../../shared/metrics.js'
import { resolveContainerLabel } from '../../../shared/container-names.js' import { resolveContainerLabel } from '../../../shared/container-names.js'
import { loadContainerNameMap, resolveDockerSocket } from './docker.js' import { loadContainerNameMap, resolveDockerSocket } from './docker.js'
import { readFileBuf } from '../../utils/fd-cache.js'
import logger from '../../utils/logger.js' import logger from '../../utils/logger.js'
const log = logger.child('cgroups') const log = logger.child('cgroups')
@@ -37,11 +38,7 @@ function maxCgroups() {
} }
function readFile(p) { function readFile(p) {
try { return readFileBuf(p)
return fs.readFileSync(p, 'utf8')
} catch {
return null
}
} }
function cgroupRoot() { function cgroupRoot() {
+2 -5
View File
@@ -10,6 +10,7 @@ import fs from 'fs'
import path from 'path' import path from 'path'
import { CollectorPlugin } from './plugin.js' import { CollectorPlugin } from './plugin.js'
import { registerChart } from '../../../shared/metrics.js' import { registerChart } from '../../../shared/metrics.js'
import { readFileBuf } from '../../utils/fd-cache.js'
import { execFile } from '../../utils/exec.js' import { execFile } from '../../utils/exec.js'
import logger from '../../utils/logger.js' import logger from '../../utils/logger.js'
@@ -39,11 +40,7 @@ export function isDmcacheEnabled() {
} }
function readFile(p) { function readFile(p) {
try { return readFileBuf(p)
return fs.readFileSync(p, 'utf8')
} catch {
return null
}
} }
/** /**
+2 -5
View File
@@ -16,6 +16,7 @@ import net from 'net'
import os from 'os' import os from 'os'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { execFile } from '../../utils/exec.js' import { execFile } from '../../utils/exec.js'
import { readFileBuf } from '../../utils/fd-cache.js'
import { import {
SAMPLE_INTERVAL_MS, SAMPLE_INTERVAL_MS,
registerChart, registerChart,
@@ -59,11 +60,7 @@ export function resolveDockerSocket(preferred) {
} }
function readFile(p) { function readFile(p) {
try { return readFileBuf(p)
return fs.readFileSync(p, 'utf8')
} catch {
return null
}
} }
function bytesToMiB(n) { function bytesToMiB(n) {
+13 -16
View File
@@ -16,6 +16,7 @@ import {
SAMPLE_INTERVAL_MS, SAMPLE_INTERVAL_MS,
registerChart, registerChart,
} from '../../../shared/metrics.js' } from '../../../shared/metrics.js'
import { readFileCached, readFileBuf } from '../../utils/fd-cache.js'
import { extractHelper, hasEmbeddedHelper } from '../../native/extract-helper.js' import { extractHelper, hasEmbeddedHelper } from '../../native/extract-helper.js'
import logger from '../../utils/logger.js' import logger from '../../utils/logger.js'
@@ -202,13 +203,12 @@ export class EbpfCollector extends EventEmitter {
_readVm() { _readVm() {
/** @type {Record<string, number>} */ /** @type {Record<string, number>} */
const out = {} const out = {}
try { const raw = readFileCached('/proc/vmstat')
for (const line of fs.readFileSync('/proc/vmstat', 'utf8').split('\n')) { if (raw) {
for (const line of raw.split('\n')) {
const [k, v] = line.trim().split(/\s+/) const [k, v] = line.trim().split(/\s+/)
if (k) out[k] = Number(v) || 0 if (k) out[k] = Number(v) || 0
} }
} catch {
// ignore
} }
return out return out
} }
@@ -228,30 +228,27 @@ export class EbpfCollector extends EventEmitter {
const den = dHit + dMiss const den = dHit + dMiss
let fileNr = [0, 0] let fileNr = [0, 0]
try { const fileNrRaw = readFileCached('/proc/sys/fs/file-nr')
const parts = fs.readFileSync('/proc/sys/fs/file-nr', 'utf8').trim().split(/\s+/) if (fileNrRaw) {
const parts = fileNrRaw.trim().split(/\s+/)
fileNr = [Number(parts[0]) || 0, Number(parts[2]) || 0] fileNr = [Number(parts[0]) || 0, Number(parts[2]) || 0]
} catch {
// ignore
} }
let forks = 0 let forks = 0
let ctxt = 0 let ctxt = 0
try { const statRaw = readFileCached('/proc/stat')
for (const line of fs.readFileSync('/proc/stat', 'utf8').split('\n')) { if (statRaw) {
for (const line of statRaw.split('\n')) {
if (line.startsWith('processes ')) forks = Number(line.slice(10)) || 0 if (line.startsWith('processes ')) forks = Number(line.slice(10)) || 0
if (line.startsWith('ctxt ')) ctxt = Number(line.slice(5)) || 0 if (line.startsWith('ctxt ')) ctxt = Number(line.slice(5)) || 0
} }
} catch {
// ignore
} }
let tcp = 0 let tcp = 0
try { const sockRaw = readFileCached('/proc/net/sockstat')
const m = fs.readFileSync('/proc/net/sockstat', 'utf8').match(/TCP:\s+inuse\s+(\d+)/) if (sockRaw) {
const m = sockRaw.match(/TCP:\s+inuse\s+(\d+)/)
if (m) tcp = Number(m[1]) || 0 if (m) tcp = Number(m[1]) || 0
} catch {
// ignore
} }
const batch = [ const batch = [
+2 -5
View File
@@ -11,6 +11,7 @@ import path from 'path'
import os from 'os' import os from 'os'
import { CollectorPlugin } from './plugin.js' import { CollectorPlugin } from './plugin.js'
import { registerChart } from '../../../shared/metrics.js' import { registerChart } from '../../../shared/metrics.js'
import { readFileBuf } from '../../utils/fd-cache.js'
import logger from '../../utils/logger.js' import logger from '../../utils/logger.js'
const log = logger.child('fs-stats') const log = logger.child('fs-stats')
@@ -23,11 +24,7 @@ export function isFsStatsEnabled() {
} }
function readFile(p) { function readFile(p) {
try { return readFileBuf(p)
return fs.readFileSync(p, 'utf8')
} catch {
return null
}
} }
function readNum(p) { function readNum(p) {
+2 -5
View File
@@ -9,6 +9,7 @@ import fs from 'fs'
import os from 'os' import os from 'os'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { SAMPLE_INTERVAL_MS, registerChart } from '../../../shared/metrics.js' import { SAMPLE_INTERVAL_MS, registerChart } from '../../../shared/metrics.js'
import { readFileBuf } from '../../utils/fd-cache.js'
import logger from '../../utils/logger.js' import logger from '../../utils/logger.js'
const log = logger.child('mdstat') const log = logger.child('mdstat')
@@ -31,11 +32,7 @@ const CHART_HEALTH = {
} }
function readFile(p) { function readFile(p) {
try { return readFileBuf(p)
return fs.readFileSync(p, 'utf8')
} catch {
return null
}
} }
/** /**
+33 -23
View File
@@ -17,6 +17,7 @@ import path from 'path'
import os from 'os' import os from 'os'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { SAMPLE_INTERVAL_MS, registerChart } from '../../../shared/metrics.js' import { SAMPLE_INTERVAL_MS, registerChart } from '../../../shared/metrics.js'
import { readFileBuf } from '../../utils/fd-cache.js'
import logger from '../../utils/logger.js' import logger from '../../utils/logger.js'
const log = logger.child('processes') const log = logger.child('processes')
@@ -50,6 +51,10 @@ function sanitizeDim(name) {
return s || 'unknown' return s || 'unknown'
} }
const PROC_EXTRA_CACHE = new Map()
const PROC_EXTRA_TICK = 5
let procTickCount = 0
/** /**
* @returns {Array<{ pid: number, name: string, utime: number, stime: number, rssPages: number, threads: number, readBytes: number|null, writeBytes: number|null }>|null} * @returns {Array<{ pid: number, name: string, utime: number, stime: number, rssPages: number, threads: number, readBytes: number|null, writeBytes: number|null }>|null}
*/ */
@@ -61,47 +66,52 @@ export function listProcStats() {
} catch { } catch {
return null return null
} }
procTickCount = (procTickCount + 1) % PROC_EXTRA_TICK
const readExtra = procTickCount === 0
/** @type {Array<{ pid: number, name: string, utime: number, stime: number, rssPages: number, threads: number, readBytes: number|null, writeBytes: number|null }>} */ /** @type {Array<{ pid: number, name: string, utime: number, stime: number, rssPages: number, threads: number, readBytes: number|null, writeBytes: number|null }>} */
const out = [] const out = []
for (const ent of dirs) { for (const ent of dirs) {
if (!/^\d+$/.test(ent)) continue if (!/^\d+$/.test(ent)) continue
const pid = Number(ent) const pid = Number(ent)
let raw const raw = readFileBuf(path.join('/proc', ent, 'stat'))
try { if (!raw) continue
raw = fs.readFileSync(path.join('/proc', ent, 'stat'), 'utf8')
} catch {
continue
}
const open = raw.indexOf('(') const open = raw.indexOf('(')
const close = raw.lastIndexOf(')') const close = raw.lastIndexOf(')')
if (open < 0 || close < open) continue if (open < 0 || close < open) continue
const name = raw.slice(open + 1, close) const name = raw.slice(open + 1, close)
const rest = raw.slice(close + 2).split(/\s+/) const rest = raw.slice(close + 2).split(/\s+/)
// fields after comm: state(0) … utime(11) stime(12) … rss(21)
const utime = Number(rest[11]) const utime = Number(rest[11])
const stime = Number(rest[12]) const stime = Number(rest[12])
const rssPages = Number(rest[21]) const rssPages = Number(rest[21])
if (!Number.isFinite(utime) || !Number.isFinite(stime)) continue if (!Number.isFinite(utime) || !Number.isFinite(stime)) continue
let threads = 0 let threads = 0
try {
const status = fs.readFileSync(path.join('/proc', ent, 'status'), 'utf8')
const m = status.match(/^Threads:\s*(\d+)/m)
if (m) threads = Number(m[1]) || 0
} catch {
// ignore
}
let readBytes = null let readBytes = null
let writeBytes = null let writeBytes = null
try {
const ioRaw = fs.readFileSync(path.join('/proc', ent, 'io'), 'utf8') if (readExtra) {
const rb = ioRaw.match(/^read_bytes:\s*(\d+)/m) const status = readFileBuf(path.join('/proc', ent, 'status'))
const wb = ioRaw.match(/^write_bytes:\s*(\d+)/m) if (status) {
if (rb) readBytes = Number(rb[1]) || 0 const m = status.match(/^Threads:\s*(\d+)/m)
if (wb) writeBytes = Number(wb[1]) || 0 if (m) threads = Number(m[1]) || 0
} catch { }
// unreadable for this pid const ioRaw = readFileBuf(path.join('/proc', ent, 'io'))
if (ioRaw) {
const rb = ioRaw.match(/^read_bytes:\s*(\d+)/m)
const wb = ioRaw.match(/^write_bytes:\s*(\d+)/m)
if (rb) readBytes = Number(rb[1]) || 0
if (wb) writeBytes = Number(wb[1]) || 0
}
}
const cached = PROC_EXTRA_CACHE.get(pid)
if (cached) {
if (threads === 0) threads = cached.threads
if (readBytes === null) readBytes = cached.readBytes
if (writeBytes === null) writeBytes = cached.writeBytes
}
if (readExtra || !cached) {
PROC_EXTRA_CACHE.set(pid, { threads, readBytes, writeBytes })
} }
out.push({ out.push({
+2 -5
View File
@@ -14,6 +14,7 @@ import {
makeSensorTempChart, makeSensorTempChart,
makeThermalZoneChart, makeThermalZoneChart,
} from '../../../shared/metrics.js' } from '../../../shared/metrics.js'
import { readFileBuf } from '../../utils/fd-cache.js'
import logger from '../../utils/logger.js' import logger from '../../utils/logger.js'
const log = logger.child('sensors') const log = logger.child('sensors')
@@ -26,11 +27,7 @@ export function isSensorsEnabled() {
} }
function readFile(p) { function readFile(p) {
try { return readFileBuf(p)
return fs.readFileSync(p, 'utf8')
} catch {
return null
}
} }
function makeSensorFanChart(chip, label) { function makeSensorFanChart(chip, label) {
+6 -11
View File
@@ -6,6 +6,7 @@ import fs from 'fs'
import os from 'os' import os from 'os'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { SAMPLE_INTERVAL_MS, registerChart } from '../../../shared/metrics.js' import { SAMPLE_INTERVAL_MS, registerChart } from '../../../shared/metrics.js'
import { readFileBuf } from '../../utils/fd-cache.js'
import logger from '../../utils/logger.js' import logger from '../../utils/logger.js'
const log = logger.child('sockets') const log = logger.child('sockets')
@@ -42,12 +43,8 @@ function countTcp(file) {
syn_recv: 0, syn_recv: 0,
other: 0, other: 0,
} }
let raw const raw = readFileBuf(file)
try { if (!raw) return counts
raw = fs.readFileSync(file, 'utf8')
} catch {
return counts
}
for (const line of raw.split('\n').slice(1)) { for (const line of raw.split('\n').slice(1)) {
const parts = line.trim().split(/\s+/) const parts = line.trim().split(/\s+/)
if (parts.length < 4) continue if (parts.length < 4) continue
@@ -60,11 +57,9 @@ function countTcp(file) {
} }
function countUdp(file) { function countUdp(file) {
try { const raw = readFileBuf(file)
return Math.max(0, fs.readFileSync(file, 'utf8').trim().split('\n').length - 1) if (!raw) return 0
} catch { return Math.max(0, raw.trim().split('\n').length - 1)
return 0
}
} }
export class SocketsCollector extends EventEmitter { export class SocketsCollector extends EventEmitter {
+20
View File
@@ -1,6 +1,7 @@
import fs from 'fs' import fs from 'fs'
const READ_BUF = Buffer.alloc(65536) const READ_BUF = Buffer.alloc(65536)
const SMALL_BUF = Buffer.alloc(8192)
const fdCache = new Map() const fdCache = new Map()
export function readFileCached(path) { export function readFileCached(path) {
@@ -24,6 +25,25 @@ export function readFileCached(path) {
} }
} }
/**
* Read a file using the shared small buffer, no FD caching.
* Good for dynamic paths (per-PID, per-cgroup) where open+close each tick is acceptable
* but Buffer allocation is not.
*/
export function readFileBuf(path) {
try {
const fd = fs.openSync(path, 'r')
try {
const bytes = fs.readSync(fd, SMALL_BUF, 0, SMALL_BUF.length, 0)
return SMALL_BUF.toString('utf8', 0, bytes)
} finally {
fs.closeSync(fd)
}
} catch {
return null
}
}
export function closeFdCache() { export function closeFdCache() {
for (const [path, entry] of fdCache) { for (const [path, entry] of fdCache) {
if (entry) { if (entry) {