1
0

feat: 新增本机 CPU checker

- 新增 type: cpu checker,基于 os.cpus() 两次快照计算 CPU 使用率
- 配置项:sampleDuration(默认 1s)、includePerCore(默认 false)
- expect 字段:usagePercent、idlePercent、maxCoreUsagePercent、minCoreUsagePercent、durationMs
- idlePercent 与 usagePercent 互补恒等于 100,百分比范围 0-100
- logicalCoreCount 仅输出到 observation,不作为 expect 字段
- 不暴露 userPercent / systemPercent
- 语义校验禁止 sampleDuration >= timeout
- 支持 AbortSignal 超时取消
- 完整测试覆盖:schema、validate、normalize、resolve、calculate、execute、expect、config-loader
- 新增用户文档 docs/user/checkers/cpu.md
- 更新 checker 索引、配置类型列表、示例配置和 schema
This commit is contained in:
2026-05-26 22:34:57 +08:00
parent f38286d74d
commit c2dcfab80c
22 changed files with 1839 additions and 3 deletions

View File

@@ -0,0 +1,112 @@
import { describe, expect, test } from "bun:test";
import type { CpuCoreSnapshot } from "../../../../../src/server/checker/runner/cpu/types";
import { calculateCpuStats } from "../../../../../src/server/checker/runner/cpu/calculate";
function makeCore(user: number, nice: number, sys: number, idle: number, irq: number): CpuCoreSnapshot {
return { times: { idle, irq, nice, sys, user } };
}
describe("calculateCpuStats", () => {
test("单核心完全空闲", () => {
const before = [makeCore(0, 0, 0, 100, 0)];
const after = [makeCore(0, 0, 0, 200, 0)];
const stats = calculateCpuStats(before, after);
expect(stats.usagePercent).toBe(0);
expect(stats.idlePercent).toBe(100);
expect(stats.maxCoreUsagePercent).toBe(0);
expect(stats.minCoreUsagePercent).toBe(0);
expect(stats.logicalCoreCount).toBe(1);
});
test("单核心完全忙碌idle 不变)", () => {
const before = [makeCore(100, 0, 0, 100, 0)];
const after = [makeCore(200, 0, 0, 100, 0)];
// idle delta = 0, total delta = 100
// idlePercent = 0, usagePercent = 100
const stats = calculateCpuStats(before, after);
expect(stats.usagePercent).toBe(100);
expect(stats.idlePercent).toBe(0);
});
test("单核心部分使用", () => {
const before = [makeCore(100, 0, 0, 900, 0)];
const after = [makeCore(150, 0, 0, 950, 0)];
// idle delta = 50, total delta = 100
// idlePercent = 50, usagePercent = 50
const stats = calculateCpuStats(before, after);
expect(stats.usagePercent).toBe(50);
expect(stats.idlePercent).toBe(50);
});
test("多核心加权平均", () => {
// 核心 0: idle delta = 200, total delta = 1000 -> 80% usage
// 核心 1: idle delta = 800, total delta = 1000 -> 20% usage
const before = [makeCore(0, 0, 0, 1000, 0), makeCore(0, 0, 0, 1000, 0)];
const after = [makeCore(800, 0, 0, 1200, 0), makeCore(200, 0, 0, 1800, 0)];
const stats = calculateCpuStats(before, after);
// 总 idle = 200+800=1000, 总 delta = 1000+1000=2000
// idlePercent = 1000/2000*100 = 50
// usagePercent = 100 - 50 = 50
expect(stats.idlePercent).toBe(50);
expect(stats.usagePercent).toBe(50);
expect(stats.maxCoreUsagePercent).toBe(80);
expect(stats.minCoreUsagePercent).toBe(20);
expect(stats.logicalCoreCount).toBe(2);
expect(stats.perCoreUsagePercent).toEqual([80, 20]);
});
test("四核心各不相同", () => {
const bf = [
makeCore(1000, 0, 0, 9000, 0), // core 0 baseline
makeCore(1000, 0, 0, 9000, 0), // core 1
makeCore(1000, 0, 0, 9000, 0), // core 2
makeCore(1000, 0, 0, 9000, 0), // core 3
];
const af = [
makeCore(1900, 0, 0, 9100, 0), // delta: user=900, idle=100, total=1000 -> 90% usage, 10% idle
makeCore(1500, 0, 0, 9500, 0), // delta: user=500, idle=500, total=1000 -> 50% usage
makeCore(1200, 0, 0, 9800, 0), // delta: user=200, idle=800, total=1000 -> 20% usage
makeCore(1010, 0, 0, 9990, 0), // delta: user=10, idle=990, total=1000 -> 1% usage
];
const stats = calculateCpuStats(bf, af);
// 总 idle = 100+500+800+990 = 2390, 总 delta = 4000
// idlePercent = 2390/4000*100 = 59.75 -> 59.8
expect(stats.idlePercent).toBe(59.8);
expect(stats.usagePercent).toBe(40.2);
expect(stats.maxCoreUsagePercent).toBe(90);
expect(stats.minCoreUsagePercent).toBe(1);
expect(stats.perCoreUsagePercent).toEqual([90, 50, 20, 1]);
expect(stats.logicalCoreCount).toBe(4);
});
test("delta 为 0 时返回 0", () => {
const before = [makeCore(100, 0, 0, 100, 0)];
const after = [makeCore(100, 0, 0, 100, 0)];
const stats = calculateCpuStats(before, after);
expect(stats.usagePercent).toBe(0);
expect(stats.idlePercent).toBe(0);
});
test("保留 1 位小数", () => {
// 总 idle = 333, 总 delta = 1000 -> idlePercent = 33.3
const before = [makeCore(0, 0, 0, 1000, 0)];
const after = [makeCore(667, 0, 0, 1333, 0)];
const stats = calculateCpuStats(before, after);
// idle delta = 333, total delta = 1000
expect(stats.idlePercent).toBe(33.3);
expect(stats.usagePercent).toBe(66.7);
});
test("nice 和 irq 计入 total 但不影响 idle", () => {
const bf = [makeCore(0, 0, 0, 0, 0)];
const af = [makeCore(300, 100, 100, 400, 100)];
// total delta = 300+100+100+400+100 = 1000
// idle delta = 400
// idlePercent = 400/1000*100 = 40
const stats = calculateCpuStats(bf, af);
expect(stats.idlePercent).toBe(40);
expect(stats.usagePercent).toBe(60);
});
});