DevOps

NVIDIA GPU Monitoring with DCGM Exporter, Prometheus, and Grafana

Your GPU box reports 100 percent utilisation and the job is still slow. nvidia-smi will not tell you why, because utilisation only counts whether a kernel was resident, not whether the card was allowed to run at speed. NVIDIA DCGM will tell you. It reads the same driver telemetry, adds health watches and diagnostics on top, and hands the whole lot to Prometheus through an exporter.

Original content from computingforgeeks.com - post 171116

This NVIDIA GPU monitoring guide sets up DCGM, dcgm-exporter, Prometheus, Grafana, and Alertmanager on a real GPU host, then builds alert rules for the four signals that matter on a GPU fleet: utilisation, memory, power, and thermal health. Every command, error, and number below came off a live NVIDIA A10G, with a second pass on an A100 to catch what changes inside a container.

Ran this end to end on Ubuntu 24.04 with an A10G in August 2026; it works start to finish.

Why NVIDIA GPU monitoring needs more than nvidia-smi

DCGM is a daemon plus a CLI plus a library. The daemon, nv-hostengine, holds the watches on GPU fields so several clients can read telemetry without each opening its own NVML session. That daemon model is what makes fleet monitoring practical.

Capabilitynvidia-smiDCGM
Utilisation, memory, power, temperatureYesYes
Per-engine activity (tensor, DRAM, graphics)NoYes, the profiling fields
Background health watches with pass/warn/failNoYes
Active hardware diagnostics that run a workloadNoYes, four levels
Decoded clock throttle reasons as separate signalsText onlyYes, as labelled metrics
Prometheus outputNoYes, via dcgm-exporter

DCGM runs on non-datacenter cards too, but not all of it. Field value watches and the active health checks work on GeForce and Quadro as well as Tesla. What is restricted is policy notification and the deeper diagnostic levels, which stay Tesla-only, and the profiling fields, which need a datacenter card from Volta onwards. So the health watches below work almost anywhere; the DCGM_FI_PROF_* metrics in this guide assume a Tesla, A-series, L-series, or H-series card.

Prerequisites

You need a host with an NVIDIA datacenter GPU, a working driver, and root. If the driver is not installed yet, work through installing the NVIDIA drivers and CUDA toolkit first, then come back. Confirm the card is visible:

nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv

The card, driver, and framebuffer size print on one line:

name, driver_version, memory.total [MiB]
NVIDIA A10G, 595.91.07, 23028 MiB

Note the driver version. The next step depends on it.

1. Install DCGM on Ubuntu and RHEL

DCGM 4 splits its binaries by CUDA major version, and the package you want is keyed to the CUDA version your driver reports, not to any CUDA toolkit you may have installed separately. Read it off the driver:

nvidia-smi -q | grep "CUDA Version"

A current driver reports CUDA 13, which means the cuda13 package:

CUDA Version                              : 13.2

Add the NVIDIA CUDA repository and install. On Ubuntu 24.04:

wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get install -y datacenter-gpu-manager-4-cuda13

The metapackage pulls three more, and it is not small. Budget the disk before you run it on a thin root volume:

The following NEW packages will be installed:
  datacenter-gpu-manager-4-core datacenter-gpu-manager-4-cuda13
  datacenter-gpu-manager-4-proprietary
  datacenter-gpu-manager-4-proprietary-cuda13
0 upgraded, 4 newly installed, 0 to remove and 36 not upgraded.
Need to get 587 MB of archives.
After this operation, 1965 MB of additional disk space will be used.

If the driver reports CUDA 12, swap the suffix and install datacenter-gpu-manager-4-cuda12 instead. Picking the wrong one installs binaries that will not load against your driver.

On RHEL, Rocky Linux, and AlmaLinux the repository changes but the package names do not:

sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel9/x86_64/cuda-rhel9.repo
sudo dnf install -y datacenter-gpu-manager-4-cuda13 datacenter-gpu-manager-exporter

Two things change on EL10. The repository path becomes rhel10, and dnf5 replaced the old subcommand, so the middle line is sudo dnf config-manager addrepo --from-repofile=<url> rather than --add-repo. You can drop the dnf-plugins-core line entirely there, because dnf5 has config-manager built in. Everything after the install step, including the unit names, is identical to the Ubuntu path below.

One message during setup is harmless and appears on any host that has had DCGM before: useradd: user 'nvidia-dcgm' already exists.

2. Start the host engine and confirm the GPU is discovered

The package ships a systemd unit that runs the daemon as root and drops privileges to a dedicated service account:

sudo systemctl enable --now nvidia-dcgm
systemctl cat nvidia-dcgm.service | grep -E "ExecStart|User"

That is the whole service definition worth knowing:

User=root
# ExecStart=%Y/../../../bin/nv-hostengine -n --service-account nvidia-dcgm
ExecStart=/usr/bin/nv-hostengine -n --service-account nvidia-dcgm

