跳过正文
  1. 博客文章/

Windows CAD 工作站监控体系实战:Categraf v0.5.5 + 夜莺 + GPU/TCP 深度采集

·2817 字·14 分钟·
可观测性 虚拟化 Categraf 夜莺 Nightingale 监控 可观测性 Windows Server Nvidia_smi GPU监控 PowerShell PromQL Grafana Observability
Zayn
作者
Zayn
专注 Kubernetes、CI/CD、可观测性等云原生技术栈,记录生产环境中的实战经验与踩坑复盘。
目录
可观测性实践 - 这篇文章属于一个选集。
1: 本文
一个看似简单的问题:把 Categraf 装到 Windows 上,对接到夜莺,看到 GPU 使用率曲线。实际走完会踩到四个互不相关的坑:Windows 服务启动语义、默认模板全量加载、nvidia_smi 插件的单例配置、netstat 插件的 Linux-only 实现。没有一个会在日志里报错——它们全部是静默失败。这篇是这条从 zero 到生产可用路径的完整复盘。

太长不看版
#

这篇讲的是 Categraf v0.5.5 在 Windows Server 2022 上对接夜莺 (Nightingale) 的工程深度,不是官方 quickstart 的中文翻版。核心贡献是四个隐蔽陷阱的解决方案和一份生产可用的看板。

部署踩的四个坑(按"日志里看不到"的严重程度排序):

  1. nvidia_smi 是单例插件,不吃 [[instances]] 包装。写错了不报错,整个插件静默 skip,GPU 指标全没。
  2. v0.5.5 的 nvidia_smi_* 指标名全改了,加了 _ratio / _bytes / _watts / _clock_hz 后缀,老教程的 PromQL 全部查不到数据。
  3. netstat 插件在 Windows 上假装启动,它依赖 /proc/net/tcp,Windows 上根本没这个路径,插件无声 no-op。
  4. 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 个活跃会话

这是一台生产工作站,不是实验环境。监控的目标非常具体:

  1. GPU 使用率/温度/功耗 — CAD 软件对 GPU 的依赖从 2D 到 3D 不等,需要知道谁在吃显卡
  2. 显存压力 — 16 GB 显存看上去很大,Creo 打开复杂装配体瞬间就能吃掉 12 GB
  3. PCIe 链路完整性 — 直通 GPU 的 PCIe 链路质量直接决定 VM 稳定性,这台机器之前出现过 PCIe 降级事件
  4. RDP 会话健康 — 会话数、卡死进程、事件日志错误
  5. 系统运行时间 — 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 -futail -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

这份日志里有两类奇怪的东西:

  1. not supportedamd_rocm_smi / conntrack / iptables / kernel / linux_sysctl_fs 这些是 Linux-only 插件,在 Windows 上直接跳过,这是预期的
  2. startedgreenplum / 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.jsonsystemd 服务状态。

这是一个让新手百思不得其解的设计:它的意图应该是"模板文件作为文档,注释状态表示未启用",但大部分模板的 [[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
教训Categraf 插件的配置格式不统一。大部分是多实例 [[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_gpunvidia_smi_utilization_gpu_ratio0–1(比例!不是 0–100!)
nvidia_smi_utilization_memorynvidia_smi_utilization_memory_ratio0–1
nvidia_smi_memory_usednvidia_smi_memory_used_bytes字节
nvidia_smi_memory_totalnvidia_smi_memory_total_bytes字节
nvidia_smi_memory_freenvidia_smi_memory_free_bytes字节
nvidia_smi_power_drawnvidia_smi_power_draw_watts瓦特
nvidia_smi_fan_speednvidia_smi_fan_speed_ratio0–1
nvidia_smi_clocks_current_graphicsnvidia_smi_clocks_current_graphics_clock_hz赫兹
nvidia_smi_clocks_current_memorynvidia_smi_clocks_current_memory_clock_hz赫兹
nvidia_smi_clocks_current_smnvidia_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
对 CAD 工作站的诊断意义:如果用户抱怨"GPU 性能变差了"但看使用率和温度都正常,这一类节流原因能直接指向根因。比如 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_establishedwindows_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=Nqwinsta 解析当前活跃 RDP 会话数
windows_rdp,type=disconnected count=N同上掉线但未注销的会话
windows_process,state=hung count=NGet-Process | Where !Responding卡死进程数(有窗口句柄)
windows_pagefile allocated_mb,used_mb,peak_mbWin32_PageFileUsage页面文件使用情况
windows_eventlog,log=system errors_5m=NGet-WinEvent 过滤 5 分钟内 Level 1/2系统日志错误数
windows_eventlog,log=application errors_5m=N同上,应用日志应用日志错误数
windows_pending_reboot value=0/1检查两个注册表路径Windows Update 待重启
windows_uptime seconds=NWin32_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面板数内容
单机概况5uptime / CPU 使用率 / 内存使用率 / 磁盘使用率 / io_util
CPU3空闲率 / 使用率详情 / 负载
内存详情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(单值显示)类型,但直接显示 45 不够直观。夜莺的 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” 只是"构造函数没报错"的意思,离"数据正在采集并成功推送"还有三层:

  1. 插件 init 成功(对应 started 日志)
  2. 每个采集周期能拿到非空结果
  3. 数据能通过 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_exportertextfile_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),但在生产长期运行场景下这是必须补上的。推荐的最小面板集:

  1. categraf_current_queue_size 趋势图(阈值告警 > 10000)
  2. rate(categraf_metrics_enqueue_failed_sum[5m]) 失败率
  3. categraf_process_resident_memory_bytes 内存(阈值告警 > 500 MiB)
  4. categraf_prometheus_remote_storage_samples_in_totalrate()(检测是否停止推送)

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_ratiocpu_usage_idlemem_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

使用方法

  1. 复制 gist 里 JSON 的全部内容
  2. 在夜莺 Web UI 左侧导航 → 仪表盘 → 右上角 新建导入
  3. 粘贴 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_counttype=active/disconnectedRDP 会话数
windows_process_countstate=hung卡死进程数
windows_pagefile_allocated_mb-页面文件分配大小
windows_pagefile_used_mb-页面文件当前使用
windows_pagefile_peak_mb-页面文件历史峰值
windows_eventlog_errors_5mlog=system/application5 分钟内错误/严重日志数
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"
关于 PowerShell 执行策略:上面用的是 RemoteSigned 而不是常见教程里的 Bypass。原因见§6.5的详细论证——简单说就是 Bypass 会完全跳过执行策略检查,如果脚本目录的 ACL 被错配,恶意替换的脚本会直接以服务身份运行;RemoteSigned 配合 C:\Program Files\categraf\scripts\ 的 admin-only 默认 ACL 是更安全的组合。-NonInteractive 则防止脚本里出现 Read-Host 意外挂住整个 exec 周期。

参考
#

可观测性实践 - 这篇文章属于一个选集。
1: 本文

相关文章

把一张 RX 6400 塞进虚拟机:PVE GPU 直通 + Windows Server RDS 部署全记录
·1337 字·7 分钟
虚拟化 PVE Proxmox GPU Passthrough Windows Server RDS AMD RX 6400 VFIO IOMMU
Windows RDS 下 Illustrator 2025 普通用户 0xc0000142 崩溃修复
·3255 字·16 分钟
Windows 调试 Windows Server RDS Adobe Illustrator 0xc0000142 PerSessionTempDir Std::filesystem Minidump WER Debugging MSVC C++ Exception
PVE AutoSnap 自动快照工具使用指南
·391 字·2 分钟
虚拟化 备份 PVE Proxmox AutoSnap 虚拟化