Conversation
A hostengine that starts before the NVIDIA driver is ready initialises without NVML. The exporter connects to it, finds no GPUs, and builds its registry with no GPU collector, logging: Not collecting GPU metrics; error retrieving DCGM MIG hierarchy: Cannot perform the requested operation because NVML doesn't exist on this system. The registry is only rebuilt on reload or a bind event, so the exporter then serves an empty metrics page for the life of the process. /health returns 200 throughout, because it only checks that the registry pointer is non-nil, so a liveness probe pointed at it never fires and the pod sits Ready and blind until something restarts it by hand. Add --health-require-gpus (DCGM_EXPORTER_HEALTH_REQUIRE_GPUS). When set, /health returns 503 if the registry holds no collector for dcgm.FE_GPU, which lets an existing httpGet liveness probe recover the pod on its own. Off by default, so nodes that legitimately expose no GPUs keep passing and no existing deployment changes behaviour. Reload takes precedence: the registry is legitimately empty mid-reload, and returning 503 there would restart the pod on every reload. Registry.CollectorCount reports the number of collectors registered for an entity group, under the existing read lock. Signed-off-by: Weigang Geng <weigang@aranya.tech>
gengwg
marked this pull request as draft
September 16, 2026 23:21
added 2 commits
September 16, 2026 16:25
Review follow-up. The four original tests did not pin the entity-group keying: replacing CollectorCount's body with len(r.collectorGroups) passed all of them, and that mutation reproduces the original bug, since the incident registry held exactly one non-GPU collector. "Absent" used an empty registry, which is a different shape from "a registry holding only non-GPU collectors". Add the incident shape (one FE_CPU collector, expect 503) and a direct registry unit test asserting CollectorCount is keyed by entity group. Both fail under that mutation and pass on the real implementation. Add the pieces pkg/cmd/AGENTS.md asks for on a new flag: the defaultConfig() entry, the defaults-match assertion, and a table test covering the flag, its explicit-false form and the env var. Document the user-visible surface: an llms.txt contract bullet, the chart arguments comment and a deployment/README.md section. Both note that the predicate counts FE_GPU collectors, so a counters file contributing no FE_GPU fields also reads as zero, and that basicAuth.users degrades both probes to tcpSocket, which cannot observe the 503. Also correct two comments that overstated recovery: a restart only recovers once the hostengine is sighted, and the registry is rebuilt on reload and bind rather than being fixed for the process lifetime. Load the registry pointer once in Health so an unbind between loads cannot skip the check. Signed-off-by: Weigang Geng <weigang@aranya.tech>
TestMain only runs goleak, so nothing unsets flag env vars between tests. With DCGM_EXPORTER_HEALTH_REQUIRE_GPUS exported, the two off cases would pass or fail for the wrong reason. Call the existing unsetFlagEnvVars helper after NewApp and before t.Setenv, matching the neighbouring defaults test. Signed-off-by: Weigang Geng <weigang@aranya.tech>
gengwg
marked this pull request as ready for review
September 17, 2026 00:05
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A DCGM hostengine that starts before the NVIDIA driver is ready initialises without NVML. It listens normally, but reports no GPUs. An exporter that connects to it in that state finds nothing to collect and builds its registry with no GPU collector:
A healthy start on the same node logs
collector_count=2.The registry is only rebuilt on reload or a GPU bind event, so from here the exporter serves an empty metrics page for the life of the process.
/healthreturns 200 the whole time, because it only checks that the registry pointer is non-nil. The pod stays1/1 Running, a liveness probe on/healthnever fires, and the node exports no GPU metrics until a human restarts it.Fixing the hostengine underneath does not help: the exporter does not re-enumerate. I confirmed that on a live node — restarting the hostengine took
dcgmi discovery -lfrom0 GPUs foundto8 GPUs found, and metrics stayed absent until the exporter was restarted as well.What this changes
Adds
--health-require-gpus/DCGM_EXPORTER_HEALTH_REQUIRE_GPUS. When set,/healthreturns 503 if the registry holds no collector fordcgm.FE_GPU, so an existinghttpGetliveness probe on/healthrestarts the pod and it re-enumerates on its own.IsReloadInProgress().Registry.CollectorCount(entityGroup)reports the collector count for an entity group under the existing read lock.Where this was seen
A production H100 cluster running gpu-operator. Nodes were being rebooted one at a time for unrelated maintenance while a gpu-operator chart upgrade was in flight. Nodes that rebooted before the upgrade came back sighted; after it, 8 of 9 came back blind and stayed blind, one for two days before anyone noticed. There was no signal in Kubernetes to find them by: every pod read
1/1 Running.Relationship to gpu-operator#2855
NVIDIA/gpu-operator#2855 proposes an exec liveness probe on the
nvidia-dcgmcontainer so a deaf hostengine restarts itself. That covers the hostengine; this covers the exporter in front of it. Both are needed, because recovering the hostengine alone leaves the exporter holding its empty registry.Doing the exporter half here rather than as a probe in gpu-operator is deliberate: the dcgm-exporter image ships no HTTP client (no curl, wget or nc), so an exec probe would have to hand-roll a request over bash
/dev/tcp. ThehttpGet /healthprobe already exists in the gpu-operator asset and starts working once the endpoint reports the truth.Testing
go test ./internal/pkg/server/... ./internal/pkg/registry/... ./pkg/cmd/...passes. Five new server cases cover: default off with no GPU collectors (200), required and absent (503 plusX-GPU-Collectors: 0), required with only non-GPU collectors — the incident shape — (503), required and present (200), and reload in progress with the flag on (200). A registry unit test pinsCollectorCountto the entity group, and a CLI table test covers the flag, its explicit-false form and both env values.I verified the tests fail when the production code is broken, rather than only that they pass:
TestHealthReturnsUnavailableWhenGPUCollectorsRequiredButAbsentTestHealthReturnsOKDuringReloadEvenWhenGPUCollectorsRequiredAlternatives considered
Validation
make check-format— clean.make test-main— all packages pass exceptinternal/pkg/nvmlproviderandinternal/pkg/integration_test, which fail identically on the unmodified base commit on this machine (no NVIDIA driver / NVML present). Verified by stashing the change and re-running those two packages..codex/validation.md. The new behaviour is a registry-state predicate and is provable at package level, which is what the added tests do.Mutation-checked rather than only run green — with
CollectorCountchanged to ignore its entity-group argument (return len(r.collectorGroups)),TestHealthReturnsUnavailableWhenOnlyNonGPUCollectorsRegisteredandTestRegistryCollectorCountIsKeyedByEntityGroupboth fail; removing the 503 branch or the reload guard each fail their own test.Open question for maintainers: the predicate
CollectorCount(dcgm.FE_GPU) == 0is a proxy for "no GPUs visible", and it is not exact in either direction:FE_GPUfields reports 503 on a node that does have GPUs. The default CSV ships all sixDCGM_EXP_*commented out, so nothing else registers underFE_GPUin that case.DCGM_EXP_*counter registers underFE_GPU, so a node where the watch list exists but DCGM reports no GPUs would read healthy.The sharper predicate is the topology the server already holds — the
FE_GPUwatch list'sDeviceInfo().GPUCount(), the same accessorlogTopologyInfouses — which is zero in the first case and non-zero in the second.I have not switched to it because I cannot verify here how
GPUCount()behaves under MIG partitioning or-d i/-d g:0-3device selection; if-d iyields a GPU watch list with no physical GPUs it would false-positive where the current predicate does not. Happy to switch if a maintainer can confirm that behaviour, or to leave it as-is given the flag is opt-in and both caveats are now documented beside it.