Ask DCGM what hardware it found:

dcgmi discovery -l

Each GPU gets an ID that every later command uses, alongside counts for NVSwitch, ConnectX, and CPU entities:

1 GPU found (Active).
+--------+----------------------------------------------------------------------+
| GPU ID | Device Information                                                   |
+--------+----------------------------------------------------------------------+
| 0      | Name: NVIDIA A10G                                                    |
|        | PCI Bus ID: 00000000:00:1E.0                                         |
|        | Device UUID: GPU-d3ed7d5d-47b5-609f-56ed-d76ebb18890e                |
+--------+----------------------------------------------------------------------+
0 NvSwitches found.
0 ConnectX found.
0 CPUs found.

The attribute dump is where you find the numbers your alert thresholds should be built from. Note the flag is -i, which is the short form of --info, so passing both is an error:

dcgmi discovery -i aptc --gpuid 0

Power limits and the two thermal limits are the useful part:

| Device Name              | NVIDIA A10G                                     |
| Serial Number            | 1320522015573                                   |
| InfoROM Version          | G133.0210.00.04                                 |
| VBIOS                    | 94.02.75.00.01                                  |
| Current Power Limit (W)  | 300                                             |
| Default Power Limit (W)  | 300                                             |
| Max Power Limit (W)      | 300                                             |
| Min Power Limit (W)      | 100                                             |
| Enforced Power Limit (W) | 300                                             |
| Shutdown Temperature (C) | 98                                              |
| Slowdown Temperature (C) | 95                                              |

Slowdown at 95 C and shutdown at 98 C are the hardware’s own limits. Any temperature alert you write should fire well below 95 C, because by the time the card is cutting clocks to protect itself you have already lost throughput.

3. Watch live GPU metrics with dcgmi dmon

dcgmi dmon streams fields by numeric ID. These are the ones worth memorising:

Field IDShort tagWhat it is
150TMPTRGPU core temperature, C
140MMTMPMemory temperature, C
155POWERPower draw, W
203GPUTLGPU utilisation, percent
252FBUSDFramebuffer used, MiB
100 / 113SMCLK / SMMAXCurrent and maximum SM clock, MHz
112DVCCTRCurrent clock event reasons, bitmask
230XIDERXID error count
1001 / 1004 / 1005GRACT / TENSO / DRAMAGraphics engine, tensor pipe, DRAM activity

Watch the four fleet signals at once:

dcgmi dmon -e 203,252,155,150 -c 5

Under a sustained fp16 matmul the A10G pinned itself against its power cap:

#Entity         GPUTL             FBUSD             POWER             TMPTR
ID                                                   W                 C
GPU 0           100               21288             296.935           73
GPU 0           100               21288             301.198           73
GPU 0           100               21288             297.752           73
GPU 0           100               21288             302.069           73
GPU 0           100               21288             298.164           73

Compare that against the same card idle, and you have the ranges your thresholds live between:

SignalIdleUnder load
Utilisation0 percent100 percent
Framebuffer used0 MiB21288 MiB of 23028
Power draw60 W297 to 302 W against a 300 W cap
Core temperature31 C73 C

Readings above the cap are normal, not a measurement bug. The enforced limit is an average the board management holds you to, so instantaneous samples ride a couple of watts either side of it. Alert on sustained draw, never on a single sample crossing the number.

The gotcha here is the thing utilisation hides. On the A100 the same test reported 100 percent utilisation while the SM clock sat at 1050 MHz against a 1410 MHz maximum, because the card was clamped by its power cap. Utilisation was perfect and the card was running a quarter slower than it could. That is why the power and clock fields belong in the same dashboard as utilisation, and it is the reason for field 112.

4. Run GPU health checks

Health watches are a background system: you enable a set of watch groups, DCGM samples them, and you ask for a verdict. Enable and check:

dcgmi health -g 0 -s a
dcgmi health -g 0 -c

The selector letters are p for PCIe, m for memory, i for InfoROM, t for thermal and power, n for NVLink, d for driver, x for ConnectX, and a for all of them. With the card sitting on its power limit, the verdict is a warning rather than a failure:

+---------------------------+----------------------------------------------------------+
| Health Monitor Report                                                                |
+===========================+==========================================================+
| Overall Health            | Warning                                                  |
| GPU                       |                                                          |
| -> 0                      | Warning                                                  |
|    -> Errors              |                                                          |
|       -> Power system     | Warning                                                  |
|                           | Detected clocks event due to power violation in GPU 0.   |
|                           | Monitor the power conditions. This GPU can still         |
|                           | perform workload.                                        |
+---------------------------+----------------------------------------------------------+

