Further CPU Experimental Changes
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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,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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user