import { z } from "zod"; import { Timestamp } from "./common.ts"; // 资源监控(界面第二轮 V 线,参考 MoviePilot 仪表盘的 CPU / 内存 / 网络组件): // api 进程每 5 秒读一次 /proc 与 cgroup v2,采样只放内存(保留 6 小时,进程重启后从头攒)。 // 容器里优先用 cgroup(本容器的 CPU 占用、内存、磁盘读写),取不到时退回 /proc 整机口径;都取不到的字段为 null。 /** 采集口径:cgroup = 本容器(cgroup v2);host = /proc 整机;none = 取不到 */ export const ResourceScope = z.enum(["cgroup", "host", "none"]); export type ResourceScope = z.infer; const Num = z.number().nonnegative().nullable(); export const ResourceSample = z.object({ at: Timestamp, /** CPU 占用 %(相对可用核数,0–100) */ cpuPercent: Num, memoryUsedBytes: Num, memoryPercent: Num, netRxBps: Num, netTxBps: Num, diskReadBps: Num, diskWriteBps: Num, }); export type ResourceSample = z.infer; export const SystemResources = z.object({ at: Timestamp, /** 采样间隔(秒) */ intervalSec: z.number().positive(), /** 返回的趋势每点代表的秒数(长时间窗口按桶取平均) */ bucketSec: z.number().positive(), cpu: z.object({ scope: ResourceScope, percent: Num, /** 可用核数:cgroup cpu.max 的限额,否则为在线核数 */ cores: Num, /** 整机 CPU 占用 %(/proc/stat),与 scope 无关 */ hostPercent: Num, /** 整机 1 / 5 / 15 分钟平均负载(3 个数) */ load: z.array(z.number()).nullable(), }), memory: z.object({ scope: ResourceScope, /** 已用:cgroup 为 memory.current − inactive_file(同 docker stats);整机为 MemTotal − MemAvailable */ usedBytes: Num, /** 上限:cgroup memory.max(不限时退回整机总量) */ limitBytes: Num, percent: Num, hostTotalBytes: Num, hostAvailableBytes: Num, swapUsedBytes: Num, }), network: z.object({ scope: ResourceScope, rxBps: Num, txBps: Num, rxTotalBytes: Num, txTotalBytes: Num, }), diskIo: z.object({ scope: ResourceScope, readBps: Num, writeBps: Num }), /** 整机已运行秒数(/proc/uptime) */ hostUptimeSec: Num, samples: z.array(ResourceSample), }); export type SystemResources = z.infer; export const SystemResourcesQuery = z.object({ /** 趋势窗口(分钟),5–360,缺省 60 */ minutes: z.coerce.number().int().min(5).max(360).default(60), }); export type SystemResourcesQuery = z.infer;