Two things trip people up here. First, watches need time to accumulate. Checking immediately after enabling them returns Healthy because nothing has been sampled yet, which is easy to mistake for a clean bill of health. DCGM’s own help text puts the figure at 60 seconds before the first meaningful query, so give it a full minute.

Second, -s a produces a false failure on PCIe cards that carry NVLink hardware with no peer attached. On an A100 PCIe the NVLink watch reports all 12 links down and drags the verdict to Overall Health: Failure on a perfectly healthy card. Drop the NVLink watch on those hosts:

dcgmi health -g 0 -s pmit

Cards with no NVLink hardware at all, like the A10G, are not affected and can safely use -s a. Check which case you are in before you decide:

dcgmi nvlink -s

Links rendered as underscores mean no NVLink hardware is present, so the n watch has nothing to complain about. Links reported as Down are the false-failure case.

5. Run GPU diagnostics

Diagnostics actively run work on the card, unlike health watches which only observe. There are four levels, and they cost progressively more time:

dcgmi diag -r 1

Level 1 runs the software plugin only, and reports the versions it detected alongside the result:

+---------------------------+------------------------------------------------+
| Diagnostic                | Result                                         |
+===========================+================================================+
|-----  Metadata  ----------+------------------------------------------------|
| DCGM Version              | 4.6.1                                          |
| Driver Version Detected   | 595.91.07                                      |
| GPU Device IDs Detected   | 2237                                           |
|-----  Deployment  --------+------------------------------------------------|
| software                  | Pass                                           |
|                           | GPU0: Pass                                     |
+---------------------------+------------------------------------------------+

Level 2 adds the memory and PCIe plugins:

dcgmi diag -r 2

NVIDIA documents level 2 as taking between 2.5 and 10.5 minutes, but that range assumes a 4-GPU or 8-GPU system. Measured on single-GPU hosts it is far quicker:

LevelPluginsA10G, 1 GPUA100, 1 GPU
1software1 s2 s
2software, memory, pcie9 s10 s
3adds diagnostic, nvbandwidth, memory_bandwidth, targeted_stress, targeted_power348 snot measured

Level 3 is a different kind of commitment. It saturates the card for the best part of six minutes, and on a single-GPU host the nvbandwidth plugin reports Skip because there is no peer to measure against:

| software                  | Pass                                           |
| memory                    | Pass                                           |
| diagnostic                | Pass                                           |
| nvbandwidth               | Skip                                           |
| pcie                      | Pass                                           |
+-----  Stress  ------------+------------------------------------------------+
| memory_bandwidth          | Pass                                           |
| targeted_stress           | Pass                                           |
| targeted_power            | Pass                                           |

Run level 3 or 4 on a card that is draining or freshly racked, never on one serving traffic, and keep them out of any cron job that can overlap a real workload. Level 1 is cheap enough to run on every boot.

6. Install dcgm-exporter and expose metrics to Prometheus

The exporter ships in the same repository you already added:

sudo apt-get install -y datacenter-gpu-manager-exporter
dcgm-exporter --version

The package version and the exporter’s own version string differ, which matters when you are matching against upstream release notes:

DCGM Exporter version 4.6.0-4.8.3

Start it and check what it is serving:

sudo systemctl enable --now nvidia-dcgm-exporter
curl -s localhost:9400/metrics | grep -c "^DCGM"

A healthy single-GPU host serves a couple of dozen series. If that count comes back 0, skip ahead to the troubleshooting section, because the exporter fails all-or-nothing and a zero here has two completely different causes.

One metric line shows the label set every rule and dashboard will key on:

DCGM_FI_DEV_GPU_TEMP{gpu="0",UUID="GPU-d3ed7d5d-47b5-609f-56ed-d76ebb18890e",
pci_bus_id="00000000:00:1E.0",device="nvidia0",modelName="NVIDIA A10G",
hostname="ip-10-42-1-55",DCGM_FI_DRIVER_VERSION="595.91.07"} 73

That hostname label is lowercase. The rename landed in 4.8.3, one release after 4.8.2 still emitted Hostname with a capital H, so this breaks on upgrade between two consecutive versions. Any dashboard or alert annotation copied from an older source that references {{ $labels.Hostname }} renders empty now. The two popular community dashboards are unaffected because they key on instance and gpu instead.

7. Enable the clock event, XID, and health metrics

The stock counters file leaves the most useful alerting metrics commented out. Look at what is sitting behind a #:

grep "DCGM_EXP" /etc/dcgm-exporter/default-counters.csv

Six exporter-computed metrics ship disabled:

# DCGM_EXP_CLOCK_EVENTS_COUNT, gauge,   Clock events observed during the configured time window.
# DCGM_EXP_CLOCK_EVENTS_TOTAL, counter, Total clock events observed since exporter start (edge-counted).
# DCGM_EXP_XID_ERRORS_COUNT,   gauge,   XID errors observed during the configured time window.
# DCGM_EXP_XID_ERRORS_TOTAL,   counter, Total XID errors observed since exporter start.
# DCGM_EXP_GPU_HEALTH_STATUS,  gauge,   Current DCGM-reported GPU health status.
# DCGM_EXP_P2P_STATUS,         gauge,   Current NVLink P2P status per peer link.

