/** * BCache collector via /sys/fs/bcache and per-block bcache sysfs. * Enable: PEARDATA_BCACHE=1 (auto when sysfs present) */ import fs from 'fs' import path from 'path' import { CollectorPlugin } from './plugin.js' import { registerChart } from '../../../shared/metrics.js' import { readFileBuf } from '../../utils/fd-cache.js' export function isBcacheEnabled() { const v = process.env.PEARDATA_BCACHE if (v === '0' || v === 'off' || v === 'false') return false if (v === '1' || v === 'on' || v === 'true') return true return fs.existsSync('/sys/fs/bcache') } function readNum(p) { const raw = readFileBuf(p) return raw ? Number(raw.trim()) || 0 : 0 } export class BcacheCollector extends CollectorPlugin { constructor() { super({ name: 'bcache' }) this.prev = new Map() } isEnabled() { return isBcacheEnabled() } async collect() { const ts = Date.now() /** @type {Array<{ chart: string, context: string, ts: number, values: object }>} */ const batch = [] let dirs = [] try { dirs = fs.readdirSync('/sys/fs/bcache') } catch { return batch } for (const id of dirs) { const base = path.join('/sys/fs/bcache', id) if (!fs.existsSync(path.join(base, 'stats_total'))) continue const hits = readNum(path.join(base, 'stats_total', 'cache_hits')) const misses = readNum(path.join(base, 'stats_total', 'cache_misses')) const bypass = readNum(path.join(base, 'stats_total', 'cache_bypass_hits')) const safe = id.slice(0, 12) const chart = `bcache.${safe}` registerChart({ id: chart, name: chart, context: 'bcache.cache', title: `BCache ${safe}`, units: 'events/s', family: safe, chartType: 'line', priority: 4700, plugin: 'bcache', dimensions: [ { id: 'hits', name: 'hits', algorithm: 'incremental' }, { id: 'misses', name: 'misses', algorithm: 'incremental' }, { id: 'bypass', name: 'bypass', algorithm: 'incremental' }, ], }) const prev = this.prev.get(id) const dt = this.intervalMs / 1000 batch.push({ chart, context: 'bcache.cache', ts, values: { hits: prev ? Math.max(0, hits - prev.hits) / dt : 0, misses: prev ? Math.max(0, misses - prev.misses) / dt : 0, bypass: prev ? Math.max(0, bypass - prev.bypass) / dt : 0, }, }) this.prev.set(id, { hits, misses, bypass }) } return batch } } let singleton = null export function getBcacheCollector() { if (!singleton) singleton = new BcacheCollector() return singleton }