61 lines
3.1 KiB
JavaScript
61 lines
3.1 KiB
JavaScript
import { checkService, nextState } from './probe.mjs';
|
|
|
|
export function createMonitor(store, config, { check = checkService, runScenario = null, now = Date.now, setTimer = setTimeout, clearTimer = clearTimeout } = {}) {
|
|
let active = null;
|
|
let stopped = true;
|
|
let timer;
|
|
let lastPruned = 0;
|
|
let scenarioActive = null, scenarioTimer;
|
|
function previousState(service, scope, current) {
|
|
// Expected failures during maintenance do not count toward a new outage.
|
|
if (current && store.inMaintenance(service, current.checked_at) && !store.inMaintenance(service, now()) && !store.hasOpenIncident(service, scope)) return null;
|
|
return current;
|
|
}
|
|
async function cycle() {
|
|
if (active) return active;
|
|
active = (async () => {
|
|
await Promise.all(config.services.map(async service => {
|
|
const result = await check(service, config);
|
|
store.record(service, result, nextState(previousState(service, 'availability', store.state(service)), result, config), now(), { notify: config.telegram?.enabled });
|
|
}));
|
|
if (now() - lastPruned > 86400000) { store.prune(now() - config.retentionDays * 86400000); lastPruned = now(); }
|
|
})();
|
|
try { await active; } finally { active = null; }
|
|
}
|
|
async function scenarioCycle() {
|
|
if (!runScenario) return;
|
|
if (scenarioActive) return scenarioActive;
|
|
scenarioActive = (async () => {
|
|
await Promise.all(config.services.filter(service => service.scenario).map(async service => {
|
|
const previous = store.scenario(service);
|
|
if (previous && now() - previous.checked_at < config.scenarioIntervalSeconds * 1000) return;
|
|
const result = await runScenario(service);
|
|
store.recordScenario(service, result, nextState(previousState(service, 'scenario', previous), result, { ...config, slowMs: config.scenarioSlowMs }), now(), { notify: config.telegram?.enabled });
|
|
}));
|
|
})();
|
|
try { await scenarioActive; } finally { scenarioActive = null; }
|
|
}
|
|
async function loop() {
|
|
try { await cycle(); } catch { console.error('Monitoring cycle failed; previous results will expire.'); }
|
|
if (!stopped) timer = setTimer(loop, config.intervalSeconds * 1000);
|
|
}
|
|
async function scenarioLoop() {
|
|
const interval = config.scenarioIntervalSeconds * 1000;
|
|
let delay = interval;
|
|
try {
|
|
await scenarioCycle();
|
|
// Resume each saved deadline after a restart, even when a check is not due yet.
|
|
for (const service of config.services.filter(service => service.scenario)) {
|
|
const saved = store.scenario(service);
|
|
if (saved) delay = Math.min(delay, Math.max(100, saved.checked_at + interval - now()));
|
|
}
|
|
} catch { console.error('User journey checks failed; previous results will expire.'); }
|
|
if (!stopped) scenarioTimer = setTimer(scenarioLoop, delay);
|
|
}
|
|
return {
|
|
cycle, scenarioCycle,
|
|
start() { if (!stopped) return; stopped = false; void loop(); if (runScenario) void scenarioLoop(); },
|
|
async stop() { stopped = true; clearTimer(timer); clearTimer(scenarioTimer); await Promise.all([active, scenarioActive].filter(Boolean)); },
|
|
};
|
|
}
|