These matter because of a limitation you cannot work around in PromQL. Field 112 carries the clock event reasons as a raw bitmask. It is not in the stock counters file in any form, commented or otherwise, so you would have to add the line yourself, and even then PromQL has no bitwise operators, which makes testing a single bit cleanly impossible. DCGM_EXP_CLOCK_EVENTS_COUNT decodes the bitmask for you and emits one series per reason.

Copy the stock file and uncomment the five you want:

sudo cp /etc/dcgm-exporter/default-counters.csv /etc/dcgm-exporter/gpu-counters.csv
sudo sed -i 's/^# \(DCGM_EXP_CLOCK_EVENTS\)/\1/; s/^# \(DCGM_EXP_XID_ERRORS\)/\1/; s/^# \(DCGM_EXP_GPU_HEALTH_STATUS\)/\1/' /etc/dcgm-exporter/gpu-counters.csv

Point the unit at your file with a systemd override. Blanking ExecStart before setting it again is required, because systemd appends rather than replaces otherwise:

sudo mkdir -p /etc/systemd/system/nvidia-dcgm-exporter.service.d
sudo vim /etc/systemd/system/nvidia-dcgm-exporter.service.d/override.conf

Add the two lines:

[Service]
ExecStart=
ExecStart=/usr/bin/dcgm-exporter -f /etc/dcgm-exporter/gpu-counters.csv

Reload and restart both services. Restarting the host engine first matters, for reasons covered in troubleshooting:

sudo systemctl daemon-reload
sudo systemctl restart nvidia-dcgm
sudo systemctl restart nvidia-dcgm-exporter

The clock event metric now carries a readable reason:

DCGM_EXP_CLOCK_EVENTS_COUNT{gpu="0",modelName="NVIDIA A10G",hostname="ip-10-42-1-55",
clock_event="power_cap",window_size_in_ms="300000"} 8

The clock_event label takes one of nine values, each mapping to a bit in field 112:

Bitclock_event labelMeaning
0x001gpu_idleCard is idle, clocks dropped on purpose
0x002clocks_settingApplication clocks were set manually
0x004power_capSoftware power cap is clamping clocks
0x008hw_slowdownHardware slowdown engaged
0x010sync_boostHeld back to keep a sync boost group aligned
0x020sw_thermalSoftware thermal slowdown
0x040hw_thermalHardware thermal slowdown
0x080hw_power_brakeExternal power brake asserted
0x100display_clocksLimited by display clock setting

The health metric is the other prize. It puts the same verdicts dcgmi health prints into Prometheus, one series per watch, with the DCGM error code as a label:

DCGM_EXP_GPU_HEALTH_STATUS{gpu="0",hostname="ip-10-42-1-55",
health_error_category="HARDWARE_POWER",health_error_code="DCGM_FR_CLOCK_THROTTLE_POWER",
health_error_severity="MONITOR",health_watch="POWER"} 10

Watches reported are SM, MEM, PCIE, POWER, THERMAL, NVLINK, DRIVER, INFOROM, MCU, PMU, and ALL. A value of 0 is healthy, 10 is a warning, and 20 is a failure. Worth knowing: the exporter sets its own watches on its own group, so this metric populates even when dcgmi health -g 0 -c still insists that watches are not enabled.

8. Scrape the exporter with Prometheus

If Prometheus is not on this host yet, the Prometheus installation guide covers the binary, user, and unit. Create the rules directory first, because the config below references a rules file and promtool refuses to validate a config that points at a path which does not exist yet:

sudo mkdir -p /etc/prometheus/rules
sudo touch /etc/prometheus/rules/gpu-alerts.yml
sudo chown -R prometheus:prometheus /etc/prometheus/rules

Section 10 fills that file. Now point Prometheus at port 9400:

sudo vim /etc/prometheus/prometheus.yml

The scrape job and the alerting wiring both go in:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - /etc/prometheus/rules/gpu-alerts.yml

alerting:
  alertmanagers:
    - static_configs:
        - targets: ["localhost:9093"]

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ["localhost:9090"]

  - job_name: dcgm-exporter
    static_configs:
      - targets: ["localhost:9400"]

On a fleet, replace that static target list with your service discovery of choice and keep the job name, because the alert rules below match on it. Validate before restarting:

promtool check config /etc/prometheus/prometheus.yml
sudo systemctl restart prometheus

