1
0
Files
DiAL/tests/server/metrics.test.ts

140 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, test } from "bun:test";
import {
analyzeIncidentSequence,
buildTrend,
calculateAvailability,
calculateCurrentStreak,
calculatePercentile,
type MetricCheckpoint,
resolveAutoBucket,
} from "../../src/server/metrics";
describe("后端指标计算", () => {
test("可用率无数据返回 0并保留两位精度", () => {
expect(calculateAvailability(0, 0)).toBe(0);
expect(calculateAvailability(2, 3)).toBe(66.67);
});
test("百分位按 ceil(count * N / 100) - 1 取值", () => {
const durations = Array.from({ length: 100 }, (_, index) => index + 1);
expect(calculatePercentile([], 95)).toBeNull();
expect(calculatePercentile([40, 10, 30, 20], 95)).toBe(40);
expect(calculatePercentile(durations, 95)).toBe(95);
expect(calculatePercentile(durations, 99)).toBe(99);
});
test("无检查数据时故障分析返回空口径", () => {
const result = analyzeIncidentSequence([], "2025-01-01T00:00:00.000Z", "2025-01-01T01:00:00.000Z");
expect(result).toEqual({ incidentCount: 0, longestOutage: null, mttr: null });
expect(calculateCurrentStreak([])).toBeNull();
});
test("窗口起始即故障计入 incident 和最长故障,但不计入 MTTR", () => {
const result = analyzeIncidentSequence(
[
checkpoint("2025-01-01T00:05:00.000Z", false),
checkpoint("2025-01-01T00:10:00.000Z", false),
checkpoint("2025-01-01T00:20:00.000Z", true),
],
"2025-01-01T00:00:00.000Z",
"2025-01-01T01:00:00.000Z",
);
expect(result.incidentCount).toBe(1);
expect(result.longestOutage).toBe(20 * 60 * 1000);
expect(result.mttr).toBeNull();
});
test("未恢复故障计算到窗口结束且不计入 MTTR", () => {
const result = analyzeIncidentSequence(
[checkpoint("2025-01-01T00:05:00.000Z", true), checkpoint("2025-01-01T00:20:00.000Z", false)],
"2025-01-01T00:00:00.000Z",
"2025-01-01T01:00:00.000Z",
);
expect(result.incidentCount).toBe(1);
expect(result.longestOutage).toBe(40 * 60 * 1000);
expect(result.mttr).toBeNull();
});
test("连续异常只计一次 incident恢复后纳入 MTTR", () => {
const result = analyzeIncidentSequence(
[
checkpoint("2025-01-01T00:00:00.000Z", true),
checkpoint("2025-01-01T00:05:00.000Z", false),
checkpoint("2025-01-01T00:10:00.000Z", false),
checkpoint("2025-01-01T00:20:00.000Z", true),
],
"2025-01-01T00:00:00.000Z",
"2025-01-01T01:00:00.000Z",
);
expect(result.incidentCount).toBe(1);
expect(result.longestOutage).toBe(15 * 60 * 1000);
expect(result.mttr).toBe(15 * 60 * 1000);
});
test("连续状态支持 capped 标记", () => {
expect(
calculateCurrentStreak(
[
checkpoint("2025-01-01T00:00:00.000Z", true),
checkpoint("2025-01-01T00:01:00.000Z", false),
checkpoint("2025-01-01T00:02:00.000Z", false),
],
2,
),
).toEqual({ capped: true, count: 2, up: false });
});
test("UTC 小时趋势分桶返回 up/down 和延迟范围", () => {
const checkpoints = [
checkpoint("2025-01-01T00:10:00.000Z", true, 100),
checkpoint("2025-01-01T00:40:00.000Z", false, null),
checkpoint("2025-01-01T01:05:00.000Z", true, 300),
];
const trend = buildTrend(checkpoints, "2025-01-01T00:00:00.000Z", "2025-01-01T01:59:59.999Z", "1h");
expect(trend).toEqual([
{
availability: 50,
avgDurationMs: 100,
bucketEnd: "2025-01-01T01:00:00.000Z",
bucketStart: "2025-01-01T00:00:00.000Z",
downChecks: 1,
maxDurationMs: 100,
minDurationMs: 100,
p95DurationMs: 100,
totalChecks: 2,
upChecks: 1,
},
{
availability: 100,
avgDurationMs: 300,
bucketEnd: "2025-01-01T01:59:59.999Z",
bucketStart: "2025-01-01T01:00:00.000Z",
downChecks: 0,
maxDurationMs: 300,
minDurationMs: 300,
p95DurationMs: 300,
totalChecks: 1,
upChecks: 1,
},
]);
});
test("resolveAutoBucket 按窗口大小选择合适桶", () => {
expect(resolveAutoBucket(30_000, 7 * 24 * 60 * 60 * 1000)).toBe("1h");
expect(resolveAutoBucket(30_000, 60 * 60 * 1000)).toBe("30s");
expect(resolveAutoBucket(30_000, 24 * 60 * 60 * 1000)).toBe("15m");
});
});
function checkpoint(timestamp: string, matched: boolean, durationMs: null | number = null): MetricCheckpoint {
return { durationMs, matched, timestamp };
}