- 可观测性
- 指标监控
【免费下载链接】nvidia_gpu_exporter
Nvidia GPU exporter for prometheus using nvidia-smi binary OR using NVML
本文以 nvidia_gpu_exporter 的指标文档为骨架,系统讲清这个 Prometheus exporter 到底导出哪些指标、每个指标的含义与单位约定、哪些指标只有 NVML 后端能提供,以及如何在生产中对 XID 错误、GPU 恢复动作、MIG 实例做可落地的告警。读完你能掌握:如何读懂nvidia_smi_*指标族的命名与量纲、如何区分两个后端各自的指标面、如何用rate()/时间戳写出不会被首次事件漏报的告警表达式。
指标从何而来:AUTO 字段自动发现
exporter 导出的指标集合并不固定,它取决于你的 GPU 型号、驱动和操作系统。查询字段(query field)默认处于AUTO模式,exporter 会去探测nvidia-smi实际支持哪些字段,并把探测到的字段以"尽力而为"(best-effort)的方式暴露成指标。也就是说,新驱动新增的字段会先自动出现在默认(exec)后端里,无需改配置。
想看某个特定硬件上完整、真实的 scrape 输出长什么样,可以直接对照集成测试用来比对的期望输出,位于 internal/integration/testdata。那里每个捕获到的 GPU、驱动、负载状态各有一个文件,内容是 exporter 为该硬件产生的精确nvidia_smi_*输出,因此它们"随实现而天然保持最新"。
需要说明的是,除了这些 GPU 指标,exporter 还会导出标准的 Go runtime、进程级和promhttp指标族,这些不在上述测试数据里展示。
对于 NVML 后端和默认(exec)后端共同提供的每一个查询字段,两侧导出的指标在名称、标签、取值上完全一致。唯一的例外是采集状态指标:NVML 后端用nvidia_smi_nvml_return_code取代了nvidia_smi_command_exit_code(因为 NVML 后端不启动子进程,没有"进程退出码"可言,见后文)。在这个共享核心之上,NVML 后端额外提供下一节所述的 NVML 专属指标族。demo 后端则用合成数据近似 NVML 后端的指标面,参见 CONFIGURE.md。
指标命名与量纲约定(源码级视角)
理解指标名,关键在于 exporter 如何把nvidia-smi返回的字段名转成 Prometheus 指标名。从源码看,exporter.go 里有一张单位后缀映射表knownUnitSuffixes,它把nvidia-smi追加在字段名后的单位后缀映射为指标名后缀和数值乘数:
| 返回单位后缀 | 指标名后缀 | 乘数 | 效果 |
|---|---|---|---|
[W] | _watts | 1 | 保留瓦特 |
[MHz] | _clock_hz | 1,000,000 | MHz → Hz |
[MiB] | _bytes | 1,048,576 | MiB → bytes |
[%] | _ratio | 0.01 | 百分比 → 0~1 比例 |
[us] | _seconds | 0.000001 | 微秒 → 秒 |
[ms] | _seconds | 0.001 | 毫秒 → 秒 |
[seconds] | _seconds | 1 | 秒 |
[W/s] | _watts_per_second | 1 | 功率变化率 |
这张表直接解释了为什么memory.used [MiB]会变成nvidia_smi_memory_used_bytes,且值是以字节表示的科学计数法(例如7.06740224e+08),为什么utilization.gpu [%]变成nvidia_smi_utilization_gpu_ratio且取值是0.42而不是42。所有量化指标都先被换算到 SI/基础单位,这是读面板时最容易踩的坑:_clock_hz单位是 Hz,_ratio是 0~1 的比例,_bytes是字节。
默认前缀是nvidia_smi(常量DefaultPrefix),它在两个后端下都保持不变——这个名字标识的是数据 schema,而非采集机制,即便走 NVML 后端也不改前缀。
一次 scrape 的样子:身份指标与采集健康
下面的摘录来自一台单 GPU 机器。每条 per-GPU 序列都用该 GPU 的uuid做标签,因此能与携带身份标签的nvidia_smi_gpu_info关联起来:
# HELP nvidia_smi_gpu_info A metric with a constant '1' value labeled by gpu uuid, name, driver_model_current, driver_model_pending, vbios_version, driver_version, pci_bus_id, serial, compute_cap, pci_sub_device_id, index, cuda_version. # TYPE nvidia_smi_gpu_info gauge nvidia_smi_gpu_info{uuid="df6e7a7c-7314-46f8-abc4-b88b36dcf3aa",name="NVIDIA GeForce RTX 2080 SUPER",driver_version="471.11",cuda_version="11.4",pci_bus_id="00000000:71:00.0",index="0",...} 1 # HELP nvidia_smi_utilization_gpu_ratio utilization.gpu [%] # TYPE nvidia_smi_utilization_gpu_ratio gauge nvidia_smi_utilization_gpu_ratio{uuid="df6e7a7c-7314-46f8-abc4-b88b36dcf3aa"} 0.42 # HELP nvidia_smi_memory_used_bytes memory.used [MiB] # TYPE nvidia_smi_memory_used_bytes gauge nvidia_smi_memory_used_bytes{uuid="df6e7a7c-7314-46f8-abc4-b88b36dcf3aa"} 7.06740224e+08 # HELP nvidia_smi_power_draw_watts power.draw [W] # TYPE nvidia_smi_power_draw_watts gauge nvidia_smi_power_draw_watts{uuid="df6e7a7c-7314-46f8-abc4-b88b36dcf3aa"} 28.07 # HELP nvidia_smi_temperature_gpu temperature.gpu # TYPE nvidia_smi_temperature_gpu gauge nvidia_smi_temperature_gpu{uuid="df6e7a7c-7314-46f8-abc4-b88b36dcf3aa"} 34nvidia_smi_gpu_info是一个恒定值1、用一堆身份标签描述每张 GPU 的"信息指标"。从源码看(exporter.go),cuda_version标签并不是一个查询字段,而是由采集结果的 extras 追加在身份字段之后——因此它不进入查询字段 schema,却会出现在gpu_info的标签上(见下一节)。
除了 per-GPU 序列,exporter 还会报告它自身的采集健康状态。这些指标不带uuid标签,是"对 exporter 本身做告警"应当使用的一组:
# HELP nvidia_smi_last_collect_success Whether the most recent collection succeeded (1) or not (0) # TYPE nvidia_smi_last_collect_success gauge nvidia_smi_last_collect_success 1 # HELP nvidia_smi_last_collect_success_timestamp_seconds Unix timestamp of the most recent successful collection # TYPE nvidia_smi_last_collect_success_timestamp_seconds gauge nvidia_smi_last_collect_success_timestamp_seconds 1.751475457e+09 # HELP nvidia_smi_last_collect_duration_seconds Duration of the most recent collection # TYPE nvidia_smi_last_collect_duration_seconds gauge nvidia_smi_last_collect_duration_seconds 0.055694458 # HELP nvidia_smi_failed_scrapes_total Number of failed collections # TYPE nvidia_smi_failed_scrapes_total counter nvidia_smi_failed_scrapes_total 0 # HELP nvidia_smi_command_exit_code Exit code of the most recent nvidia-smi run # TYPE nvidia_smi_command_exit_code gauge nvidia_smi_command_exit_code 0一个容易误解的细节:nvidia_smi_failed_scrapes_total统计的是失败的采集(collection),而不是失败的抓取(scrape)。两者在引入后台采集(background collection)后开始分道扬镳,但指标名为了兼容保留了下来。从 exporter.go 的renderHealth看,失败计数和成功 gauge 总是会被输出(gauge 在首次尝试前读作 0),而退出码、耗时要在首次采集完成后才输出,成功时间戳要在首次成功后才输出——而不是报告无意义的 0。
完整集合要大得多,且随硬件不同而变化,跨各种捕获 GPU 的完整输出见 internal/integration/testdata。
CUDA 版本标签
两个后端都会把"已安装驱动所支持的 CUDA 版本"打到nvidia_smi_gpu_info的cuda_version标签上。注意:这是驱动自带的 CUDA API 版本,不是已安装的 CUDA toolkit 版本。
默认(exec)后端在启动时从nvidia-smi --version读取一次;NVML 后端直接问驱动库。读不到时标签值留空。源码中这一标签由采集的 extras 填充(见上节renderRow),所以它是"附加"而非"查询"来的。
枚举值指标:从状态字符串到数字
很多nvidia-smi字段报告的是"状态"而不是数字。exporter 会把它们映射成数字,这样才能被抓取。规则有两条:
- 一个字段不可用(读数形如
N/A、[Not Supported]或[Insufficient Permissions])不会被导出; - 一个字段有值但不是已知值(意料之外或全新的状态)也会被跳过,而不是去猜——同时会打日志,让这个缺口可见。
双态字段(1/0)
取值是Enabled/Disabled、Yes/No或Active/Not Active的字段都变成1/0。从源码看,transform.go 的TransformRawValue把enabled/yes/active归一到1,把disabled/no/not active归一到0。覆盖的字段例如:
persistence_mode、accounting.mode、display_mode、display_activeecc.mode.current/.pending、mig.mode.current/.pendinggsp.mode.current/.default、c2c.moderemapped_rows.failure/.pending- 所有
clocks_event_reasons.*节流标志(gpu_idle、hw_slowdown、sw_power_cap等)
哪些字段会出现取决于 GPU 和驱动,因为查询字段是自动发现的。
多态字段(状态即整数值)
多态字段的整数就是状态本身:
nvidia_smi_pstate:性能状态,0(P0,最高性能)到15(P15,最低)。值越小代表越忙,所以它不是负载 gauge。pstate用的是原生性能状态编号。nvidia_smi_gpu_recovery_action:0None(健康)、1GPU Reset、2Node Reboot、3Drain P2P、4Drain and Reset。值> 0意味着驱动在建议一个恢复动作,是"GPU 不健康"的好告警条件。nvidia_smi_fabric_state:0Not Supported、1Not Started、2In Progress、3Completed。只出现在加入 NVLink fabric 的 GPU 上。nvidia_smi_compute_mode:0Default、1Exclusive Thread、2Prohibited、3Exclusive Process。
从源码看,compute_mode走的是 transform.go 里的通用枚举分支(default/exclusive_thread/prohibited/exclusive_process);而gpu_recovery_action和fabric_state走的是专门的 NVML 枚举映射表fieldValueMappers(transform.go),它们映射到各自的原生 NVML 枚举整型。gpu_recovery_action同时接受带前缀和不带前缀的两种写法(因为nvidia-smi的确切措辞尚未完全固定),未识别的取值会被上报而非丢弃。
gpu_recovery_action和fabric_state只在硬件与驱动确实报告它们时才出现(恢复动作需要较新的驱动)。它们缺席于某个 exporter 的输出是预期行为,不是 bug。
NVML 专属指标
有些读数存在于驱动库里,但不在nvidia-smi的查询接口里,所以只有 NVML 后端能导出它们。
能耗与 PCIe 吞吐
nvidia_smi_energy_joules_total{uuid}(counter):GPU 自驱动上次加载以来消耗的总能量(焦耳)。NVML 后端下始终开启。在无法报告的 GPU(Volta 之前一代)上缺席。计数器会在驱动重载或 GPU reset 时清零,所以要用rate()或increase()读取。nvidia_smi_pcie_throughput_tx_bytes_per_second{uuid}与nvidia_smi_pcie_throughput_rx_bytes_per_second{uuid}(gauge):GPU 发送/接收的 PCIe 流量。由于采集成本,需通过--collect.pcie-throughput显式开启,见 CONFIGURE.md。驱动在各自的 20ms 计数器窗口内采样每个方向,所以这两个值是相邻的两个样本,不是同时的一对。
从源码看,这两个吞吐指标只在--collect.pcie-throughput打开时才构造描述符(exporter.go 的newPCIeDescs),描述符的 help 文本明确标注"由驱动在专用 20ms 窗口内采样"。能耗计数器同样由Features.Energy开关控制(exporter.go)。
MIG 实例
在开启 MIG 模式的 GPU 上,NVML 后端还会导出每个 MIG 实例的指标。无需任何配置——MIG 实例存在时它们就出现。
nvidia_smi_mig_info{uuid, mig_uuid, gpu_instance_id, compute_instance_id, profile}(恒定1):每个 MIG 设备的身份。uuid是父 GPU 的,所以 MIG 序列能与所有 per-GPU 序列关联;mig_uuid是 MIG 设备自己的。nvidia_smi_mig_memory_{total,used,free,reserved}_bytes{uuid, gpu_instance_id}(gauge):GPU 实例的显存。帧缓冲(framebuffer)属于 GPU 实例,由其下的 compute 实例共享,所以显存每个 GPU 实例只报告一次。若按 MIG 设备打标,在一个承载多个 compute 切片的实例上会重复计数。nvidia_smi_mig_{graphics_activity,sm_activity,sm_occupancy,tensor_activity}_ratio{uuid, gpu_instance_id}与nvidia_smi_mig_pcie_throughput_{tx,rx}_bytes_per_second{uuid, gpu_instance_id}(gauge):GPU 实例的活动度,计算自最近两次采集之间的窗口。
活动度指标有几个值得知道的细节:
- 它们从第二次看到该 GPU 实例的采集开始出现,且只在驱动支持 GPM 接口的 GPU 上出现(Hopper 一代及之后)。A100 一类的 MIG 只能拿到清单(inventory)和显存。
- 窗口两端都有保护。采集间隔不足一秒时,继续按旧窗口输出上次的值;间隔超过十分钟后采样重新开始,下一次采集对该实例什么都不输出,等同于"首次见到"。
- 利用率归属每个 GPU 实例,而非每个 MIG 设备。一个承载多个 compute 实例的 GPU 实例只报告一组值。用
(uuid, gpu_instance_id)对mig_info做 join 即可把它们映射回各个 MIG 设备。 - 存在多个独立抓取器时,窗口跟随最后采集的那个。想要稳定的窗口,请搭配
--collect.interval使用。 - 销毁一个 GPU 实例再用同一数字 id 创建不同形状的实例,会被检测到,活动度采样重新开始;而在同一位置重建完全相同的形状则无法区分(因为 MIG uuid 是确定性的),所以跨越这种替换的那一个窗口可能混合了两个实例的值,随后自我纠正。
从源码看,这些细节在 mig_nvml.go 里逐条对应:
- 窗口保护常量
gpmMinWindow = time.Second、gpmMaxWindow = 10 * time.Minute(mig_nvml.go),分别对应"不足一秒沿用旧值"和"超过十分钟重新开始"。 - "销毁后重建不同形状"的检测靠
fingerprint:它把该 GPU 实例下所有成员(mig_uuid、profile、compute instance id)拼成指纹,两次采集指纹不一致就丢弃保留样本、重新采样(mig_nvml.go 的gpmUtilization)。而"同形状同位置重建无法区分"正是因为 MIG uuid 是确定性的,指纹会相同。 - "A100 只有清单和显存"对应
collectMIGUtilization里support.IsSupportedDevice == 0的分支(mig_nvml.go),注释明确写着"pre-Hopper MIG(如 A100)…… 只有清单和显存"。GPM 是一个"要么全有要么全无"的组:它通过一个 export 分配采样句柄、通过另一个 export 释放,若驱动只提供其中一部分就会泄漏或崩溃,因此只要四个 GPM 入口点不完整就整组不启用(gpmAvailable)。 - "显存每个 GPU 实例只报告一次"由 exporter.go 的
renderMIGInstance用一个emittedGIs集合保证:同一 GPU 实例的显存和利用率在每次抓取只输出一次。
开启--collect.compute-apps-mig(需要--collect.compute-apps且使用 NVML 后端)后,per-process 指标额外携带gpu_instance_id和compute_instance_id标签,把每个进程归属到它的 MIG 实例上(非 MIG GPU 上的进程这两个标签为空)。它是 opt-in 的,因为它会改变 per-process 序列的标签集。从源码看,这对应 exporter.go 的computeAppMIGLabels;NVML 把"非 MIG 进程"上报的哨兵值0xFFFFFFFF渲染成空标签(mig_nvml.go 的migAppID)。
容器提示:要获得完整功能(包括 MIG 下的 per-process 指标),需要一个 privileged 容器、设置NVIDIA_MIG_MONITOR_DEVICES=all、并共享宿主 PID 命名空间。不需要 root,exporter 保持在非 root uid(在 H200 上已验证)。非特权情况下,仅设该环境变量,per-MIG 的清单、显存、活动度指标仍可用,只是 GPU 级显存字段会缺席。在 Kubernetes 上,privileged 必须连同其余安全上下文一起申请,参见 chart README。
XID 错误
NVML 后端还会监视 XID 错误——驱动编号化的错误事件(卡死的内核、ECC 失败、掉线的 GPU)。这些对nvidia-smi和查询字段指标都不可见,只能通过驱动事件或内核日志显现。
nvidia_smi_xid_errors_total{uuid, xid}(counter):自 exporter 启动以来观察到的 XID 错误事件数。序列在其首个事件到达时才出现,exporter 启动前的历史无法回放。计数器位于采集管线之外,因此即使采集失败也保持可见——而这恰恰是 XID 出现的时候。nvidia_smi_xid_last_timestamp_seconds{uuid, xid}(gauge):最近一个事件被 exporter 接收的时刻。驱动事件本身不带时间戳。
做告警时,优先用时间戳并过滤到一个显式代码白名单:
time() - nvidia_smi_xid_last_timestamp_seconds{xid=~"48|62|64|74|79|95|119|120"} < 300它会在最近五分钟内、命中这些 reset/reboot 级 XID 时触发,包括一个序列的首个事件。
对所有代码告警都是噪音:很多 XID 是应用级故障(13、31、43)或信息性的(63、92),无需运维动作。
像increase(nvidia_smi_xid_errors_total[5m]) > 0这种基于 rate 的表达式会漏掉一个序列的首个事件,因为 Prometheus 从未在它之前观察到过 0 值。这正是为什么上面的时间戳表达式更可靠。Helm chart 内置的规则正是按上述方式构建的。
无需任何配置。在驱动不支持事件注册的机器上,这些指标族只是保持为空。
从源码看这套机制:xid_nvml.go 的xidAccumulator是一个带锁的进程内累加器,bump负责记录每个事件,counts负责在抓取时快照出排序后的计数——它与采集管线完全解耦(xid_nvml.go 的XIDCounts注释明确说"计数器在采集失败时也保持可见")。事件监视器RunXIDWatcher(xid_nvml.go)在采集周期之旁运行,用 1s 超时轮询、指数退避(1s 起步、30s 封顶)重试注册;在机器不支持事件时它就静默空转,指标族保持为空,与文档描述一致。exporter.go 的Collect特意让 XID 计数在"无数据就返回"之前渲染,正是一张抛 XID 的 GPU 通常同时采集失败、而这些序列必须在那个时刻保持可见。
对应到 charts/nvidia-gpu-exporter/README.md 所描述的告警,prometheusrule.yaml 里NvidiaGpuXidCritical的默认代码白名单正是48 62 64 74 79 95 119 120(critical 层),另有94(一个被隔离的内存错误)归在 warning 层;chart 在模板期就校验同一代码不能同时出现在两层,避免两条相互矛盾的指令。NvidiaGpuRecoveryActionNeeded则直接用nvidia_smi_gpu_recovery_action > 0(critical),与上文"值 > 0 即不健康"的说明呼应。
每进程指标(opt-in)
开启--collect.compute-apps后,exporter 会为每个在 GPU 上持有 compute 上下文的进程额外导出一组序列。容器、Windows 与 MIG 相关的注意事项见 配置参考。
# HELP nvidia_smi_compute_app_info A metric with a constant '1' value labeled by the identity of a process with a compute context on a GPU. # TYPE nvidia_smi_compute_app_info gauge nvidia_smi_compute_app_info{pid="10291",process_name="/root/tools/memhog",uuid="00000000-0000-0000-0000-000000000000"} 1 nvidia_smi_compute_app_info{pid="10309",process_name="./gpu_burn",uuid="00000000-0000-0000-0000-000000000000"} 1 # HELP nvidia_smi_compute_app_used_memory_bytes GPU memory used by the process. Absent when the driver cannot report it (e.g. Windows WDDM). # TYPE nvidia_smi_compute_app_used_memory_bytes gauge nvidia_smi_compute_app_used_memory_bytes{pid="10291",process_name="/root/tools/memhog",uuid="00000000-0000-0000-0000-000000000000"} 2.690646016e+09 nvidia_smi_compute_app_used_memory_bytes{pid="10309",process_name="./gpu_burn",uuid="00000000-0000-0000-0000-000000000000"} 2.9605494784e+10 # HELP nvidia_smi_compute_apps Number of processes with a compute context on the GPU. # TYPE nvidia_smi_compute_apps gauge nvidia_smi_compute_apps{uuid="00000000-0000-0000-0000-000000000000"} 2 # HELP nvidia_smi_compute_apps_last_collect_success Whether the most recent per-process collection succeeded (1) or not (0) # TYPE nvidia_smi_compute_apps_last_collect_success gauge nvidia_smi_compute_apps_last_collect_success 1这些 per-process 指标的标签集在 exporter.go 的newComputeAppDescs中构造,默认是{uuid, pid, process_name},开启 MIG 归属时扩展为带两个实例 id 标签的版本。nvidia_smi_compute_apps对空闲 GPU 报告显式的0;当 per-process 查询本身失败时,所有 per-process 序列消失、nvidia_smi_compute_apps_last_collect_success读作0——从源码看(exporter.go 的renderApps),失败时只输出 success gauge(值为 0)并抑制一切 per-process 序列(包括零填充的 per-GPU 计数),从而让"没有进程"(count 0、success 1)与"无法观察进程列表"(无 count 序列、success 0)始终可区分:查询失败永远不会被误读成空闲 GPU。
两个后端指标面速查
下表汇总两种后端(以及 demo)各自能提供的指标面,便于按需求选型。它来自 CONFIGURE.md 与本文所引源码:
| 能力 | exec | nvml | demo |
|---|---|---|---|
| 查询字段指标(同名、同标签、同值) | 有 | 有 | 合成 |
带cuda_version标签的gpu_info | 有 | 有 | 有 |
per-process 指标(--collect.compute-apps) | 有 | 有 | 有 |
| 采集状态指标 | command_exit_code | nvml_return_code | nvml_return_code |
总能耗计数器(energy_joules_total) | 无 | 有 | 有 |
PCIe 吞吐(--collect.pcie-throughput) | 无 | 有 | 始终开 |
per-MIG 实例指标(mig_info、mig_memory_*、mig_*_ratio) | 无 | 有 | 有 |
per-process MIG 归属(--collect.compute-apps-mig) | 无 | 有 | 有 |
XID 错误计数器(xid_errors_total) | 无 | 有 | 有 |
注意采集状态指标在两侧的名字不同:exec 后端是nvidia_smi_command_exit_code(进程退出码),NVML 后端是nvidia_smi_nvml_return_code(NVML 返回码,0表示成功)。源码里这两者被明确区分开(exporter.go),以避免"悄悄改变一个既有指标的含义"。
进一步阅读
- 完整的命令行参考(含
--collect.backend、--collect.compute-apps、--collect.pcie-throughput、--query-field-names-exclude等)见 CONFIGURE.md。 - 每种捕获 GPU 的精确 scrape 输出见 internal/integration/testdata,例如 H200 MIG 负载样例 里能看到
mig_info、mig_memory_*与 per-process 序列的真实形态。 - Kubernetes 部署与 XID/恢复动作告警规则见 charts/nvidia-gpu-exporter/README.md 及其 PrometheusRule 模板。
- 可观测性
- 指标监控
【免费下载链接】nvidia_gpu_exporter
Nvidia GPU exporter for prometheus using nvidia-smi binary OR using NVML
相关推荐
toBeBetterJavaer监控告警:自定义指标开发实践
toBeBetterJavaer监控告警:自定义指标开发实践 你是否还在为系统异常发现不及时而烦恼?是否希望通过自定义业务指标精准掌握系统健康状态?本文将以to
文档教程知识库技术博客后端用 RSpec 为 Rails 应用编写行为测试:Rails Girls 测试指南(testing-rspec)实战解析
用 RSpec 为 Rails 应用编写行为测试:Rails Girls 测试指南(testing rspec)实战解析 本指南源自 Rails Girls G
教程后端RabbitMQ指标导出:Prometheus格式指标
RabbitMQ指标导出:Prometheus格式指标 RabbitMQ作为一款功能丰富的多协议消息代理,提供了完善的监控机制。其中,Prometheus格式指
后端消息队列消息路由
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考