Prometheus and promtool disagree about a missing rules file, which is worth knowing before it costs you an afternoon. Prometheus globs rule_files and starts happily with zero rules loaded, while promtool check config hard-errors with does not point to an existing file and exits non-zero. The dangerous half is Prometheus: the service looks healthy and no rules are evaluated.

Check the target went green in the Prometheus UI under Status then Targets.

Prometheus scraping the DCGM exporter target on port 9400

With the target up, GPU telemetry is queryable. Graphing DCGM_FI_DEV_POWER_USAGE across a load test shows the card jumping from its idle draw to the power ceiling and staying there.

Querying NVIDIA GPU power draw with DCGM metrics in Prometheus

The expression browser is fine for confirming a metric exists. It is not where you want to be at three in the morning, which is what the next section fixes.

9. Build the GPU dashboard in Grafana

Install Grafana if it is not already running, following the Grafana setup guide, then add Prometheus as a datasource. Provisioning it from a file survives reinstalls better than clicking through the UI:

sudo vim /etc/grafana/provisioning/datasources/prometheus.yml

Four lines of configuration are enough:

apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://localhost:9090
    isDefault: true

Restart Grafana to pick it up. If you need to reset the admin password on a current release, the old grafana-cli wrapper now fails with Grafana-server Init Failed: Could not find config defaults unless you pass a homepath, and it prints a deprecation notice pointing at the new subcommand:

sudo grafana cli --homepath /usr/share/grafana admin reset-admin-password 'YourStrongPassword'

Build panels around the four fleet signals, then add clocks and clock events beside them so a throttled card is visible at a glance rather than hidden behind a green utilisation number.

Grafana GPU monitoring dashboard showing DCGM utilisation, memory, power and temperature

The queries behind those panels are short. Framebuffer percentage has to be computed because DCGM exports used and free separately rather than a ratio:

DCGM_FI_DEV_GPU_UTIL
DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE) * 100
DCGM_FI_DEV_POWER_USAGE
DCGM_FI_DEV_GPU_TEMP
DCGM_FI_PROF_PIPE_TENSOR_ACTIVE * 100
DCGM_EXP_CLOCK_EVENTS_COUNT

Put a threshold line on the power panel at the card’s enforced limit and on the temperature panel at your warning level. A flat line riding the ceiling is the visual signal that the card is capped.

GPU power draw pinned at the 300W cap with core temperature rising under load

Below those, the clock event panel and a table of DCGM health watches turn two of the harder-to-read signals into something you can scan.

Grafana panels for GPU clock events by reason and DCGM health watch status

If you would rather start from something prebuilt, dashboards 12239 and 15117 on grafana.com both target dcgm-exporter and work unmodified against current releases.

Community NVIDIA DCGM exporter Grafana dashboard 12239 with live GPU data

Import both and you will find only one. They ship the same dashboard UID, so importing the second overwrites the first. Give one of them a fresh UID during import if you want to keep both.

10. Alert on GPU utilisation, memory, power, and thermal health

Fill in the rules file created back in section 8:

sudo vim /etc/prometheus/rules/gpu-alerts.yml

These rules cover the four signals plus the hardware faults that end a GPU’s life:

