Files
wekan/server/lib/cpuLog.js
T
Lauri Ojansivu 1c636f09b3 CPU-usage monitor + governor + Admin Panel / Problems / CPU usage report
WeKan and FerretDB on one machine can drive CPU high enough to starve other
software. Add a monitor, a governor, and a report. Design: docs/Features/CPU/CPU.md.

Monitor (server/lib/cpuMonitor.js): samples system-wide CPU% from os.cpus()
idle/total deltas on an interval (so it includes WeKan, FerretDB and every other
process). A pure, hysteresis state machine (models/lib/cpuHighTracker.js) turns
the samples into ONLY the START and END of each sustained high-CPU period — two
rows per episode, never a flood. Enters high after 3 consecutive samples >= 85%,
leaves after 3 consecutive < 70%. Each row carries duration, peak, system load,
and what WeKan was doing (setActivity label). Recorded to a new 'cpu' event
stream (server/lib/cpuLog.js).

Governor: cpuMonitor exports isHighCpu() and pauseIfBusy(), so long batch
operations await a short pause and yield the CPU only while the system is busy
(no cost when idle). Wired into the existing-file extension corrector. Tunable
via WEKAN_CPU_* env vars.

Report: 'cpu' added to EVENT_STREAMS; reuses the generic eventStreamReport
(stream="cpu") with search + pagination; a "CPU usage" item is added to the
Admin Panel / Problems side menu (cpuReportTitle i18n).

Tests: cpuHighTracker unit (start/end, duration/peak, no-flap hysteresis;
positive + negative) and cpuMonitorWiring source guards. CHANGELOG + design doc.
2026-07-20 03:11:07 +03:00

34 lines
1.4 KiB
JavaScript

// ============================================================================
// WeKan CPU-usage event logger (Admin Panel → Problems → CPU usage).
// ----------------------------------------------------------------------------
// Records ONLY the START and END of each sustained high-CPU period (not a
// continuous stream) as documents in the existing WeKan database
// (models/eventLog.js, stream:'cpu') via a normal Meteor JavaScript query — so it
// works identically on FerretDB and MongoDB. Best-effort, never throws.
// ============================================================================
import EventLog from '/models/eventLog';
const { sanitizeDetail } = require('/models/lib/securityLogFormat');
export function record(evt = {}) {
try {
const doc = {
stream: 'cpu',
at: evt.at instanceof Date ? evt.at : new Date(),
severity: evt.severity || 'medium',
category: evt.category || 'cpu',
bleed: evt.bleed || 'CpuBleed',
action: evt.action || 'detected',
source: evt.source || 'cpuMonitor',
cwe: evt.cwe || 'CWE-400',
detail: sanitizeDetail(evt.detail),
};
const p = EventLog.insertAsync(doc);
if (p && typeof p.catch === 'function') p.catch(() => {});
} catch (e) {
if (process.env.DEBUG === 'true') console.warn('cpuLog.record failed:', e && e.message);
}
}
export default { record };