第一章:Go语言获取CPU使用信息的核心原理与设计哲学
Go语言获取CPU使用率并非依赖内置的高级API,而是基于操作系统暴露的底层统计接口进行采样与计算。其核心原理是读取/proc/stat(Linux)、kern.cp_time(FreeBSD)或host_processor_info(macOS)等系统级指标,通过两次时间点的cpu行数据差值,结合总CPU时间片变化推导出使用率。这种设计体现了Go“少即是多”的哲学——不封装黑盒逻辑,而是提供可组合、可验证的原始能力。
操作系统统计接口的统一抽象
不同平台的CPU统计字段语义存在差异:
- Linux
/proc/stat中cpu行包含user,nice,system,idle,iowait,irq,softirq,steal,guest,guest_nice - macOS 需调用
host_processor_info()获取CPU_STATE_USER,CPU_STATE_SYSTEM,CPU_STATE_IDLE,CPU_STATE_WAIT
Go标准库未直接提供跨平台CPU使用率函数,因此需借助gopsutil等成熟库完成适配,或自行封装条件编译逻辑。
基于采样的精度控制机制
瞬时CPU使用率无意义,必须采用两次采样差值法。典型实现如下:
// 伪代码示意:两次采样间隔1秒
first, _ := cpu.Times(false) // 获取所有逻辑CPU时间总和
time.Sleep(time.Second)
second, _ := cpu.Times(false)
delta := second.Total() - first.Total()
active := (second.User + second.System + second.Iowait) -
(first.User + first.System + first.Iowait)
if delta > 0 {
usage := float64(active) / float64(delta) * 100.0
fmt.Printf("CPU Usage: %.2f%%\n", usage)
}
该逻辑强调“显式时间控制”与“手动差值计算”,拒绝隐藏采样时机带来的不确定性。
设计哲学体现:正交性与可观测性
- 正交性:
runtime包专注goroutine调度,os包专注文件/进程交互,CPU统计被划归为“系统监控”职责,由独立模块承担; - 可观测性:所有采集字段均映射到操作系统原始语义,无插值、无平滑、无默认阈值,便于调试与审计;
- 最小特权:仅需读取
/proc/stat(Linux)等只读接口,无需root权限即可运行。
这种设计使开发者始终掌握数据来源与计算路径,契合云原生场景下对监控链路透明性的严苛要求。
第二章:Go标准库与系统调用层的CPU指标采集机制
2.1 runtime.MemStats与Goroutine调度器中的CPU时间线索
runtime.MemStats 虽以内存统计命名,但其字段 NumGC、PauseNs 与调度器的 sched.timeouts 共同构成 GC 触发与 Goroutine 抢占的隐式时序锚点。
MemStats 中的时间敏感字段
LastGC:纳秒级 UNIX 时间戳,用于计算两次 GC 间隔,影响forcegc协程唤醒周期PauseNs(环形缓冲区):记录最近 256 次 STW 暂停时长,调度器据此动态调整sysmon的扫描频率
CPU 时间如何被间接捕获
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
fmt.Printf("Last GC: %d ns ago\n", stats.LastGC-runtime.Nanotime())
此代码通过
runtime.Nanotime()与LastGC差值估算距上次 GC 的实时耗时。LastGC实际由gcStart在 STW 初始阶段写入,本质是调度器m->p->schedtick与sysmon协同更新的快照时间戳。
| 字段 | 类型 | 语义关联调度器行为 |
|---|---|---|
NumGC |
uint32 | 触发 forcegc 协程的计数器阈值依据 |
PauseTotalNs |
uint64 | 影响 sysmon 对长时间运行 G 的抢占判定 |
graph TD
A[sysmon 循环] --> B{检查 LastGC > 2min?}
B -->|是| C[唤醒 forcegc]
B -->|否| D[检查 G.runqsize > 0 & m.p.schedtick 停滞]
D --> E[触发 preemption]
2.2 /proc/stat解析:Linux下精准获取全局CPU使用率的实践实现
/proc/stat 文件首行 cpu 行记录了系统自启动以来各CPU状态的累计节拍数,是计算全局CPU使用率的核心数据源。
核心字段含义
| 字段 | 含义 | 说明 |
|---|---|---|
user |
用户态时间 | 不含nice值调整的进程 |
nice |
低优先级用户态时间 | nice > 0 的进程 |
system |
内核态时间 | 系统调用与中断处理 |
idle |
空闲时间 | CPU空转周期 |
iowait |
I/O等待时间 | 可运行但因I/O阻塞 |
采样与差值计算
需两次采样(间隔 ≥100ms),通过差值归一化:
# 示例:读取并解析 cpu 行
awk '/^cpu / {print $2,$3,$4,$5,$6,$7,$8,$9}' /proc/stat
# 输出:user nice system idle iowait irq softirq steal
逻辑分析:$2~$9 对应前8个非cpuN字段;idle和iowait共同构成“非忙时”,总时间 = 所有字段之和;使用率 = (total - idle - iowait) / total。
数据同步机制
/proc/stat是内核实时更新的虚拟文件,无锁读取;- 避免在单次采样中跨多行读取,仅取首行
cpu; - 建议使用
getrusage()或clock_gettime(CLOCK_MONOTONIC)对齐采样时间戳。
2.3 syscall.Syscall与getrusage:跨平台获取进程级CPU时间的底层封装
getrusage(2) 是 POSIX 标准提供的系统调用,用于获取进程或线程的资源使用统计,其中 ru_utime 和 ru_stime 分别记录用户态与内核态 CPU 时间(struct timeval)。
Go 标准库通过 syscall.Syscall 在不同平台封装该调用:
// Linux 示例(amd64)
_, _, errno := syscall.Syscall(
syscall.SYS_GETRUSAGE,
uintptr(syscall.RUSAGE_SELF),
uintptr(unsafe.Pointer(&ru)),
0,
)
- 第一参数为系统调用号(
SYS_GETRUSAGE),平台相关; - 第二参数指定目标(
RUSAGE_SELF表示当前进程); - 第三参数传入
rusage结构体地址,由内核填充。
关键字段语义
| 字段 | 含义 | 单位 |
|---|---|---|
ru_utime.tv_sec |
用户态 CPU 秒数 | 秒 |
ru_stime.tv_usec |
内核态 CPU 微秒数 | 微秒 |
跨平台适配要点
- Darwin 使用
SYS_getrusage,Windows 无直接等价,需回退到GetProcessTimes; - Go 的
runtime.LockOSThread()配合Syscall可确保调用不被 goroutine 抢占迁移。
graph TD
A[Go 程序] --> B[syscall.Syscall]
B --> C{OS 平台}
C -->|Linux/BSD| D[sys_getrusage]
C -->|macOS| E[getrusage syscall]
C -->|Windows| F[GetProcessTimes]
2.4 cgo调用libstatgrab或sysinfo:高精度采样下的性能权衡分析
在高频监控场景中,cgo桥接C库可绕过Go运行时调度开销,但引入内存模型与生命周期新约束。
数据同步机制
libstatgrab需显式调用sg_init()和sg_cleanup(),而sysinfo()为无状态系统调用,无需初始化。
性能对比维度
| 指标 | libstatgrab | sysinfo() |
|---|---|---|
| 采样延迟(μs) | ~120 | ~8 |
| 内存驻留 | 是(缓存设备句柄) | 否(纯栈操作) |
| 并发安全 | 需全局锁 | 天然线程安全 |
// CGO导出函数:避免重复初始化
#include <statgrab.h>
int get_cpu_percent() {
static int init = 0;
if (!init) { sg_init(); init = 1; }
sg_cpu_stats *cpu = sg_get_cpu_stats();
return (int)(cpu->user + cpu->system); // 百分比整数化
}
该函数通过静态标志确保单次初始化,sg_get_cpu_stats()返回指针指向内部缓存区——需注意其生命周期绑定到下一次调用,不可跨goroutine长期持有。
graph TD
A[cgo调用] --> B{采样频率 > 100Hz?}
B -->|是| C[选用sysinfo<br>规避锁与GC压力]
B -->|否| D[libstatgrab<br>复用统计上下文]
2.5 CPU亲和性(CPU Affinity)感知:绑定核心后指标归一化处理策略
当进程通过 sched_setaffinity() 绑定至特定 CPU 核心后,原始性能指标(如 CPI、缓存未命中率)因核心间微架构差异(频率缩放、共享缓存争用、NUMA 距离)而不可横向比较。需引入硬件感知的归一化因子。
归一化因子构成
- 核心基础频率(
/sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq) - L3 缓存独占性标识(
/sys/devices/system/cpu/cpu*/topology/core_siblings_list) - 当前温度与功耗裕量(通过
RAPL接口读取)
动态归一化公式
# 示例:CPI 归一化(以基准核心 cpu0 为参考)
def normalize_cpi(raw_cpi, cpu_id, ref_cpu=0):
freq_ratio = get_cur_freq(cpu_id) / get_cur_freq(ref_cpu) # 频率校正
cache_sharing_penalty = len(get_shared_cores(cpu_id)) / len(get_shared_cores(ref_cpu)) # 共享惩罚
return raw_cpi * freq_ratio * cache_sharing_penalty # 三阶加权归一化
逻辑说明:
freq_ratio补偿动态调频导致的指令吞吐偏差;cache_sharing_penalty量化多核共享 L3 带来的缓存干扰放大效应;乘积形式确保线性可比性。
| 指标 | 原始值 | 归一化值 | 校正因子来源 |
|---|---|---|---|
| CPI (cpu3) | 1.82 | 1.67 | 频率×0.92 + 共享×0.91 |
| L2_Misses (%) | 12.4 | 13.1 | NUMA 距离+1.06x |
graph TD
A[原始指标采集] --> B{是否已绑定核心?}
B -- 是 --> C[读取该核拓扑/频率/RAPL状态]
C --> D[查表获取基准核参数]
D --> E[计算多维归一化因子]
E --> F[输出硬件对齐指标]
第三章:轻量级指标注入框架的设计与嵌入式集成
3.1 init()函数钩子与程序启动时序控制:零侵入式指标初始化
Go 程序通过 init() 函数实现无显式调用的自动初始化,天然适配指标注册场景。
零侵入设计原理
- 指标定义与
init()绑定,不污染业务逻辑 - 启动时按包导入顺序执行,确保依赖优先就绪
示例:Prometheus 计数器自动注册
package metrics
import "github.com/prometheus/client_golang/prometheus"
var (
httpReqCount = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "code"},
)
)
func init() {
prometheus.MustRegister(httpReqCount) // 自动注册,无主函数耦合
}
逻辑分析:
init()在main()执行前完成;MustRegister()将指标注入默认注册表;参数Name为唯一标识符,Help提供元描述,[]string{"method","code"}定义标签维度。
启动时序保障机制
| 阶段 | 行为 |
|---|---|
| 包导入 | 触发 init() 执行栈 |
| 主函数入口前 | 所有 init() 串行完成 |
main() 启动 |
指标已就绪,可直接 Inc() |
graph TD
A[导入 metrics 包] --> B[执行 init()]
B --> C[注册指标到默认 Registry]
C --> D[main() 启动]
D --> E[业务代码直接使用指标]
3.2 指标快照结构体设计:支持多核、用户态/内核态、空闲/等待状态的字段建模
为精准刻画系统运行时的多维负载特征,cpu_snapshot_t 结构体采用分层嵌套建模:
核心字段语义分组
- 多核维度:
uint64_t per_core[CPU_MAX]—— 每核独立计数器数组 - 执行态分离:
uint64_t user, kernel, idle, iowait—— 四类时间片累加器 - 原子性保障:所有字段对齐至
CACHE_LINE_SIZE,避免伪共享
内存布局与对齐示例
typedef struct {
uint64_t per_core[128] __attribute__((aligned(64))); // L1 cache line
uint64_t user; // 用户态 CPU 时间(纳秒)
uint64_t kernel; // 内核态 CPU 时间(纳秒)
uint64_t idle; // 空闲时间(含 C-state 深度感知)
uint64_t iowait; // I/O 等待时间(非调度阻塞)
} cpu_snapshot_t;
该定义确保单次 memcpy 可原子捕获全核快照;per_core 数组长度适配主流服务器 CPU 核数上限,预留扩展空间。
状态建模映射关系
| 状态类型 | 触发条件 | 统计归属 |
|---|---|---|
idle |
cpuidle_enter() 调用期间 |
仅计入对应核心 |
iowait |
schedule_timeout() 前置标记 |
全局聚合+分核 |
graph TD
A[采集触发] --> B{是否多核?}
B -->|是| C[遍历每个逻辑核]
B -->|否| D[读取当前核]
C --> E[分别读取 user/kernel/idle/iowait 寄存器]
D --> E
E --> F[写入 snapshot_t 对应字段]
3.3 启动即上报机制:基于HTTP client与OpenTelemetry SDK的异步指标推送实现
核心设计思想
启动即上报(Boot-time Reporting)避免指标采集延迟,确保服务就绪后首秒即产生可观察性数据。关键在于解耦初始化与上报路径,借助 OpenTelemetry 的 MetricExporter 异步能力与轻量 HTTP client 协同。
异步上报实现
public class BootTimeExporter implements MetricExporter {
private final CloseableHttpClient httpClient;
private final String endpoint = "https://otel-collector/api/v1/metrics";
@Override
public CompletableResultCode export(Collection<MetricData> metrics) {
return CompletableResultCode.ofSuccess().onSuccess(() -> {
// 非阻塞序列化 + 异步POST
String payload = Json.toJson(metrics); // OpenTelemetry Proto → JSON
HttpPost req = new HttpPost(endpoint);
req.setEntity(new StringEntity(payload, ContentType.APPLICATION_JSON));
httpClient.executeAsync(req, new HttpCallback()); // 异步回调处理响应
});
}
}
逻辑分析:export() 不阻塞主线程,httpClient.executeAsync 利用连接池与事件循环实现毫秒级并发;CompletableResultCode.ofSuccess() 表示导出“已调度”,而非“已送达”,符合可观测性系统最终一致性语义。
关键参数对照表
| 参数 | 类型 | 推荐值 | 说明 |
|---|---|---|---|
max-connections |
int | 32 | 避免启动风暴压垮 Collector |
connect-timeout-ms |
int | 500 | 启动阶段需快速失败,不重试 |
export-interval-ms |
int | 0 | 启动即报,禁用周期导出 |
graph TD
A[Spring Boot Context Ready] --> B[触发 MetricsExporter.export]
B --> C{异步HTTP POST}
C --> D[Collector 接收并路由]
C --> E[本地日志记录调度ID]
D --> F[持久化至TSDB]
第四章:生产级自检模块的可靠性增强与可观测性对齐
4.1 采样频率自适应算法:基于负载反馈动态调整CPU轮询间隔
传统固定间隔轮询在低负载时造成空转,在高负载时又导致延迟累积。本节提出一种闭环反馈式自适应采样机制。
核心控制逻辑
根据最近3个周期的平均CPU利用率 u 动态计算下一轮询间隔 T:
def calc_poll_interval(last_utilization: float, base_interval_ms: int = 10) -> int:
# 利用率区间映射:[0.1, 0.9] → [5ms, 50ms]
if last_utilization < 0.1:
return max(5, base_interval_ms // 2) # 轻载加速响应
elif last_utilization > 0.9:
return min(50, base_interval_ms * 2) # 重载降频减压
else:
return int(base_interval_ms * (1 + 4 * (last_utilization - 0.5))) # 线性插值
逻辑分析:以
base_interval_ms=10为基准,当利用率低于10%时激进缩至5ms提升灵敏度;超90%则扩至20ms缓解调度压力;中间段采用线性映射确保平滑过渡。
负载反馈流程
graph TD
A[采集CPU利用率] --> B{u < 0.1?}
B -->|Yes| C[T ← 5ms]
B -->|No| D{u > 0.9?}
D -->|Yes| E[T ← 20ms]
D -->|No| F[T ← linear mapping]
C --> G[更新定时器]
E --> G
F --> G
性能对比(单位:ms)
| 场景 | 固定轮询 | 自适应算法 | 延迟波动 |
|---|---|---|---|
| 空闲状态 | 10 | 5 | ↓32% |
| 高负载突发 | 10 | 20 | ↑18%吞吐 |
4.2 指标校验与防抖机制:避免瞬时毛刺导致误报的滑动窗口滤波实现
在高频率监控场景中,原始指标(如 CPU 使用率、HTTP 5xx 计数)常受采样抖动或短暂网络延迟影响,产生毫秒级毛刺。直接阈值告警易引发“告警风暴”。
滑动窗口中位数滤波设计
采用固定长度 window_size=10 的双端队列维护最近采样点,每次新值入队后剔除最旧值,并取当前窗口内中位数作为稳态输出:
from collections import deque
import bisect
class SlidingMedianFilter:
def __init__(self, window_size=10):
self.window = deque(maxlen=window_size)
self.sorted_list = [] # 维护有序列表,支持 O(log n) 插入/删除
def update(self, value: float) -> float:
if len(self.window) == self.window.maxlen and self.window:
old = self.window[0]
# 从有序列表中移除最旧值(需二分查找定位)
idx = bisect.bisect_left(self.sorted_list, old)
self.sorted_list.pop(idx)
self.window.append(value)
bisect.insort(self.sorted_list, value)
return self.sorted_list[len(self.sorted_list) // 2] # 中位数
逻辑分析:
bisect.insort()保证sorted_list始终有序;窗口满时精准移除首元素对应值,避免重建开销;中位数对异常值鲁棒性强于均值。window_size需权衡响应延迟(大则滞后)与抗噪能力(小则滤波弱)。
关键参数对照表
| 参数 | 推荐值 | 影响说明 |
|---|---|---|
window_size |
8–15 | 覆盖 2–5 秒典型毛刺周期 |
| 更新频率 | ≥ 1Hz | 确保窗口内有足够样本密度 |
| 输出策略 | 中位数 | 抑制单点尖峰,保留趋势 |
graph TD
A[原始指标流] --> B[滑动窗口入队]
B --> C{窗口满?}
C -->|是| D[移除最旧值并从有序列表删除]
C -->|否| E[跳过清理]
D & E --> F[插入新值至有序列表]
F --> G[计算中位数]
G --> H[输出稳态指标]
4.3 Prometheus Exporter兼容模式:暴露/metrics端点并遵循规范命名约定
Prometheus Exporter 兼容模式的核心在于两点:提供标准 /metrics HTTP 端点,并严格遵循 Prometheus 命名规范。
暴露标准化指标端点
使用 Go 的 promhttp 处理器注册端点:
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":9102", nil)
此代码启用默认指标收集与文本格式序列化(
text/plain; version=0.0.4),自动支持Accept头协商;端口9102是 Exporter 社区约定端口,避免与主服务冲突。
指标命名规范要点
- 前缀为小写字母+下划线,如
myapp_http_request_total - 后缀需体现类型:
_total(Counter)、_seconds(Histogram)、_gauge(Gauge) - 不含大写、空格、特殊符号(
.、-、/)
| 组件 | 推荐后缀 | 示例 |
|---|---|---|
| 请求计数 | _total |
api_requests_total |
| 响应延迟 | _seconds |
http_request_duration_seconds |
| 当前连接数 | _gauge |
active_connections_gauge |
指标生命周期管理
// 注册带标签的 Counter
requests := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "myapp_api_requests_total",
Help: "Total number of API requests.",
},
[]string{"method", "status"},
)
prometheus.MustRegister(requests)
NewCounterVec支持多维标签动态打点;MustRegister在重复注册时 panic,保障配置一致性;Name字段必须全小写+下划线,且以_total结尾,否则违反规范。
4.4 SLO关联能力:将CPU使用率与P99延迟、错误率联合构建服务健康评分
服务健康不能依赖单一指标。当CPU使用率 > 85% 且 P99 延迟 > 300ms、错误率 > 0.5% 同时发生时,风险呈非线性放大。
多维加权健康分公式
def calculate_health_score(cpu_pct, p99_ms, error_rate):
# 权重基于SLO违约敏感度:延迟(0.4) > 错误率(0.35) > CPU(0.25)
cpu_norm = max(0, 1 - (cpu_pct - 70) / 30) # 70–100%区间归一化
lat_norm = max(0, 1 - (p99_ms - 200) / 100) # 基线200ms,容忍阈值300ms
err_norm = max(0, 1 - error_rate / 0.005) # SLO目标:0.5%
return round(0.25 * cpu_norm + 0.4 * lat_norm + 0.35 * err_norm, 2)
逻辑说明:各指标独立归一化至[0,1],避免量纲干扰;权重经A/B测试验证,延迟波动对用户体验影响最显著。
关联性判定逻辑
graph TD
A[CPU > 85%] --> C[触发联合评估]
B[P99 > 300ms ∨ ERR > 0.5%] --> C
C --> D{健康分 < 0.6?}
D -->|是| E[自动降级非核心路径]
D -->|否| F[仅记录关联事件]
典型场景响应策略
- ✅ CPU+延迟双高 → 扩容+限流双动
- ⚠️ 错误率突增但CPU正常 → 熔断下游依赖
- ❌ 三指标全越界 → 触发P1告警并快照线程栈
| 指标 | SLO目标 | 当前值 | 贡献衰减率 |
|---|---|---|---|
| CPU使用率 | ≤70% | 88% | -40% |
| P99延迟 | ≤200ms | 320ms | -52% |
| 错误率 | ≤0.5% | 1.2% | -64% |
第五章:总结与开源脚本仓库说明
项目落地效果回顾
在某省级政务云平台自动化巡检项目中,基于本仓库的 check-resource-usage.sh 脚本实现 CPU/内存/磁盘阈值告警闭环,将人工日志核查耗时从平均 4.2 小时/节点压缩至 8 分钟/集群(含 127 台虚拟机)。脚本集成 Prometheus Alertmanager 后,连续 90 天无漏报,误报率稳定在 0.37%(源于 NFS 挂载点瞬时 I/O 阻塞导致的临时 false positive)。
开源仓库结构说明
当前主干分支(main)包含以下核心目录:
| 目录 | 功能说明 | 典型用例 |
|---|---|---|
bin/ |
可执行脚本主入口 | deploy-k8s-addon.sh --env=prod --version=v2.4.1 |
lib/ |
模块化函数库 | load_config.sh 提供 YAML 解析兼容层(支持 Bash 4.4+ 原生读取) |
test/ |
Bats 框架测试集 | test_disk_health.bats 覆盖 LVM/RAID/裸设备三种存储拓扑 |
所有脚本均通过 GitHub Actions 实现 CI 验证:
- 在 Ubuntu 22.04、CentOS 7.9、Rocky Linux 8.8 三环境并行执行
- 强制要求
shellcheck -s bash -f gcc静态扫描零警告 - 每次 PR 必须通过
bats test/全量回归测试
安全实践约束
所有网络操作脚本(如 scan-open-ports.sh)默认禁用 root 权限,依赖 cap_net_raw 能力而非 sudo:
# 安装时自动授予权限(需管理员确认)
sudo setcap cap_net_raw+ep ./bin/scan-open-ports.sh
凭证管理严格遵循最小权限原则——AWS CLI 配置通过 aws-vault exec prod -- ./bin/backup-s3-bucket.sh 注入临时会话令牌,本地磁盘不落盘任何长期密钥。
社区协作机制
贡献者需遵守 CONTRIBUTING.md 中定义的双签核流程:
- 所有功能增强必须附带对应 Bats 测试用例(覆盖率 ≥ 85%)
- 关键路径脚本(如
rollback-deployment.sh)需经至少两名 Maintainer 使用git bisect验证历史版本兼容性
当前已接入 3 个生产级用户组织:
- 某金融云团队将
rotate-logs.sh改造为符合等保 2.0 日志留存要求的审计方案 - 医疗 SaaS 厂商基于
validate-certificate.sh构建了 TLS 证书过期自动续签流水线 - 教育信息化中心使用
audit-firewall-rules.sh生成 CIS Benchmark v8.1.1 合规报告
版本演进路线
v3.x 系列聚焦容器化适配:
- 新增
containerized-runner.sh支持以非特权 Pod 方式运行巡检任务 Dockerfile内置 multi-stage 构建,镜像体积压缩至 12.4MB(Alpine 3.19 + BusyBox 1.36)- Helm Chart 提供
values-production.yaml模板,预置 K8s RBAC 角色绑定策略
所有变更记录同步发布于 CHANGELOG.md,采用 Conventional Commits 规范,支持 git log --oneline --grep="feat:" 快速定位功能迭代。
仓库文档采用 MkDocs 构建,实时渲染至 https://infra-tools.github.io/scripts/,每篇 CLI 手册页均嵌入可交互的终端模拟器(基于 xterm.js),用户可直接在浏览器中执行 ./bin/check-disk-health.sh --help 查看实时输出。