groups:
  - name: gpu-availability
    rules:
      - alert: GPUExporterDown
        expr: up{job="dcgm-exporter"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "DCGM exporter on {{ $labels.instance }} is not responding"

  - name: gpu-thermal
    rules:
      - alert: GPUTemperatureHigh
        expr: DCGM_FI_DEV_GPU_TEMP > 80
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "GPU {{ $labels.gpu }} on {{ $labels.hostname }} at {{ $value }}C"

      - alert: GPUTemperatureCritical
        expr: DCGM_FI_DEV_GPU_TEMP > 90
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "GPU {{ $labels.gpu }} on {{ $labels.hostname }} at {{ $value }}C"
          description: "Thermal slowdown is imminent. Check airflow and inlet temperature."

      - alert: GPUThermalSlowdown
        expr: DCGM_EXP_CLOCK_EVENTS_COUNT{clock_event=~"sw_thermal|hw_thermal"} > 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "GPU {{ $labels.gpu }} is in thermal slowdown ({{ $labels.clock_event }})"

  - name: gpu-power-and-clocks
    rules:
      - alert: GPUPowerCapThrottling
        expr: DCGM_EXP_CLOCK_EVENTS_COUNT{clock_event="power_cap"} > 0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "GPU {{ $labels.gpu }} on {{ $labels.hostname }} is clamped by its power cap"
          description: "The card is drawing its full budget and running below boost clock."

      - alert: GPUHardwareSlowdown
        expr: DCGM_EXP_CLOCK_EVENTS_COUNT{clock_event=~"hw_slowdown|hw_power_brake"} > 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "GPU {{ $labels.gpu }} in hardware slowdown ({{ $labels.clock_event }})"

  - name: gpu-memory
    rules:
      - alert: GPUMemoryNearlyFull
        expr: >
          DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE) * 100 > 90
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "GPU {{ $labels.gpu }} framebuffer {{ $value | printf \"%.1f\" }}% full"
          description: "The next allocation is likely to fail with an out of memory error."

  - name: gpu-hardware-health
    rules:
      - alert: GPUHealthDegraded
        expr: DCGM_EXP_GPU_HEALTH_STATUS{health_error_severity!="MONITOR"} > 0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "DCGM health watch {{ $labels.health_watch }} degraded on GPU {{ $labels.gpu }}"
          description: "Error {{ $labels.health_error_code }} on {{ $labels.hostname }}."

      - alert: GPUXidError
        expr: DCGM_EXP_XID_ERRORS_COUNT > 0
        labels:
          severity: critical
        annotations:
          summary: "XID error on GPU {{ $labels.gpu }} of {{ $labels.hostname }}"
          description: "Cross-reference the code in dmesg before recycling the node."

      - alert: GPURowRemapFailure
        expr: DCGM_FI_DEV_ROW_REMAP_FAILURE > 0
        labels:
          severity: critical
        annotations:
          summary: "Row remap failure on GPU {{ $labels.gpu }}"
          description: "The card is out of spare memory rows. Drain the node and replace the GPU."

      - alert: GPUUncorrectableRemappedRows
        expr: increase(DCGM_FI_DEV_UNCORRECTABLE_REMAPPED_ROWS[30m]) > 0
        labels:
          severity: warning
        annotations:
          summary: "Uncorrectable memory row remapped on GPU {{ $labels.gpu }}"

  - name: gpu-utilisation
    rules:
      - alert: GPUSaturated
        expr: avg_over_time(DCGM_FI_DEV_GPU_UTIL[10m]) > 90
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "GPU {{ $labels.gpu }} saturated at {{ $value | printf \"%.0f\" }}%"

      - alert: GPUIdleButAllocated
        expr: avg_over_time(DCGM_FI_PROF_GR_ENGINE_ACTIVE[1h]) < 0.05
        for: 1h
        labels:
          severity: info
        annotations:
          summary: "GPU {{ $labels.gpu }} on {{ $labels.hostname }} nearly idle for an hour"
          description: "This card is being paid for and not used."

Validate and reload without a restart:

promtool check rules /etc/prometheus/rules/gpu-alerts.yml
sudo systemctl kill -s HUP prometheus

Use the signal, not the HTTP endpoint. Plenty of guides reach for curl -X POST http://localhost:9090/-/reload, but the management API is disabled unless Prometheus was started with --web.enable-lifecycle. Without the flag it answers 403 Lifecycle API is not enabled. and curl still exits 0, so you get a clean-looking terminal and rules that never loaded. Add the flag to the unit if you want that endpoint; SIGHUP works on a stock install either way. Prefer the unit-scoped form above over kill -HUP $(pidof prometheus): if the service is down, pidof returns nothing and the command collapses into a bare kill -HUP that prints usage text, which is a confusing thing to meet while debugging a dead service.

Three of those rules deserve an explanation, because they came out of watching the rules misbehave on a real card.

GPUHealthDegraded filters out MONITOR severity deliberately. A GPU running flat out at its power limit sets the POWER watch to 10 with severity MONITOR, so an unfiltered > 0 rule fires on every healthy busy GPU in the fleet. Filter it out and you still catch the watches that mean something. The same rule needs a second exclusion on PCIe fleets: the exporter always watches every category including NVLINK, and the down-link error carries triage severity rather than MONITOR, so the false failure described in section 4 fires here permanently too. Add health_watch!="NVLINK" to the selector on hosts with unconnected NVLink hardware.

The framebuffer percentage reads very slightly high. There is no total-memory field, so the expression divides used by used plus free, and DCGM reports a third bucket, DCGM_FI_DEV_FB_RESERVED, that is left out of the denominator. On a 24 GB card the reserved slice is a few hundred MiB, enough to reach a > 90 threshold marginally sooner than the card really does.

Two rules also take twice as long to fire as they look. GPUSaturated averages over 10 minutes and then waits for: 10m, so first notification is around 20 minutes in; GPUIdleButAllocated does the same over an hour and needs two hours of history behind it. Neither is broken when it stays quiet on a fresh Prometheus.

GPUIdleButAllocated is a cost rule rather than a fault rule. On rented GPUs, an idle card with the graphics engine below 5 percent for an hour is money leaving the building, and nothing else in this rule set would tell you.

The throttle rules deliberately do not use the violation counters. DCGM exposes power_violation (field 240) and per-reason nanosecond counters like clocks_event_power_cap_ns (field 1420), and they look like the obvious thing to put a rate() on. On an A100 running 40 seconds of continuous, confirmed power capping, both stayed frozen at exactly the same value before and after. So did nvidia-smi’s own SW Power Capping counter, which means the underlying NVML accumulator was not advancing rather than DCGM misreporting it. A rate() alert on those fields would never have fired. The exporter’s clock event metrics are edge-counted independently, which is why they work.

