Files
openclaw/src/logging/diagnostic-memory.test.ts
Gustavo Madeira Santana 28818f9140 Improve gateway diagnostics export for support reports (#70324)
Merged via squash.

Prepared head SHA: 3d6ee85993
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-22 20:47:14 -04:00

155 lines
4.0 KiB
TypeScript

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);
});
});