mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
72 lines
2.8 KiB
JavaScript
72 lines
2.8 KiB
JavaScript
// Memory/compute overload sensor: the quiet death nobody reports, where RSS climbs until macOS
|
|
// kills a renderer or the fans spin up. Idle-scheduled, unref'd, and it EMITS ONLY on threshold
|
|
// crossings, so a healthy session ships nothing at all.
|
|
'use strict';
|
|
|
|
// Overridable so support can ask a user to run with a tighter cap, and so the wire is testable
|
|
// without allocating gigabytes on someone's machine.
|
|
const SAMPLE_MS = Number(process.env.OSW_MEM_SAMPLE_MS || 60_000);
|
|
// Crossed once, reported once: a leak is a trend, not a per-minute alarm.
|
|
const TOTAL_MB_CAP = Number(process.env.OSW_MEM_CAP_MB || 3000);
|
|
const GROWTH_MB_PER_MIN = Number(process.env.OSW_MEM_GROWTH_MB || 40);
|
|
const GROWTH_WINDOW = 10;
|
|
|
|
let p_timer = null;
|
|
let p_history = [];
|
|
let p_capReported = false;
|
|
let p_growthReported = false;
|
|
|
|
function totalMb(metrics) {
|
|
let kb = 0;
|
|
for (const m of metrics) kb += (m.memory && m.memory.workingSetSize) || 0;
|
|
return Math.round(kb / 1024);
|
|
}
|
|
|
|
/** Least-squares slope in MB/min over the sample window; a straight climb is the leak signature. */
|
|
function slopeMbPerMin(history) {
|
|
const n = history.length;
|
|
if (n < 4) return 0;
|
|
const meanX = (n - 1) / 2;
|
|
const meanY = history.reduce((a, b) => a + b, 0) / n;
|
|
let num = 0;
|
|
let den = 0;
|
|
for (let i = 0; i < n; i += 1) {
|
|
num += (i - meanX) * (history[i] - meanY);
|
|
den += (i - meanX) * (i - meanX);
|
|
}
|
|
return den === 0 ? 0 : num / den;
|
|
}
|
|
|
|
function startMemorySensor(app, getMainWindow) {
|
|
if (p_timer) return;
|
|
p_timer = setInterval(() => {
|
|
let metrics;
|
|
try { metrics = app.getAppMetrics(); } catch (_) { return; }
|
|
const mb = totalMb(metrics);
|
|
p_history.push(mb);
|
|
if (p_history.length > GROWTH_WINDOW) p_history.shift();
|
|
const slope = slopeMbPerMin(p_history);
|
|
const send = (reason, extra) => {
|
|
const win = getMainWindow();
|
|
if (win && !win.isDestroyed()) {
|
|
try { win.webContents.send('diag:memory', { reason, total_mb: mb, procs: metrics.length, slope_mb_min: Math.round(slope), ...extra }); } catch (_) {}
|
|
}
|
|
console.error('[diag][memory]', reason, 'total_mb=' + mb, 'procs=' + metrics.length, 'slope=' + Math.round(slope));
|
|
};
|
|
if (!p_capReported && mb >= TOTAL_MB_CAP) { p_capReported = true; send('cap_crossed', {}); }
|
|
if (p_capReported && mb < TOTAL_MB_CAP * 0.8) p_capReported = false;
|
|
if (!p_growthReported && p_history.length >= GROWTH_WINDOW && slope >= GROWTH_MB_PER_MIN) {
|
|
p_growthReported = true;
|
|
send('growth_suspect', { window_min: GROWTH_WINDOW });
|
|
}
|
|
}, SAMPLE_MS);
|
|
p_timer.unref?.();
|
|
}
|
|
|
|
function stopMemorySensor() {
|
|
if (p_timer) { clearInterval(p_timer); p_timer = null; }
|
|
p_history = [];
|
|
}
|
|
|
|
module.exports = { startMemorySensor, stopMemorySensor, slopeMbPerMin, totalMb, TOTAL_MB_CAP, GROWTH_MB_PER_MIN };
|