/** * Ring-buffer stats history for per-container charts. * Samples include CPU, memory, and optional network / block I/O rates. */ const DEFAULT_MAX_POINTS = Number(process.env.PEARDOCK_STATS_HISTORY_POINTS) || 900 /** @type {Map>} */ const history = new Map() /** * @param {string} containerId * @param {{ * cpu: number, * memory: number, * memoryLimit?: number, * netRxRate?: number, * netTxRate?: number, * blkReadRate?: number, * blkWriteRate?: number, * }} sample */ export function recordSample(containerId, sample) { if (!containerId) return let series = history.get(containerId) if (!series) { series = [] history.set(containerId, series) } series.push({ t: Date.now(), cpu: Number(sample.cpu) || 0, memory: Number(sample.memory) || 0, memoryLimit: Number(sample.memoryLimit) || 0, netRxRate: Number(sample.netRxRate) || 0, netTxRate: Number(sample.netTxRate) || 0, blkReadRate: Number(sample.blkReadRate) || 0, blkWriteRate: Number(sample.blkWriteRate) || 0, }) while (series.length > DEFAULT_MAX_POINTS) series.shift() } /** * @param {string} containerId * @param {{ limit?: number, since?: number }} [opts] */ export function getHistory(containerId, opts = {}) { let series = history.get(containerId) || [] const since = Number(opts.since) if (Number.isFinite(since) && since > 0) { series = series.filter((p) => p.t >= since) } const cap = Math.min( Number(opts.limit) || DEFAULT_MAX_POINTS, DEFAULT_MAX_POINTS ) return series.slice(-cap) } /** * @param {string[]} activeIds */ export function pruneMissing(activeIds) { const set = new Set(activeIds) for (const id of history.keys()) { if (!set.has(id)) history.delete(id) } } export function clearHistory() { history.clear() } export function maxHistoryPoints() { return DEFAULT_MAX_POINTS }