feat(server): 资源监控——/proc 与 cgroup 采样、/system/resources 趋势,心跳带进程 CPU

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 17:58:08 +08:00
1 parent 744252b969
commit 2ce1841628
6 files changed
+712 -2

No files matched your search

+14
View File
@@ -25,6 +25,7 @@ import {
safeHostname,
startHeartbeatWriter,
} from "./heartbeat.ts";
import { createResourceSampler, type ResourceSampler } from "./resources.ts";
import { type BatchOptions, CLEANUP_JOB_IDS, RETENTION_JOBS } from "./retention.ts";
import { buildStorage, type StoragePaths, storageHealth } from "./storage.ts";
@@ -58,6 +59,8 @@ export interface SystemModuleOptions {
retentionJobs?: boolean;
/** 保留清理的分批参数(测试) */
retentionBatch?: BatchOptions;
/** 资源采样器(测试注入);false 不起;缺省 api / all 角色注册路由时起一个每 5 秒读 /proc 的采样器 */
resourceSampler?: ResourceSampler | false;
}
type Infer<T> = T extends z.ZodType ? z.infer<T> : Record<string, never>;
@@ -100,6 +103,8 @@ export function createSystemModule(opts: SystemModuleOptions = {}): ServerModule
} {
const startedAt = new Date();
let writer: HeartbeatWriter | null = null;
let sampler: ResourceSampler | null = null;
const ownSampler = !opts.resourceSampler;
return {
name: "system",
apiHeartbeat: () => writer,
@@ -113,6 +118,8 @@ export function createSystemModule(opts: SystemModuleOptions = {}): ServerModule
sample: () => ({ sseConnections: sseConnectionCount() }),
});
}
if (opts.resourceSampler) sampler = opts.resourceSampler;
else if (opts.resourceSampler !== false && !sampler) sampler = createResourceSampler();
const selfKind = ctx.role === "all" ? "all" : ctx.role === "api" ? "api" : null;
const selfId = selfKind ? instanceIdOf(selfKind, safeHostname(), Deno.pid) : undefined;
const paths = (): StoragePaths => {
@@ -140,6 +147,11 @@ export function createSystemModule(opts: SystemModuleOptions = {}): ServerModule
});
});
route(app, Api.system.healthHistory, async ({ query }) => await loadHistory(ctx.db, query.hours));
route(app, Api.system.resources, async ({ query }) => {
// 采样器只在注册路由的进程里起;测试关掉时临时起一个不自动采样的
sampler ??= createResourceSampler({ autoStart: false });
return await sampler.snapshot(query.minutes);
});
route(app, Api.system.storage, async () =>
buildStorage({
db: ctx.db,
@@ -226,6 +238,8 @@ export function createSystemModule(opts: SystemModuleOptions = {}): ServerModule
async stop() {
await writer?.stop();
writer = null;
if (ownSampler) sampler?.stop();
sampler = null;
},
};
}