044941d406
Settlement alarm (settle-pass THROW pages high; morning zero-settle alarm keyed off the Postgres ledger settle results, once per ET date, never on an empty yesterday), per-sport 3-consecutive-slot failure pager (pure opsWatch.createFailureTracker, pages once per losing streak), odds-api >=80% quota alert (once per day, Redis-deduped), systemHealth (statfs + os mem, disk>85 / mem>90 pages), daily 9 AM ET pulse (ONE notification: ledger rows yesterday via ledgerService.countRowsForDate, settles 24h, quota, disk/mem, desk line), docs/OPS-RUNBOOK.md (Uptime Kuma monitors, Coolify deploy-failure -> ntfy, phone subscription). All copy VOICE v1.1 — deadpan, numbers, no exclamation points (tests lint for it). 2398 -> 2437 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
61 lines
2.1 KiB
JavaScript
61 lines
2.1 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* systemHealth — box vitals (Session 8, A1 board — ops).
|
|
*
|
|
* Pure + injectable: disk usage via fs.promises.statfs('/'), memory via
|
|
* os.freemem/os.totalmem. Returns percentages (0-100, rounded) or null when a
|
|
* probe fails — the caller renders "n/a", never a fabricated number (Data
|
|
* Semantics Rule applies to ops copy too). Zero dependencies, zero cost.
|
|
*/
|
|
|
|
const fsp = require('fs').promises;
|
|
const os = require('os');
|
|
|
|
/** Alerting thresholds — checked by the daily pulse. */
|
|
const THRESHOLDS = { disk_pct: 85, mem_pct: 90 };
|
|
|
|
const roundPct = (frac) => Math.min(100, Math.max(0, Math.round(frac * 100)));
|
|
|
|
/**
|
|
* { disk_pct, mem_pct } — each null when its probe fails.
|
|
* opts: { fsImpl (statfs), osImpl (freemem/totalmem), path }.
|
|
*/
|
|
async function getSystemHealth(opts = {}) {
|
|
const fsImpl = opts.fsImpl || fsp;
|
|
const osImpl = opts.osImpl || os;
|
|
const out = { disk_pct: null, mem_pct: null };
|
|
try {
|
|
const s = await fsImpl.statfs(opts.path || '/');
|
|
if (s && Number.isFinite(s.blocks) && s.blocks > 0 && Number.isFinite(s.bavail)) {
|
|
out.disk_pct = roundPct(1 - s.bavail / s.blocks);
|
|
}
|
|
} catch { /* disk_pct stays null */ }
|
|
try {
|
|
const total = osImpl.totalmem();
|
|
const free = osImpl.freemem();
|
|
if (Number.isFinite(total) && total > 0 && Number.isFinite(free)) {
|
|
out.mem_pct = roundPct(1 - free / total);
|
|
}
|
|
} catch { /* mem_pct stays null */ }
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Deadpan issue lines for anything over threshold (strictly >). Empty array =
|
|
* healthy. Copy carries the numbers; no punctuation theatrics.
|
|
*/
|
|
function healthIssues(health, thresholds = THRESHOLDS) {
|
|
const issues = [];
|
|
if (!health) return issues;
|
|
if (Number.isFinite(health.disk_pct) && health.disk_pct > thresholds.disk_pct) {
|
|
issues.push(`disk at ${health.disk_pct}% (threshold ${thresholds.disk_pct}%)`);
|
|
}
|
|
if (Number.isFinite(health.mem_pct) && health.mem_pct > thresholds.mem_pct) {
|
|
issues.push(`memory at ${health.mem_pct}% (threshold ${thresholds.mem_pct}%)`);
|
|
}
|
|
return issues;
|
|
}
|
|
|
|
module.exports = { getSystemHealth, healthIssues, THRESHOLDS };
|