With a load running, the rules move through pending into firing.

GPU alert rules firing in Prometheus for memory and power cap throttling

One threshold will not fire on every card, and that is correct. The A10G peaked at 73 C under sustained full load, so GPUTemperatureHigh at 80 C stayed quiet. Those thresholds are sized for datacenter cards in a warm aisle, where 80 C is a genuine early warning ahead of the hardware’s own 95 C slowdown point.

11. Route GPU alerts through Alertmanager

Alertmanager handles grouping and delivery. Group by GPU and host so a bad node produces one notification rather than one per rule:

sudo vim /etc/alertmanager/alertmanager.yml

The grouping keys are the important part:

route:
  receiver: gpu-oncall
  group_by: ["alertname", "gpu", "hostname"]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 3h

receivers:
  - name: gpu-oncall
    webhook_configs:
      - url: http://127.0.0.1:5001/alerts

Swap that webhook receiver for Slack, email, or PagerDuty using the receiver configuration in the Alertmanager setup guide. Firing alerts arrive grouped by the labels above:

GPU alerts grouped by alertname, gpu and hostname in Alertmanager

Notice the grouping labels use lowercase hostname. A group_by naming Hostname would silently group everything into one bucket, because the label would not exist.

12. Run dcgm-exporter on Kubernetes

On a cluster, the exporter runs as a DaemonSet. The NVIDIA GPU Operator installs it alongside the driver and device plugin, or you can deploy the chart on its own:

helm repo add gpu-helm-charts https://nvidia.github.io/dcgm-exporter/helm-charts
helm repo update
helm install dcgm-exporter gpu-helm-charts/dcgm-exporter --namespace gpu-monitoring --create-namespace

The chart’s default security context is the part to read before you tighten anything:

securityContext:
  runAsNonRoot: false
  runAsUser: 0
  capabilities:
    add: ["SYS_ADMIN"]  # Required for profiling metrics (DCGM_FI_PROF_*)
    drop: ["ALL"]
  allowPrivilegeEscalation: false

That SYS_ADMIN capability is load-bearing. Strip it to satisfy a pod security policy and the profiling fields stop working, which takes the whole exporter down rather than degrading it. The chart’s comment covers half of it, telling you to set runAsNonRoot: true, runAsUser: 1000 and drop SYS_ADMIN for a non-root install without profiling metrics. It does not mention the other half, which the failure above makes clear: you must also remove the DCGM_FI_PROF_* lines from the counters file, or the exporter serves nothing at all.

The chart enables a ServiceMonitor by default, so a kube-prometheus-stack install picks the target up with no extra work:

serviceMonitor:
  enabled: true
  interval: 30s
  scrapeTimeout: 25s

Section 7 has a Kubernetes counterpart, and skipping it quietly disarms five of the rules. The chart mounts its own ConfigMap over /etc/dcgm-exporter/default-counters.csv, and in that ConfigMap every DCGM_EXP_* line is commented out exactly as it is in the deb. So on a default chart install GPUThermalSlowdown, GPUPowerCapThrottling, GPUHardwareSlowdown, GPUHealthDegraded, and GPUXidError reference metrics that do not exist. Supply your own counters file instead, remembering that it replaces the list rather than adding to it:

helm upgrade --install dcgm-exporter gpu-helm-charts/dcgm-exporter \
  --namespace gpu-monitoring --create-namespace \
  --set-file customMetrics=/etc/dcgm-exporter/gpu-counters.csv

That is the edited file from section 7, and it has to be readable from wherever you run helm, not from the GPU node. Copy it to your workstation first if the two are different machines. With the right file in place the same rule set works on a cluster. Two other defaults catch people out: the chart only tolerates the control-plane taint, so GPU nodes tainted nvidia.com/gpu=present:NoSchedule need that toleration added, and installing this chart on a cluster already running the GPU Operator gives you two exporters, because the Operator ships its own.

With the counters file sorted, the alert rules from section 10 carry over to a Prometheus and Grafana Helm stack as a PrometheusRule. They are also the missing piece for anyone running vLLM on Kubernetes with NVIDIA GPUs, where knowing whether a card is thermally capped explains a lot of unexplained latency.

Troubleshooting the errors you will actually hit

Error: “Failed to watch DCGM fields … Host engine is running as non-root”

The exporter starts, logs this, and exits. curl localhost:9400/metrics returns zero DCGM metrics, not a partial set:

level=ERROR msg="Failed to watch DCGM fields" entity_type=GPU
  error="error watching fields: Host engine is running as non-root"
level=ERROR msg="DCGM collector for entity type 'GPU' cannot be initialized"

The message points at the wrong thing. On a container where this reproduces, the host engine was already running as root. The real blocker is the missing CAP_SYS_ADMIN capability, which the profiling fields require. The 4.x images are distroless, so there is no shell to run capsh in. Check from the host instead with docker inspect --format '{{.HostConfig.CapAdd}}' <container>, which prints [] when nothing was granted. Note that this reports what was requested at run time rather than the effective capability set the process ended up with, so it answers this particular question and is not a drop-in for capsh. Then add the capability:

docker run --gpus all --cap-add SYS_ADMIN -p 9400:9400 nvcr.io/nvidia/k8s/dcgm-exporter:4.6.0-4.8.3-distroless

The reason a single unwatchable field kills everything is that the exporter puts all 26 default fields into one field group and watches them in one call. Five of those are profiling fields. If any one field in the group cannot be watched, the group fails and the collector never initialises, so you get zero metrics instead of the 21 that would have worked. Proving it takes one command: strip the DCGM_FI_PROF_* lines from a copy of the counters file and the same exporter serves metrics happily.

Error: “The third-party Profiling module returned an unrecoverable error”

Same zero-metrics symptom, completely different cause, and this one bites on bare metal with full root:

level=ERROR msg="Failed to watch DCGM fields" entity_type=GPU
  error="error watching fields: The third-party Profiling module returned an unrecoverable error"
systemd[1]: nvidia-dcgm-exporter.service: Main process exited, code=exited, status=1/FAILURE
systemd[1]: nvidia-dcgm-exporter.service: Scheduled restart job, restart counter is at 4

This trips people up because the natural order of work causes it. You prove the profiling fields work with dcgmi dmon, then start the exporter, and the exporter fails. Watching the profiling fields with dcgmi leaves the profiling module in a state the next client cannot acquire. Three runs, restarting the host engine before each, isolate it:

Sequence after restarting nvidia-dcgmExporter result
Start the exporter immediatelyWorks
Run dcgmi dmon -e 1001,1004,1005 firstFails, unrecoverable profiling error
Run dcgmi dmon -e 150,155,203 firstWorks

Only the profiling fields, the ones numbered 1001 and above, cause it. Ordinary fields do not. The fix is one restart, and the ordering rule is to always bounce the host engine before starting the exporter after any manual dcgmi dmon profiling session:

sudo systemctl restart nvidia-dcgm
sudo systemctl restart nvidia-dcgm-exporter

Restart the host engine first and the exporter second, because the exporter has to acquire the profiling module from a freshly started engine.

Error: “Health watches not enabled. Please enable watches.”

dcgmi health -g 0 -c returns this even while the exporter is happily publishing DCGM_EXP_GPU_HEALTH_STATUS. Both are true at once. The exporter creates its own group and sets its own watches, and group 0 that the CLI is asking about is a different group. Enable watches for the CLI separately with dcgmi health -g 0 -s pmit.

PARSE ERROR: Argument: -v Couldn’t find match for argument

Guides written against DCGM 2.x and 3.x use -v for verbose health output. The flag is gone from dcgmi health specifically; it still exists on dcgmi diag and dcgmi discovery, which is why the error looks inconsistent. Use -j for JSON output instead, which is more useful for scripting anyway.

A memory temperature alert that never fires

Field 140 is not populated on every card. The A10G reports 0 for DCGM_FI_DEV_MEMORY_TEMP, and nvidia-smi -q -d TEMPERATURE confirms Memory Current Temp : N/A. The A100 reports it properly at 58 C under load. Before you rely on a memory temperature rule, check the metric is non-zero on the cards you actually run, or the rule is decoration.

Everything above stands on the exporter staying alive, so the one rule to keep even if you drop the rest is GPUExporterDown. A GPU fleet with a dead exporter looks exactly like a GPU fleet with no problems.

Keep reading

What Is DeepSeek Harness? Install dsh and Run Your First Agent AI What Is DeepSeek Harness? Install dsh and Run Your First Agent Run DeepSeek Harness With a Local Model: Ollama, vLLM, llama.cpp AI Run DeepSeek Harness With a Local Model: Ollama, vLLM, llama.cpp OpenCode vs Claude Code vs Cursor: AI Coding Agents Compared (2026) AI OpenCode vs Claude Code vs Cursor: AI Coding Agents Compared (2026) Install Dockge on Ubuntu: Self-Hosted Docker Compose Manager Containers Install Dockge on Ubuntu: Self-Hosted Docker Compose Manager Self-Host Cal.com on Ubuntu with Docker DevOps Self-Host Cal.com on Ubuntu with Docker Prometheus Alertmanager Setup – Slack, Email, PagerDuty Monitoring Prometheus Alertmanager Setup – Slack, Email, PagerDuty

Leave a Comment

Press ESC to close