太长不看版#
这篇讲的是 Categraf v0.5.5 在 Windows Server 2022 上对接夜莺 (Nightingale) 的工程深度,不是官方 quickstart 的中文翻版。核心贡献是四个隐蔽陷阱的解决方案和一份生产可用的看板。
部署踩的四个坑(按"日志里看不到"的严重程度排序):
nvidia_smi是单例插件,不吃[[instances]]包装。写错了不报错,整个插件静默 skip,GPU 指标全没。- v0.5.5 的
nvidia_smi_*指标名全改了,加了_ratio/_bytes/_watts/_clock_hz后缀,老教程的 PromQL 全部查不到数据。 netstat插件在 Windows 上假装启动,它依赖/proc/net/tcp,Windows 上根本没这个路径,插件无声 no-op。- Categraf 在 Windows 必须作为 Service 启动,命令行直接运行会以
failed to run windows service: The service process could not connect to the service controller立即退出。
设计层面的三个发现:
exec插件 + PowerShell 是 Windows 上所有"缺失原生指标"的万能逃生口- v0.5.5 的节流原因指标(
clocks_event_reasons_hw_thermal_slowdown等)是监控 GPU 真实健康的金矿,远比简单看温度/使用率准确 - 夜莺看板的
util字段(bytesIEC/hertz/percent(0-100)/watt/celsius)会自动做单位换算,设计时把原始单位和展示单位拆开能省掉一堆 PromQL 的/ 1e6之类的脏计算
产物(见文末):
- 一份 41 面板的夜莺仪表盘 JSON,包含 16 个 GPU 专项面板
- 一个 80 行的 PowerShell 采集脚本,补齐 Windows 平台下缺失的 TCP 状态、RDP 会话数、事件日志错误数、pagefile 使用率、卡死进程数、待重启状态、uptime 等原生指标
1. 背景:被监控的这台机器是什么#
为了让后面的技术决策有上下文,先交代一下被监控的目标长什么样:
角色 Windows Server 2022 RDS (Remote Desktop Services)
用途 多用户远程跑 Cadence 16.6 / PADS VX2.4 / AutoCAD 2020 / Creo 5.0
宿主 Proxmox VE 8.4 (AMD Ryzen 9 9950X)
资源 32 vCPU / 24 GB RAM
GPU GALAX RTX 5060 Ti 16GB (VFIO 直通)
网络 单万兆 vmbr0, 1 个 RDP 接入点, 4-8 个活跃会话
这是一台生产工作站,不是实验环境。监控的目标非常具体:
- GPU 使用率/温度/功耗 — CAD 软件对 GPU 的依赖从 2D 到 3D 不等,需要知道谁在吃显卡
- 显存压力 — 16 GB 显存看上去很大,Creo 打开复杂装配体瞬间就能吃掉 12 GB
- PCIe 链路完整性 — 直通 GPU 的 PCIe 链路质量直接决定 VM 稳定性,这台机器之前出现过 PCIe 降级事件
- RDP 会话健康 — 会话数、卡死进程、事件日志错误
- 系统运行时间 — uptime 反复置零等于死机,直接说明硬件/驱动有问题
核心诉求是"问题发生时要有证据链可查",而不是"阻止问题发生"。间歇性硬件故障没办法预防,只能用监控数据为事后分析兜底。
2. 选型:为什么是 Categraf + 夜莺#
这台机器所在的组织已经有一套夜莺(Nightingale,CNCF sandbox 阶段的国产 Prometheus 生态监控系统)实例在内网跑着(下文一律用 n9e.internal.example.com:17000 占位,真实地址请换成你自己的),提供后端存储和告警能力,所以 agent 侧的选择空间就收窄了。
候选方案和最终淘汰原因:
| 方案 | 结果 | 原因 |
|---|---|---|
| windows_exporter | 淘汰 | 纯 pull 模式,需要在宿主机侧加 scrape 配置;夜莺默认是 push 模型,集成要写一堆 Prometheus Remote Write proxy |
| telegraf | 淘汰 | 和夜莺的 ident 体系不原生兼容,仪表盘模板要重写 |
| categraf | 采用 | 夜莺官方推荐 agent,原生 push 到 /prometheus/v1/write,ident 体系自动对齐,官方维护"Windows Host by Categraf"仪表盘模板可作为起点 |
| 夜莺 ibex 模式 | 淘汰 | 需要在 VM 内暴露端口被拉取,RDP 会话进来的用户可能看到端口 |
Categraf 本质是一个内置了 80+ 输入插件的单二进制 agent,用 Go 写成,支持 Windows/Linux/macOS 交叉编译。它和夜莺的配合路径是:
flowchart LR
A["Categraf Agent
Windows Service"] -->|采集| B["系统 / GPU / 进程指标"]
A -->|"Prometheus Remote Write"| C["夜莺 n9e-center
:17000"]
A -->|"Heartbeat POST /v1/n9e/heartbeat"| C
C -->|存储| D["VictoriaMetrics / TSDB"]
C -->|"PromQL 查询"| E["夜莺 Web UI
仪表盘 / 告警"]
style A fill:#3b82f6,color:#fff
style C fill:#a855f7,color:#fff
style D fill:#6366f1,color:#fff
style E fill:#ec4899,color:#fff
关键点:
- Agent 侧每 15 秒 push 一次指标批,每 10 秒发一次 heartbeat
- Heartbeat 负责让夜莺的"机器列表"活跃,指标走 Remote Write 链路
- 这两条是独立的 HTTP 请求,各自有超时和重试逻辑
Categraf 的 zip 发布包解压后长这样:
categraf-v0.5.5-windows-amd64/
├── categraf.exe # 主程序(约 170 MB)
├── conf/
│ ├── config.toml # 主配置
│ ├── input.cpu/cpu.toml
│ ├── input.mem/mem.toml
│ ├── input.disk/disk.toml
│ ├── input.nvidia_smi/nvidia_smi.toml
│ ├── input.exec/exec.toml
│ └── ... 约 80 个 input.* 子目录
└── scripts/
├── install.bat
└── uninstall.bat
注意那 80 个 input.* 子目录——这是第一个踩坑点。
3. 安装:Windows Service 是唯一的正确姿势#
从 flashcat 镜像下载 Windows amd64 版本,解压到 C:\Program Files\categraf\。然后第一次尝试从命令行启动:
PS> & "C:\Program Files\categraf\categraf.exe"
立即报错:
2026/04/10 14:07:59 main_windows.go:42: F! failed to run windows service:
The service process could not connect to the service controller.
看一眼 main_windows.go:42,这一行是 svc.Run(serviceName, ...) 的返回处理——categraf 的 Windows 入口函数强制走 Windows Service Control Manager (SCM)。它不像在 Linux 上那样可以前台运行一次看看日志。
这个设计其实合理:Windows 环境下长驻进程的标准做法就是注册成服务,让 SCM 负责启动/停止/故障重启/日志接入事件查看器。但从 Linux 习惯过来的人会卡一下。
正确的安装命令:
# 1. 注册为 Windows Service
& "C:\Program Files\categraf\categraf.exe" --win-service-install
# 输出:done
# 2. 启动服务
& "C:\Program Files\categraf\categraf.exe" --win-service-start
# 输出:done
# 3. 验证
Get-Service -Name categraf | Format-List Name, Status, StartType
# Name : categraf
# Status : Running
# StartType : Automatic
几个值得记住的细节:
--win-service-install会把当前路径的categraf.exe写入 SCM 的ImagePath,安装后如果你移动或重命名了 exe,服务就启动不了。干净做法是先把文件放到最终位置,再 install--win-service-install默认StartType = Automatic,开机自启- 历史 alias
--install和--start在 v0.5.5 仍然保留但不再推荐,某些中文教程上还在用 - 卸载用
--win-service-stop+--win-service-uninstall,先停再删
服务启动后,运行日志不在 stdout——它们被 SCM 接管后会同时写入:
- Windows Event Log (Application) — 仅 Service 级别事件(启动/停止/崩溃)
C:\Program Files\categraf\logs\categraf.log— categraf 自己的轮转日志(需要在config.toml里显式配置)
这意味着排障时的第一反应不是 journalctl -fu 或 tail -f,而是:
Get-Content "C:\Program Files\categraf\logs\categraf.log" -Tail 50 -Wait
4. 默认模板陷阱:80 个插件同时启动#
服务启动后第一眼的日志(节选):
2026/04/10 14:08:49 agent.go:38: I! agent starting
2026/04/10 14:08:49 metrics_agent.go:252: E! input: local.amd_rocm_smi not supported
2026/04/10 14:08:49 metrics_agent.go:252: E! input: local.arp_packet not supported
2026/04/10 14:08:49 metrics_agent.go:252: E! input: local.conntrack not supported
2026/04/10 14:08:49 metrics_agent.go:327: I! input: local.cpu started
2026/04/10 14:08:49 metrics_agent.go:252: E! input: local.dcgm not supported
2026/04/10 14:08:49 metrics_agent.go:327: I! input: local.disk started
2026/04/10 14:08:49 metrics_agent.go:327: I! input: local.diskio started
2026/04/10 14:08:49 metrics_agent.go:286: E! failed to init input: local.emc_unity error: did not provide IP
2026/04/10 14:08:49 ethtool_notlinux.go:65: E! Current platform is not supported
2026/04/10 14:08:49 metrics_agent.go:327: I! input: local.ethtool started
2026/04/10 14:08:49 metrics_agent.go:327: I! input: local.greenplum started
2026/04/10 14:08:49 metrics_agent.go:327: I! input: local.hadoop started
2026/04/10 14:08:49 metrics_agent.go:252: E! input: local.iptables not supported
...
2026/04/10 14:08:49 metrics_agent.go:327: I! input: local.keepalived started
2026/04/10 14:08:49 metrics_agent.go:327: I! input: local.nfsclient started
2026/04/10 14:08:49 metrics_agent.go:327: I! input: local.self_metrics started
2026/04/10 14:08:49 metrics_agent.go:327: I! input: local.sockstat started
2026/04/10 14:08:49 metrics_agent.go:327: I! input: local.systemd started
这份日志里有两类奇怪的东西:
not supported—amd_rocm_smi/conntrack/iptables/kernel/linux_sysctl_fs这些是 Linux-only 插件,在 Windows 上直接跳过,这是预期的started—greenplum/hadoop/keepalived/nfsclient/sockstat/systemd等等——这些也在运行?我并没有配置它们!
打开 conf/input.keepalived/keepalived.toml 看一眼:
# # collect interval
# interval = 15
[[instances]]
# keepalived_json_path = "/tmp/keepalived.json"
# ...
看到了吗?[[instances]] 这一行没有注释掉。在 TOML 里,[[array]] 语法会构造一个空的实例,Categraf 的插件加载器只要看到有 [[instances]] 就会实例化一个默认配置的插件实例。这些默认模板大多数用了空配置能跑起来——既然"跑起来"了(调用 constructor 没报错),就会出现在 started 列表里,每 15 秒浪费一次 CPU 去采集根本不存在的 keepalived.json 或 systemd 服务状态。
这是一个让新手百思不得其解的设计:它的意图应该是"模板文件作为文档,注释状态表示未启用",但大部分模板的 [[instances]] 行忘了注释,所以默认效果反过来了。
清理方式有两种:
方式 A(保守):删除每个不用的 input.*/ 下的 .toml 文件,保留目录。但这不够彻底——v0.5.5 某些插件即使没有 .toml 也会用 compile-time 默认加载。
方式 B(彻底):直接删除不需要的 input.*/ 整个目录。Categraf 启动时只扫描 conf/input.*/ 存在的目录。
排障过程中我一开始保留了 9 个目录(包括 input.netstat),结果后面发现 netstat 在 Windows 上是伪启动——具体过程在§6。所以最终的保留清单是 8 个目录,input.netstat 被移除,TCP 状态采集改由 input.exec + PowerShell 承担:
$keepDirs = @(
'input.cpu', 'input.mem', 'input.disk', 'input.diskio',
'input.system', 'input.net',
'input.nvidia_smi', 'input.exec'
)
Get-ChildItem "C:\Program Files\categraf\conf" -Directory -Filter "input.*" |
Where-Object { $keepDirs -notcontains $_.Name } |
Remove-Item -Recurse -Force
清理完重启,启动日志立刻变成干净的八行:
I! agent starting
I! input: local.cpu started
I! input: local.disk started
I! input: local.diskio started
I! input: local.exec started
I! input: local.mem started
I! input: local.net started
I! input: local.nvidia_smi started
I! input: local.system started
I! [*agent.MetricsAgent] started
I! agent started
5. nvidia_smi 插件的两个隐蔽坑#
5.1 单例配置格式#
按照大部分 Categraf 插件的惯例,配置长这样:
[[instances]]
interval_times = 1
nvidia_smi_command = "C:/Windows/System32/nvidia-smi.exe"
query_field_names = "AUTO"
query_timeout = "5s"
写完重启服务,回到日志里搜 nvidia_smi——一条都没有。既没有 started,也没有 not supported,也没有错误。这个插件就像不存在一样。
诊断过程我走了很多弯路:检查 nvidia-smi 命令是否可用(可用)、检查 Categraf 二进制里是否包含这个插件(strings categraf.exe | grep nvidia_smi 显示完整的源码路径 flashcat.cloud/categraf/inputs/nvidia_smi,说明编译进来了)、把 print_configs = true 打开看是否有加载日志(没有)……
最后是对比 v0.5.5 zip 包里的原版模板找到的线索:
# conf/input.nvidia_smi/nvidia_smi.toml(原版模板)
# # collect interval
# interval = 15
# exec local command
# e.g. nvidia_smi_command = "nvidia-smi"
nvidia_smi_command = ""
# exec remote command
# nvidia_smi_command = "ssh ... nvidia-smi"
query_field_names = "AUTO"
query_timeout = "5s"
注意:原版模板完全没有 [[instances]] 这一行!所有配置字段都是顶级的。
这说明 nvidia_smi 是一个单例插件 (singleton plugin),它的设计假设是"一台机器只有一个 nvidia-smi 进程",所以不支持多实例配置。Categraf 的插件加载器在加载时,对单例插件使用的是直接反序列化到顶级结构的逻辑;而我写的 [[instances]] 让反序列化走了多实例路径,找不到期望的字段,悄悄跳过整个插件。
正确写法:
# C:\Program Files\categraf\conf\input.nvidia_smi\nvidia_smi.toml
interval = 15
nvidia_smi_command = "C:/Windows/System32/nvidia-smi.exe"
query_field_names = "AUTO"
query_timeout = "5s"
改完重启,日志立刻多出:
I! input: local.nvidia_smi started
[[instances]],少数是单例顶级字段。分辨方法是对着 zip 包里的原版模板改,不要从零写;原版模板会示范正确的结构。如果你从 Linux 上拿 v0.4.x 的配置模板迁移到 v0.5.5 Windows,这个坑特别容易出——老版本 nvidia_smi 是否是单例我没查证(可能一直都是,只是很多人没注意),但这一条规则需要铭记:看到插件启动列表里少了你以为应该在的那个,第一反应去对照原版模板。
5.2 v0.5.5 的指标命名大改#
修好配置后,照着网上常见的 Categraf nvidia_smi 教程写的 PromQL:
nvidia_smi_utilization_gpu{ident="rdstack-win-cad"}
查不到数据。但日志里 local.nvidia_smi started 明明在。
用夜莺的 /api/v1/label/__name__/values 端点列出所有指标名,搜 nvidia:
nvidia_smi_clocks_current_graphics_clock_hz ← 不是 nvidia_smi_clocks_current_graphics
nvidia_smi_clocks_current_memory_clock_hz
nvidia_smi_clocks_current_sm_clock_hz
nvidia_smi_clocks_current_video_clock_hz
nvidia_smi_fan_speed_ratio ← 不是 nvidia_smi_fan_speed
nvidia_smi_memory_free_bytes ← 不是 nvidia_smi_memory_free
nvidia_smi_memory_reserved_bytes
nvidia_smi_memory_total_bytes
nvidia_smi_memory_used_bytes
nvidia_smi_power_draw_watts ← 不是 nvidia_smi_power_draw
nvidia_smi_utilization_gpu_ratio ← 不是 nvidia_smi_utilization_gpu
nvidia_smi_utilization_memory_ratio
命名规则变了。v0.5.5 给所有带量纲的指标加了单位后缀:_bytes / _watts / _clock_hz / _ratio。老版本的平名都被废弃。
这是一次 breaking change,但 flashcat 的 changelog 里并没有显著提示(至少我没找到)。对于从 v0.4.x 升级或者从 Linux 挪过来的配置,影响是所有老看板的 PromQL 全部失效。
完整的对照表:
| v0.4.x(旧) | v0.5.5(新) | 值范围 |
|---|---|---|
nvidia_smi_utilization_gpu | nvidia_smi_utilization_gpu_ratio | 0–1(比例!不是 0–100!) |
nvidia_smi_utilization_memory | nvidia_smi_utilization_memory_ratio | 0–1 |
nvidia_smi_memory_used | nvidia_smi_memory_used_bytes | 字节 |
nvidia_smi_memory_total | nvidia_smi_memory_total_bytes | 字节 |
nvidia_smi_memory_free | nvidia_smi_memory_free_bytes | 字节 |
nvidia_smi_power_draw | nvidia_smi_power_draw_watts | 瓦特 |
nvidia_smi_fan_speed | nvidia_smi_fan_speed_ratio | 0–1 |
nvidia_smi_clocks_current_graphics | nvidia_smi_clocks_current_graphics_clock_hz | 赫兹 |
nvidia_smi_clocks_current_memory | nvidia_smi_clocks_current_memory_clock_hz | 赫兹 |
nvidia_smi_clocks_current_sm | nvidia_smi_clocks_current_sm_clock_hz | 赫兹 |
不变的指标:
nvidia_smi_temperature_gpu— 摄氏度,名字和语义都不变nvidia_smi_pcie_link_gen_current— 整数nvidia_smi_pcie_link_width_current— 整数
_ratio 的语义。_ratio 结尾的指标值范围是 0 到 1,不是 0 到 100。如果你直接把它配到 util: percent(0-100) 的夜莺面板上,图表永远贴着 0 显示(比如 GPU 用了 50% 实际值是 0.5)。PromQL 里必须手工 * 100。正确的写法:
# 错:显示 0~1 之间的小数
nvidia_smi_utilization_gpu_ratio{ident="rdstack-win-cad"}
# 对:显示 0~100 的百分比
nvidia_smi_utilization_gpu_ratio{ident="rdstack-win-cad"} * 100
5.3 v0.5.5 新增的节流原因指标#
虽然命名大改是个痛点,但 v0.5.5 同时也新增了一批非常有价值的指标——节流原因 (clocks event reasons):
nvidia_smi_clocks_event_reasons_active
nvidia_smi_clocks_event_reasons_gpu_idle
nvidia_smi_clocks_event_reasons_hw_slowdown
nvidia_smi_clocks_event_reasons_hw_thermal_slowdown
nvidia_smi_clocks_event_reasons_hw_power_brake_slowdown
nvidia_smi_clocks_event_reasons_sw_power_cap
nvidia_smi_clocks_event_reasons_sw_thermal_slowdown
nvidia_smi_clocks_event_reasons_sync_boost
nvidia_smi_clocks_event_reasons_applications_clocks_setting
nvidia_smi_clocks_event_reasons_supported
这些指标的值是 0 或 1,1 表示"当前 GPU 正在因为这个原因被降频":
| 指标 | 含义 | 诊断价值 |
|---|---|---|
hw_slowdown | 硬件强制降频(通常是下面三个之一触发) | 综合开关 |
hw_thermal_slowdown | 达到硬件温度保护阈值 | 散热失效 / 进风受阻 |
hw_power_brake_slowdown | 外部 POWER_BRAKE 信号拉低 | PSU 功率不足 / EPS 线松 |
sw_thermal_slowdown | 驱动层的温度保护(比硬件早介入) | 温度正在升高的早期信号 |
sw_power_cap | 驱动层的功率封顶(通常是 nvidia-smi -pl 设置) | 应用被限功耗 |
sync_boost | 同组 GPU 的同步 boost 机制(多卡专用) | 单卡系统通常为 0 |
hw_power_brake_slowdown > 0 且持续出现,说明 PSU 12V 供电不稳(老化或瞬时过载保护),不是软件问题——这种诊断结论光靠 GPU 利用率和温度指标是推不出来的。这些指标用普通曲线图展示会很难看(0/1 离散值 + 平滑曲线 = 锯齿),正确的展示方式是 stepAfter 插值 + 40% 填充不透明度,这样节流发生的区间会成为一段明显的"色块",视觉上一眼可见。
6. netstat 插件:Windows 上的静默失败#
修好 GPU 后,下一个发现的坑在 TCP 连接监控上。夜莺里查:
netstat_tcp_established{ident="rdstack-win-cad"}
空的。但 Categraf 日志里 local.netstat started 明明在。
6.1 诊断:/proc/net/tcp 根本不存在#
先排查配置:netstat.toml 格式正确,用 [[instances]] 包着,和其他插件一致。
然后检查 Categraf 内部。input.netstat 的 Linux 实现读的是 /proc/net/tcp、/proc/net/tcp6、/proc/net/snmp、/proc/net/netstat 这些内核虚拟文件。在 Windows 上:
PS> Test-Path '/proc/net/tcp'
False
路径不存在。但插件的构造函数没做平台检查,它每次采集周期打开文件、读失败、吞掉错误、返回空——对外表现就是"静默成功"。
这是一个比静默失败更糟糕的「伪成功」:你的监控体系告诉你"netstat 插件运行正常",但实际上所有 TCP 状态数据都是空的。如果你基于 netstat_tcp_close_wait > 100 做告警,这个告警永远不会触发——不是因为没有泄漏,而是因为数据根本没来。
这个陷阱比错误的报错更危险,因为它破坏了监控系统可信度的基本前提:“如果看不到异常,就说明没有异常”——这条前提只有在采集路径可靠时才成立。
6.2 设计模式:exec 插件作为 Windows 平台补丁#
Categraf 提供了一个非常强大的插件叫 exec,它的设计目标是"跑一段任意命令,把 stdout 作为指标解析"。支持的 data format 包括 influx / prometheus / falcon 几种主流文本协议。
这个插件本质上是一个万能逃生口:任何 Categraf 原生不支持的指标,只要你能用脚本拿到,就能用它输出。在 Windows 上它尤其重要,因为大量 Categraf 插件是 Linux-only 的。
用 PowerShell 的原生 cmdlet 替代 netstat 非常直观:
PS> Get-NetTCPConnection | Group-Object State | Select Name, Count
Name Count
---- -----
Bound 17
Listen 47
Established 15
TimeWait 4
SynSent 10
Get-NetTCPConnection 在 Windows Server 2012 R2 及以上是原生的,不需要额外安装任何东西。配合 Group-Object State 直接就能得到按状态分组的连接数。
6.3 win_metrics.ps1 完整剖析#
我写了一个完整的采集脚本,不仅替代 netstat,还顺便补齐了其他几个 Windows 专用指标。关键段落:
# === TCP 连接状态 ===
try {
$tcpStates = Get-NetTCPConnection -ErrorAction SilentlyContinue | Group-Object State
$states = @{
'Listen' = 0
'Established' = 0
'TimeWait' = 0
'CloseWait' = 0
'SynSent' = 0
'SynReceived' = 0
'FinWait1' = 0
'FinWait2' = 0
'Closing' = 0
'LastAck' = 0
'Bound' = 0
'Closed' = 0
}
foreach ($g in $tcpStates) {
if ($states.ContainsKey($g.Name)) {
$states[$g.Name] = $g.Count
}
}
$fields = @(
"listen=$($states['Listen'])"
"established=$($states['Established'])"
"time_wait=$($states['TimeWait'])"
"close_wait=$($states['CloseWait'])"
"syn_sent=$($states['SynSent'])"
"fin_wait1=$($states['FinWait1'])"
"fin_wait2=$($states['FinWait2'])"
) -join ','
Write-Output "windows_netstat_tcp $fields"
# 空值兜底:当所有连接为空时 .Sum 可能是 $null,直接输出会产生 "count=" 空值,
# 导致 influx 行协议解析失败。强制转成 int 并兜底为 0。
$total = ($tcpStates | Measure-Object Count -Sum).Sum
if ($null -eq $total) { $total = 0 }
$total = [int]$total
Write-Output "windows_netstat_tcp_total count=$total"
} catch {}
这段代码有几个设计细节值得解释:
1)预初始化所有状态为 0:
为什么要手工初始化 $states 字典并预填 0?因为 Get-NetTCPConnection | Group-Object State 的输出只包含存在的状态。如果当前系统没有任何 TimeWait 连接,Group-Object 输出里就没有 TimeWait 这一组,直接输出会漏字段,导致指标在夜莺里时有时无,曲线图充满断点。预初始化保证每次都输出完整的 12 个字段,曲线图连续。
2)用 measurement + fields 而不是 measurement + tags:
influx line protocol 里有两种组织方式:
# 方式 A: 多个独立 measurement(每个 measurement 一个 field)
tcp_state,state=established value=15
tcp_state,state=time_wait value=4
# 方式 B: 一个 measurement 多个 field
windows_netstat_tcp established=15,time_wait=4
我选方式 B。理由:Categraf 的 influx parser 会把 measurement + field 拼成最终 metric 名,方式 B 产出的是 windows_netstat_tcp_established、windows_netstat_tcp_time_wait 等顶层指标,写 PromQL 时不需要 label 过滤。方式 A 产出的是单一指标 tcp_state_value + state label,查询时必须 {state="established"},写起来啰嗦。
对于基数固定且数量少(TCP 状态只有 12 种)的枚举,方式 B 更易用。
3)try/catch 包住每一段:
如果某一段采集失败(比如权限不够、cmdlet 不存在、某个 WMI 查询超时),不要让整个脚本崩溃。每一段都用 try { ... } catch {} 包住,保证"出问题就漏一个指标,不要整个脚本死掉"。对于监控采集器来说,漏一个指标远比整个脚本退出好——前者是 degradation,后者是 outage。
6.4 完整的 win_metrics.ps1 产出#
除了 TCP/UDP 状态,这个脚本还收集:
| 指标 | 来源 | 用途 |
|---|---|---|
windows_rdp,type=active count=N | qwinsta 解析 | 当前活跃 RDP 会话数 |
windows_rdp,type=disconnected count=N | 同上 | 掉线但未注销的会话 |
windows_process,state=hung count=N | Get-Process | Where !Responding | 卡死进程数(有窗口句柄) |
windows_pagefile allocated_mb,used_mb,peak_mb | Win32_PageFileUsage | 页面文件使用情况 |
windows_eventlog,log=system errors_5m=N | Get-WinEvent 过滤 5 分钟内 Level 1/2 | 系统日志错误数 |
windows_eventlog,log=application errors_5m=N | 同上,应用日志 | 应用日志错误数 |
windows_pending_reboot value=0/1 | 检查两个注册表路径 | Windows Update 待重启 |
windows_uptime seconds=N | Win32_OperatingSystem.LastBootUpTime | 系统已运行秒数 |
为什么用 5 分钟窗口而不是全量事件日志计数?
因为事件日志是累计的,全量计数每次重启会归零,很难做 rate() 计算。而 5 分钟滑动窗口每次采集都是一个"当前时刻回看 5 分钟"的快照,可以直接在 dashboard 上做阈值告警:windows_eventlog_errors_5m > 10 就是"最近 5 分钟出现超过 10 条错误日志"。
6.5 exec 插件的配置#
# C:\Program Files\categraf\conf\input.exec\exec.toml
[[instances]]
commands = [
"powershell -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -File \"C:/Program Files/categraf/scripts/win_metrics.ps1\""
]
timeout = 10
interval_times = 4
data_format = "influx"
几个必须注意的点:
data_format = "influx"必须显式指定。默认值不是 influx,配错了脚本输出被当成乱码丢弃interval_times = 4表示采集频率 =global.interval × interval_times= 15 秒 × 4 = 60 秒。PowerShell 启动慢(冷启动约 800ms),加上事件日志查询耗时,每 15 秒跑一次太频繁。60 秒是够用的折中- 路径用正斜杠,即使是 Windows 路径。Categraf 在解析 commands 字符串时会把反斜杠当成转义字符。
"C:/Program Files/categraf/scripts/win_metrics.ps1"是正确的写法 - 路径包含空格时必须用
\"..\"转义。"C:/Program Files/..."里的空格不转义会被拆成两个参数 -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned三个 PowerShell 参数必须带:-NoProfile避免加载用户 profile(加快启动、避免继承交互式会话配置)-NonInteractive禁止任何交互式提示,防止脚本里意外的Read-Host挂死整个 exec 周期-ExecutionPolicy RemoteSigned允许本地脚本无签名运行,但要求远程下载的脚本必须有数字签名——这是比Bypass更收敛的策略。Bypass会完全跳过执行策略检查,如果win_metrics.ps1所在目录的 ACL 被错配导致普通用户可写,恶意替换的脚本会直接以服务身份执行。RemoteSigned配合默认的C:\Program Files\categraf\scripts\的 admin-only ACL 是一个显著更安全的组合
7. Dashboard 设计:41 个面板的取舍#
Dashboard JSON 是夜莺可导入的格式,基于官方的 “Windows Host by Categraf” 模板扩展,增建了一个 GPU 专项 section。整体 41 个面板,分布:
| Section | 面板数 | 内容 |
|---|---|---|
| 单机概况 | 5 | uptime / CPU 使用率 / 内存使用率 / 磁盘使用率 / io_util |
| CPU | 3 | 空闲率 / 使用率详情 / 负载 |
| 内存详情 | 2 | 使用率 / 可用率 |
| 磁盘详情 | 4 | 空间 / IO 吞吐 / IOPS / iowait |
| 网络详情 | 5 | 流量 / packets / error / drop / TCP 状态 |
| GPU (NVIDIA) | 16 | 详见下 |
GPU section 的 16 个面板组织结构:
flowchart TB
subgraph Row1["顶部 Stat 卡片 (6 × w4)"]
S1["GPU 使用率"]
S2["GPU 温度"]
S3["显存使用率"]
S4["GPU 功耗"]
S5["PCIe 代数"]
S6["PCIe 宽度"]
end
subgraph Row2["中部性能时序 (3 × w8)"]
T1["GPU 使用率详情"]
T2["显存使用量"]
T3["GPU 频率"]
end
subgraph Row3["下部温控 (3 × w8)"]
T4["GPU 温度趋势"]
T5["GPU 功耗详情"]
T6["GPU 风扇转速"]
end
subgraph Row4["关键监控 (2 × w12)"]
T7["PCIe 链路状态"]
T8["GPU P-State"]
end
subgraph Row5["底部 (w16 + w8)"]
T9["GPU 节流原因"]
T10["编码器会话"]
end
Row1 --> Row2 --> Row3 --> Row4 --> Row5
style Row1 fill:#3b82f6,color:#fff
style Row2 fill:#6366f1,color:#fff
style Row3 fill:#8b5cf6,color:#fff
style Row4 fill:#a855f7,color:#fff
style Row5 fill:#ec4899,color:#fff
几个设计决策值得单独讲。
7.1 单位系统:让 PromQL 保持简洁#
夜莺面板的 options.standardOptions.util 字段支持一批预定义单位,它们会在渲染时做自动换算:
| util 值 | 原始单位 | 渲染效果 |
|---|---|---|
bytesIEC | 字节 | 自动换算为 B/KiB/MiB/GiB/TiB |
bytesSI | 字节 | 自动换算为 B/KB/MB/GB/TB(1000 进制) |
bitsIEC | 位 | 自动换算为 bps/Kibps/Mibps |
hertz | 赫兹 | 自动换算为 Hz/KHz/MHz/GHz |
watt | 瓦特 | 自动换算为 mW/W/kW |
celsius | 摄氏度 | 附加 °C 后缀 |
percent(0-100) | 百分比 | 附加 % 后缀,限制 Y 轴 0-100 |
这些单位的价值是让 PromQL 保持最原始的形式,不需要做类似 / 1024 / 1024 或 / 1e6 的脏计算。比如显存使用量面板:
nvidia_smi_memory_used_bytes{ident="rdstack-win-cad"}
加上 util: "bytesIEC" 就够了,夜莺会根据数量级自动显示 3.5 GiB / 128 MiB 这样的可读值。如果手工 / 1024 / 1024 变成 MiB,图表 Y 轴就写死了,数据变大变小时刻度不会自适应。
GPU 频率同理:
nvidia_smi_clocks_current_graphics_clock_hz{ident="rdstack-win-cad"}
配上 util: "hertz",空闲时显示 210 MHz,满载时显示 1.98 GHz。
例外:_ratio 指标是 0-1,必须手工 * 100 再配 percent(0-100):
nvidia_smi_utilization_gpu_ratio{ident="rdstack-win-cad"} * 100
这是唯一需要在 PromQL 里做运算的类型。其他的都让单位系统处理。
7.2 PCIe 链路状态:valueMappings 让数字变状态#
PCIe 链路代数面板用的是 stat(单值显示)类型,但直接显示 4 或 5 不够直观。夜莺的 valueMappings 机制允许做数值→文字的转换:
"valueMappings": [
{"match": {"from": 0, "to": 3}, "result": {"color": "#f51919", "text": "DEGRADED!"}, "type": "range"},
{"match": {"from": 4, "to": 4}, "result": {"color": "#129b22", "text": "Gen4"}, "type": "range"},
{"match": {"from": 5, "to": 5}, "result": {"color": "#129b22", "text": "Gen5"}, "type": "range"}
]
这样在 dashboard 上看到的不是一个枯燥的 4,而是一个绿底的 Gen4 或红底的 DEGRADED!。后者是当 PCIe 链路掉到 Gen1/Gen2/Gen3 时的警告——在 AMD Zen5 + Gen5 链路训练不稳定的场景下,这是"必须第一眼看到"的关键信号。
这个面板还配了一个完全对应的时序图 PCIe链路状态 (关键监控),用 stepAfter 插值展示历史。两者互补:stat 卡给当前状态瞬时判断,时序图给历史降级事件的时间点和持续时间。
7.3 节流原因:离散值用阶梯图#
clocks_event_reasons_* 系列指标是典型的离散状态(0 或 1)。如果用平滑曲线画出来,两个状态之间的过渡会变成斜坡,视觉上误导——好像 GPU 在"逐渐"进入节流。
"lineInterpolation": "stepAfter",
"fillOpacity": 0.4
stepAfter 表示数据点之间保持当前值不变,直到下一个数据点才跳变。这样 0→1 的切换是一根垂直线,然后保持 1 的水平线段,完全符合"何时开始节流、持续了多久、何时结束"的语义。
fillOpacity: 0.4 让 1 的区段被填充成半透明色块,一眼就能在几十分钟的时间轴上锁定节流事件。
8. 可观测性设计的几条一般性教训#
这次从 0 到 41 个面板的过程里,有几条教训是超出 Categraf 本身的:
教训一:“Started” ≠ “Working”#
这次踩的所有静默失败都是同一个模式:
- Categraf 日志说
netstat started,但它在 Windows 上根本读不到任何数据 - Categraf 日志说
nvidia_smi started,但[[instances]]格式不对时它会完全跳过(连 started 都没有) input.greenplum/input.hadoop对着空配置started,每 15 秒做一次无用功
这些都验证了一个原则:
Categraf 日志里的 “started” 只是"构造函数没报错"的意思,离"数据正在采集并成功推送"还有三层:Categraf 日志里的 “started” 只是"构造函数没报错"的意思,离"数据正在采集并成功推送"还有三层:
- 插件 init 成功(对应
started日志) - 每个采集周期能拿到非空结果
- 数据能通过 writer 推送到后端
这三层里任何一层失败,前面的日志都看不到异常。唯一可靠的验证是登录夜莺 UI、写 PromQL、看是否有数据点。
这对所有监控体系都成立:Telegraf、Prometheus exporter、OpenTelemetry Collector——“组件已启动"和"数据链路已贯通"是两个正交的信号,必须分别验证。
教训二:平台差异必须被插件层感知#
netstat 插件的 bug 本质是平台耦合泄漏:它在设计时假设了 Linux /proc 文件系统,但在编译时没有用构建标签 (build tag) 把它限制为 Linux-only。结果是 Windows 二进制里包含了这个插件,它看起来可以启动,实际上一个字节的数据都读不到。
正确的设计应该是:
//go:build linux
// +build linux
package netstat
func (n *Netstat) Gather(...) { /* read /proc/net/tcp */ }
然后在 Windows 平台上这个插件根本不存在,Input not supported 会在 agent 启动时立刻报出来(就像 iptables / kernel 那些明确标了 linux-only 的插件)。
用户侧的反应就完全不同了:
- 伪启动:用户看到 started,默认一切正常,写 PromQL,拿到空结果,开始怀疑人生
- 明确不支持:用户看到
not supported,立刻知道"哦这个在 Windows 上不行”,找替代方案
后者明显更好。“让失败显式"是所有工程系统都应该追求的基本品质。
教训三:exec 插件是平台适配的万能逃生口#
Categraf 的 netstat 在 Windows 上失效不是世界末日,因为 exec 插件给了一条绕过的路:
原生插件采集不到 → 用脚本采集 → 用 exec 插件包装 → 走同一条 writer 链路
这条路的价值在于:你不需要等 Categraf 上游修复 Windows 支持,也不需要 fork 代码。只要你能用 PowerShell、bash、Python 或任何其他脚本拿到数据,就能把它喂进监控系统。
exec 插件的设计哲学值得学习——它承认"我们不可能为所有平台的所有指标写原生采集器”,所以留一条逃生口给用户。这种"逃生口模式"在可观测性系统里普遍存在:Prometheus 有 node_exporter 的 textfile_collector,OpenTelemetry 有 OTTL 的 transform processor,Grafana 有 infinity 数据源。它们都是同一个思路:当平台原生能力不够时,留一条用户可用的扩展路径。
这条经验反过来也成立:如果你在设计一个新的监控 agent 或者任何需要采集数据的系统,务必留一个 exec/subprocess 类型的扩展点。它不优雅,但它让你的用户在遇到未预期场景时不会被完全卡住。
教训四:监控系统本身也需要被监控#
Categraf 自己会输出一组 categraf_* 内部指标:
categraf_current_queue_size # 当前待发送队列长度
categraf_metrics_enqueue_sum # 累计入队指标数
categraf_metrics_enqueue_failed_sum # 累计入队失败数
categraf_prometheus_remote_storage_samples_in_total # remote write 发送样本数
categraf_process_resident_memory_bytes # Categraf 进程内存
categraf_process_cpu_seconds_total # Categraf 进程 CPU 累计时间
这些指标的价值是监控"监控系统自己的健康":
- 如果
current_queue_size持续增长 → writer 跟不上采集速度 → 后端不可达或限流 - 如果
enqueue_failed_sum增长 → 采集频率太高或后端拒绝 - 如果
process_resident_memory_bytes持续上涨 → 可能有内存泄漏
这类"元监控" (meta-monitoring) 是生产可观测性系统的基础设施。如果你的监控体系没有监控自己,当它静默罢工时没有任何信号——而前面讨论过的所有静默失败模式都会悄悄发生。
这次部署我没有为 Categraf 自身指标做面板(priorities),但在生产长期运行场景下这是必须补上的。推荐的最小面板集:
categraf_current_queue_size趋势图(阈值告警 > 10000)rate(categraf_metrics_enqueue_failed_sum[5m])失败率categraf_process_resident_memory_bytes内存(阈值告警 > 500 MiB)categraf_prometheus_remote_storage_samples_in_total的rate()(检测是否停止推送)
9. 部署检查清单#
把这次踩坑的教训沉淀成一个可复用的部署 checklist,下次在 Windows 上装 Categraf 按这个清单走一遍可以避开绝大部分坑。
安装阶段
- 下载 Categraf Windows 版本(v0.5.5 或更新),解压到最终路径(不要后移动)
- 用
--win-service-install注册服务,不要直接命令行运行 -
Get-Service -Name categraf验证状态为Running
配置清理阶段
- 删除所有不需要的
conf/input.*/目录(保留 8 个核心:cpu/mem/disk/diskio/system/net/nvidia_smi/exec;注意不要保留input.netstat,它在 Windows 上是伪启动,用input.exec+ PowerShell 替代) - 检查
conf/config.toml里[writers]的 URL、hostname、global labels - 明确设置
hostname = "<有意义的名字>",否则默认WIN-XXXXXXXX这种丑名字 - 添加
[global.labels]标识 env/role/cluster 等
插件配置验证
-
nvidia_smi.toml必须用顶级字段,不要用[[instances]] -
nvidia_smi_command用正斜杠路径C:/Windows/System32/nvidia-smi.exe -
processes插件在 Windows 上不支持,配置文件删除 -
netstat插件在 Windows 上是伪启动,直接删除整个input.netstat/目录;TCP 状态改由win_metrics.ps1+input.exec采集
服务启动验证
- 重启服务后
Get-Content C:/Program Files/categraf/logs/categraf.log -Tail 50 - 确认
agent started日志 - 确认每个期望的插件都有
started行 - 确认没有
E!级别的 init 错误 - 端到端验证:登录夜莺,写 PromQL 查询每个关键指标(
nvidia_smi_utilization_gpu_ratio、cpu_usage_idle、mem_used_percent),确认有数据返回
Dashboard 配置
- 用正确的 v0.5.5 指标名(带
_ratio/_bytes/_watts/_clock_hz后缀) -
_ratio指标必须* 100 -
_bytes/_hz/_watts配合对应单位(bytesIEC / hertz / watt) - PCIe 链路代数用 valueMappings 做 Gen4/Gen5/DEGRADED 视觉区分
- 节流原因用
stepAfter阶梯图展示
这个清单是我在这次部署结束后写的,相当于把踩过的坑变成一个防御性流程。下次给同事部署、或者给另一台机器部署时,照着走一遍就不会重复掉进同样的洞里。
产物发布#
本次部署的两个可复用产物全部公开发布在 GitHub Gist,可以直接下载使用。
1)夜莺仪表盘 JSON#
文件名:dashboard-windows-gpu-v3.json(88.8 KB,41 面板)
地址:https://gist.github.com/cdryzun/6faaa7d03ed795a36bf5085a55cfafeb
使用方法:
- 复制 gist 里 JSON 的全部内容
- 在夜莺 Web UI 左侧导航 → 仪表盘 → 右上角 新建 → 导入
- 粘贴 JSON 内容 → 保存
前置依赖:
- 目标机器必须运行 Categraf v0.5.5+ 且
input.nvidia_smi插件正常工作 - 如果要用 TCP 状态面板,还需要配合
win_metrics.ps1+input.exec - 仪表盘变量
$ident使用label_values(disk_used_percent{device=~".+:"}, ident)自动筛选 Windows 主机
包含的 GPU 专项面板:
- GPU 使用率 stat + 使用率详情(core/memory/encoder/decoder 四通道)
- 显存使用率 stat + 使用量时序(used/total/free/reserved)
- GPU 温度 stat + 温度趋势(含
temperature_gpu_tlimit限流阈值对照) - GPU 功耗 stat + 功耗详情(draw/average/instant/limit/enforced_limit)
- PCIe 链路代数 stat(带 Gen4/Gen5/DEGRADED 视觉映射)
- PCIe 链路宽度 stat
- PCIe 链路状态时序(current vs max 对照,阶梯图)
- GPU 频率(graphics/memory/sm/video 四时钟,hertz 单位自动换算)
- GPU 风扇转速
- GPU P-State 阶梯图(0=最高性能,12=最低功耗)
- GPU 节流原因(7 个
clocks_event_reasons_*指标,stepAfter 阶梯图 + 填充色块) - GPU 编码器会话数
2)Windows 指标采集脚本#
文件名:win_metrics.ps1(80 行)
地址:https://gist.github.com/cdryzun/355129948d401c7d378633dc82cb4cbd
部署位置:C:\Program Files\categraf\scripts\win_metrics.ps1
产出指标:
| Metric | 标签 | 含义 |
|---|---|---|
windows_rdp_count | type=active/disconnected | RDP 会话数 |
windows_process_count | state=hung | 卡死进程数 |
windows_pagefile_allocated_mb | - | 页面文件分配大小 |
windows_pagefile_used_mb | - | 页面文件当前使用 |
windows_pagefile_peak_mb | - | 页面文件历史峰值 |
windows_eventlog_errors_5m | log=system/application | 5 分钟内错误/严重日志数 |
windows_pending_reboot_value | - | Windows Update 待重启状态(0/1) |
windows_uptime_seconds | - | 系统运行时长(秒) |
windows_netstat_tcp_listen | - | TCP LISTEN 连接数 |
windows_netstat_tcp_established | - | TCP ESTABLISHED 连接数 |
windows_netstat_tcp_time_wait | - | TCP TIME_WAIT 连接数 |
windows_netstat_tcp_close_wait | - | TCP CLOSE_WAIT 连接数 |
windows_netstat_tcp_syn_sent | - | TCP SYN_SENT 连接数 |
windows_netstat_tcp_fin_wait1 | - | TCP FIN_WAIT1 连接数 |
windows_netstat_tcp_fin_wait2 | - | TCP FIN_WAIT2 连接数 |
windows_netstat_tcp_total_count | - | TCP 总连接数 |
windows_netstat_udp_count | - | UDP 端点总数 |
配套的 exec.toml 配置:
# C:\Program Files\categraf\conf\input.exec\exec.toml
[[instances]]
commands = [
"powershell -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -File \"C:/Program Files/categraf/scripts/win_metrics.ps1\""
]
timeout = 10
interval_times = 4
data_format = "influx"
RemoteSigned 而不是常见教程里的 Bypass。原因见§6.5的详细论证——简单说就是 Bypass 会完全跳过执行策略检查,如果脚本目录的 ACL 被错配,恶意替换的脚本会直接以服务身份运行;RemoteSigned 配合 C:\Program Files\categraf\scripts\ 的 admin-only 默认 ACL 是更安全的组合。-NonInteractive 则防止脚本里出现 Read-Host 意外挂住整个 exec 周期。参考#
- Categraf GitHub repository — 二进制发布、插件源码、配置模板
- Nightingale (夜莺) 监控系统官网 — 后端安装、仪表盘导入、告警规则配置
- Categraf 官方文档 — 插件列表、配置参考
- nvidia-smi –help-query-gpu — nvidia-smi 可查询的所有字段说明
- PowerShell Get-NetTCPConnection reference — Windows 原生 TCP 状态查询 cmdlet
- Prometheus Remote Write specification — Categraf 和夜莺之间的数据协议
- InfluxDB line protocol —
exec插件的 stdout 格式规范
