import { afterEach, beforeEach, describe, expect, it } from "vitest" ;
import {
onDiagnosticEvent,
resetDiagnosticEventsForTest,
type DiagnosticEventPayload,
} from "../infra/diagnostic-events.js" ;
import { emitDiagnosticMemorySample, resetDiagnosticMemoryForTest } from "./diagnostic-memory.js" ;
function memoryUsage(overrides: Partial<NodeJS.MemoryUsage>): NodeJS.MemoryUsage {
return {
rss: 100 ,
heapTotal: 80 ,
heapUsed: 40 ,
external: 10 ,
arrayBuffers: 5 ,
...overrides,
};
}
describe("diagnostic memory" , () => {
beforeEach(() => {
resetDiagnosticEventsForTest();
resetDiagnosticMemoryForTest();
});
afterEach(() => {
resetDiagnosticEventsForTest();
resetDiagnosticMemoryForTest();
});
it("emits memory samples with byte counts" , () => {
const events: DiagnosticEventPayload[] = [];
const stop = onDiagnosticEvent((event) => events.push(event));
emitDiagnosticMemorySample({
now: 1000 ,
uptimeMs: 123 ,
memoryUsage: memoryUsage({ rss: 4096 , heapUsed: 1024 }),
});
stop();
expect(events).toMatchObject([
{
type: "diagnostic.memory.sample" ,
uptimeMs: 123 ,
memory: {
rssBytes: 4096 ,
heapUsedBytes: 1024 ,
},
},
]);
});
it("emits pressure when RSS crosses a threshold" , () => {
const events: DiagnosticEventPayload[] = [];
const stop = onDiagnosticEvent((event) => events.push(event));
emitDiagnosticMemorySample({
now: 1000 ,
memoryUsage: memoryUsage({ rss: 2000 }),
thresholds: {
rssWarningBytes: 1000 ,
rssCriticalBytes: 3000 ,
pressureRepeatMs: 60 _000 ,
},
});
stop();
expect(events).toContainEqual(
expect.objectContaining({
type: "diagnostic.memory.pressure" ,
level: "warning" ,
reason: "rss_threshold" ,
thresholdBytes: 1000 ,
}),
);
});
it("can check pressure without recording an idle memory sample" , () => {
const events: DiagnosticEventPayload[] = [];
const stop = onDiagnosticEvent((event) => events.push(event));
emitDiagnosticMemorySample({
now: 1000 ,
emitSample: false ,
memoryUsage: memoryUsage({ rss: 2000 }),
thresholds: {
rssWarningBytes: 1000 ,
rssCriticalBytes: 3000 ,
pressureRepeatMs: 60 _000 ,
},
});
stop();
expect(events.map((event) => event.type)).toEqual(["diagnostic.memory.pressure" ]);
});
it("emits pressure when RSS grows quickly" , () => {
const events: DiagnosticEventPayload[] = [];
const stop = onDiagnosticEvent((event) => events.push(event));
emitDiagnosticMemorySample({
now: 1000 ,
memoryUsage: memoryUsage({ rss: 1000 }),
thresholds: {
rssWarningBytes: 10 _000 ,
heapUsedWarningBytes: 10 _000 ,
rssGrowthWarningBytes: 500 ,
growthWindowMs: 10 _000 ,
},
});
emitDiagnosticMemorySample({
now: 2000 ,
memoryUsage: memoryUsage({ rss: 1700 }),
thresholds: {
rssWarningBytes: 10 _000 ,
heapUsedWarningBytes: 10 _000 ,
rssGrowthWarningBytes: 500 ,
growthWindowMs: 10 _000 ,
},
});
stop();
expect(events).toContainEqual(
expect.objectContaining({
type: "diagnostic.memory.pressure" ,
level: "warning" ,
reason: "rss_growth" ,
rssGrowthBytes: 700 ,
windowMs: 1000 ,
}),
);
});
it("throttles repeated pressure events by reason and level" , () => {
const events: DiagnosticEventPayload[] = [];
const stop = onDiagnosticEvent((event) => events.push(event));
for (const now of [1000 , 2000 ]) {
emitDiagnosticMemorySample({
now,
memoryUsage: memoryUsage({ rss: 2000 }),
thresholds: {
rssWarningBytes: 1000 ,
rssCriticalBytes: 3000 ,
pressureRepeatMs: 60 _000 ,
},
});
}
stop();
expect(events.filter((event) => event.type === "diagnostic.memory.pressure" )).toHaveLength(1 );
});
});
Messung V0.5 in Prozent C=100 H=89 G=94
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland