Skip to content

Commit 5014ca5

Browse files
authored
[LEP-4267] fix(xid): detect fallen-off-bus logs as XID 79 (#1209)
Some new GPU drivers now emitting in multi-line ``` $ journalctl -k | grep -C 20 fallen Feb 14 05:03:41 000-000-00000-1-gpu-1 systemd[1]: systemd-pcrmachine.service - TPM2 PCR Machine ID Measurement was skipped because of an unmet condition check (ConditionSecurity=measured-uki). Feb 14 05:03:41 000-000-00000-1-gpu-1 kernel: mlx5_core 0000:ab:00.0 mlx5_5: Port: 1 Link INIT Feb 14 05:03:41 000-000-00000-1-gpu-1 kernel: workqueue: ib_cache_event_task [ib_core] hogged CPU for >10000us 4 times, consider switching to WQ_UNBOUND Feb 14 05:03:41 000-000-00000-1-gpu-1 systemd[1]: Starting systemd-remount-fs.service - Remount Root and Kernel File Systems... Feb 14 05:03:41 000-000-00000-1-gpu-1 systemd[1]: systemd-tpm2-setup-early.service - TPM2 SRK Setup (Early) was skipped because of an unmet condition check (ConditionSecurity=measured-uki). Feb 14 05:03:41 000-000-00000-1-gpu-1 systemd[1]: Starting systemd-udev-trigger.service - Coldplug All udev Devices... Feb 14 05:03:41 000-000-00000-1-gpu-1 systemd[1]: Started systemd-journald.service - Journal Service. Feb 14 05:03:41 000-000-00000-1-gpu-1 kernel: EXT4-fs (md127p3): re-mounted 0f3cd65a-338b-4574-bba3-ecef214e1917 r/w. Quota mode: none. Feb 14 05:03:41 000-000-00000-1-gpu-1 kernel: mlx5_core 0000:ab:00.0 mlx5_5: Port: 1 Link ACTIVE Feb 14 05:03:42 000-000-00000-1-gpu-1 kernel: bridge: filtering via arp/ip/ip6tables is no longer available by default. Update your scripts to load br_netfilter if you need this. Feb 14 05:03:42 000-000-00000-1-gpu-1 kernel: Bridge firewalling registered Feb 14 05:03:42 000-000-00000-1-gpu-1 kernel: knem 1.1.4.90mlnx3: initialized Feb 14 05:03:42 000-000-00000-1-gpu-1 systemd-journald[2382]: Received client request to flush runtime journal. Feb 14 05:03:42 000-000-00000-1-gpu-1 systemd-journald[2382]: /var/log/journal/c0f88c9a436e4570b7183c73a833c48c/system.journal: Journal file uses a different sequence number ID, rotating. Feb 14 05:03:42 000-000-00000-1-gpu-1 systemd-journald[2382]: Rotating system journal. Feb 14 05:03:42 000-000-00000-1-gpu-1 kernel: nvidia-nvlink: Nvlink Core is being initialized, major device number 236 Feb 14 05:03:42 000-000-00000-1-gpu-1 kernel: Feb 14 05:03:42 000-000-00000-1-gpu-1 kernel: nvidia 0000:18:00.0: enabling device (0140 -> 0142) Feb 14 05:03:42 000-000-00000-1-gpu-1 kernel: NVRM: The NVIDIA GPU 0000:18:00.0 NVRM: (PCI ID: 10de:2901) installed in this system has NVRM: fallen off the bus and is not responding to commands. Feb 14 05:03:42 000-000-00000-1-gpu-1 kernel: nvidia: probe of 0000:18:00.0 failed with error -1 Feb 14 05:03:42 000-000-00000-1-gpu-1 kernel: nvidia 0000:29:00.0: enabling device (0140 -> 0142) Feb 14 05:03:42 000-000-00000-1-gpu-1 kernel: nvidia 0000:3a:00.0: enabling device (0140 -> 0142) Feb 14 05:03:42 000-000-00000-1-gpu-1 kernel: nvidia 0000:4b:00.0: enabling device (0140 -> 0142) ``` --------- Signed-off-by: Gyuho Lee <gyuhol@nvidia.com>
1 parent 035de36 commit 5014ca5

11 files changed

Lines changed: 401 additions & 273 deletions

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ ARG CUDA_VERSION="12.4.1"
1313
# ==============================================================================
1414
# Stage 1: Builder - Build gpud binary and vendor Go dependencies
1515
# ==============================================================================
16-
FROM golang:1.24.7 AS builder
16+
FROM golang:1.25.7 AS builder
1717
ARG TARGETOS
1818
ARG TARGETARCH
1919
ARG BUILDPLATFORM

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ To install from the official release on Linux amd64 (x86_64) machine:
3535
curl -fsSL https://pkg.gpud.dev/install.sh | sh
3636
```
3737

38-
To specify a version:
38+
To install the latest published version explicitly:
3939

4040
```bash
41-
curl -fsSL https://pkg.gpud.dev/install.sh | sh -s v0.10.0
41+
curl -fsSL https://pkg.gpud.dev/install.sh | sh -s $(curl -fsSL https://pkg.gpud.dev/unstable_latest.txt)
4242
```
4343

4444
The install script also currently support other architectures (e.g., arm64) and OSes (e.g., macOS).

components/accelerator/nvidia/xid/component_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,37 @@ func TestCheck(t *testing.T) {
575575
assert.Equal(t, 31, data.FoundErrors[0].Xid)
576576
})
577577

578+
t.Run("with fallen-off-bus fallback format without explicit Xid", func(t *testing.T) {
579+
mockedNVML := createMockNVMLInstance()
580+
gpudInstance := &components.GPUdInstance{
581+
RootCtx: ctx,
582+
NVMLInstance: mockedNVML,
583+
}
584+
585+
comp, err := New(gpudInstance)
586+
assert.NoError(t, err)
587+
588+
c := comp.(*component)
589+
c.readAllKmsg = func(ctx context.Context) ([]kmsg.Message, error) {
590+
return []kmsg.Message{
591+
{
592+
Timestamp: metav1.NewTime(time.Now()),
593+
Message: "NVRM: The NVIDIA GPU 0000:18:00.0\n" +
594+
"NVRM: (PCI ID: 10de:2901) installed in this system has\n" +
595+
"NVRM: fallen off the bus and is not responding to commands.",
596+
},
597+
}, nil
598+
}
599+
600+
result := comp.Check()
601+
data := result.(*checkResult)
602+
assert.Len(t, data.FoundErrors, 1)
603+
assert.Equal(t, 79, data.FoundErrors[0].Xid)
604+
assert.Equal(t, "PCI:0000:18:00", data.FoundErrors[0].DeviceUUID)
605+
assert.Equal(t, apiv1.HealthStateTypeUnhealthy, result.HealthStateType())
606+
assert.Contains(t, result.Summary(), "matched 1 xid errors from 1 kmsg(s)")
607+
})
608+
578609
t.Run("with XID 63 and 64 errors that should be skipped when row remapping is supported", func(t *testing.T) {
579610
// Using a properly implemented mock with row remapping support
580611
mockedNVML := createMockNVMLInstanceWithRowRemapping()
@@ -1293,6 +1324,80 @@ func TestHandleEventChannel(t *testing.T) {
12931324
wg.Wait()
12941325
}
12951326

1327+
func TestStartWithFallenOffBusFallback(t *testing.T) {
1328+
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
1329+
defer cancel()
1330+
1331+
dbRW, dbRO, cleanup := sqlite.OpenTestDB(t)
1332+
defer cleanup()
1333+
1334+
store, err := eventstore.New(dbRW, dbRO, DefaultRetentionPeriod)
1335+
assert.NoError(t, err)
1336+
1337+
rebootEventStore := pkghost.NewRebootEventStore(store)
1338+
mockedNVML := createMockNVMLInstance()
1339+
1340+
gpudInstance := &components.GPUdInstance{
1341+
RootCtx: ctx,
1342+
EventStore: store,
1343+
RebootEventStore: rebootEventStore,
1344+
NVMLInstance: mockedNVML,
1345+
}
1346+
1347+
comp, err := New(gpudInstance)
1348+
assert.NoError(t, err)
1349+
assert.NotNil(t, comp)
1350+
1351+
c := comp.(*component)
1352+
if c.eventBucket == nil {
1353+
c.eventBucket, err = store.Bucket(Name)
1354+
assert.NoError(t, err)
1355+
}
1356+
1357+
kmsgCh := make(chan kmsg.Message, 10)
1358+
go c.start(kmsgCh, 100*time.Millisecond)
1359+
defer func() {
1360+
assert.NoError(t, comp.Close())
1361+
}()
1362+
1363+
kmsgCh <- kmsg.Message{
1364+
Timestamp: metav1.NewTime(time.Now()),
1365+
Message: "NVRM: The NVIDIA GPU 0000:18:00.0\n" +
1366+
"NVRM: (PCI ID: 10de:2901) installed in this system has\n" +
1367+
"NVRM: fallen off the bus and is not responding to commands.",
1368+
}
1369+
1370+
deadline := time.Now().Add(5 * time.Second)
1371+
foundXid79 := false
1372+
xidRegex := regexp.MustCompile(`XID (\d+)`)
1373+
for time.Now().Before(deadline) {
1374+
events, err := comp.Events(ctx, time.Now().Add(-1*time.Hour))
1375+
assert.NoError(t, err)
1376+
1377+
for _, event := range events {
1378+
if matches := xidRegex.FindStringSubmatch(event.Message); len(matches) > 1 {
1379+
xidCode, err := strconv.Atoi(matches[1])
1380+
assert.NoError(t, err)
1381+
if xidCode == 79 {
1382+
foundXid79 = true
1383+
break
1384+
}
1385+
}
1386+
}
1387+
if foundXid79 {
1388+
break
1389+
}
1390+
1391+
time.Sleep(100 * time.Millisecond)
1392+
}
1393+
1394+
assert.True(t, foundXid79, "expected synthetic XID 79 event to be stored from fallen-off-bus fallback format")
1395+
1396+
states := comp.LastHealthStates()
1397+
assert.NotEmpty(t, states)
1398+
assert.Equal(t, apiv1.HealthStateTypeUnhealthy, states[0].Health)
1399+
}
1400+
12961401
func TestCheckResult_getError(t *testing.T) {
12971402
tests := []struct {
12981403
name string

components/accelerator/nvidia/xid/kmsg.go

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,25 @@ const (
2626
// Example: NVRM: Xid (PCI:0018:01:00): 149, NETIR_LINK_EVT Fatal XC0 i0 Link 08 (0x004505c6 0x00000000 ...)
2727
// Reference: https://docs.nvidia.com/deploy/xid-errors/analyzing-xid-catalog.html
2828
RegexNVRMXidExtended = `NVRM: Xid \(PCI:([0-9a-fA-F:]+)\): (\d+)(?:, pid=(\d+), name=([^,]+))?, ([A-Z_]+(?:/[A-Z_]+)?)\s+(Nonfatal|Fatal)\s+(XC[01])\s+(i\d+)\s+Link\s+(-?\d+)\s+\((0x[0-9a-fA-F]+)\s+(0x[0-9a-fA-F]+)(?:\s+(0x[0-9a-fA-F]+))?(?:\s+(0x[0-9a-fA-F]+))?(?:\s+(0x[0-9a-fA-F]+))?(?:\s+(0x[0-9a-fA-F]+))?`
29+
30+
// Fallback signatures for "GPU has fallen off the bus" logs that do not include
31+
// explicit "NVRM: Xid (...)" text.
32+
//
33+
// Multiline example:
34+
// NVRM: The NVIDIA GPU 0000:18:00.0
35+
// NVRM: ... fallen off the bus and is not responding to commands.
36+
RegexNVRMFallenOffBusMultiline = `(?s)NVRM:\s+The NVIDIA GPU ((?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2})\.0.*?fallen off the bus and is not responding to commands\.`
37+
38+
// Single-line example:
39+
// NVRM: GPU 0000:18:00.0: GPU has fallen off the bus.
40+
RegexNVRMFallenOffBusSingleLine = `NVRM:\s+GPU ((?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2})\.0:\s+GPU has fallen off the bus\.?`
2941
)
3042

3143
var (
32-
compiledRegexNVRMXidCombined = regexp.MustCompile(RegexNVRMXidCombined)
33-
compiledRegexNVRMXidExtended = regexp.MustCompile(RegexNVRMXidExtended)
44+
compiledRegexNVRMXidCombined = regexp.MustCompile(RegexNVRMXidCombined)
45+
compiledRegexNVRMXidExtended = regexp.MustCompile(RegexNVRMXidExtended)
46+
compiledRegexNVRMFallenOffBusMultiline = regexp.MustCompile(RegexNVRMFallenOffBusMultiline)
47+
compiledRegexNVRMFallenOffBusSingleLine = regexp.MustCompile(RegexNVRMFallenOffBusSingleLine)
3448
)
3549

3650
const (
@@ -197,18 +211,56 @@ func Match(line string) *XidError {
197211
}
198212

199213
extractedID, deviceUUID := ExtractNVRMXidInfo(line)
200-
if extractedID == 0 {
201-
return nil
214+
if extractedID != 0 {
215+
detail, ok := GetDetail(extractedID)
216+
if !ok {
217+
return nil
218+
}
219+
return &XidError{
220+
Xid: extractedID,
221+
DeviceUUID: deviceUUID,
222+
Detail: detail,
223+
}
202224
}
203-
detail, ok := GetDetail(extractedID)
204-
if !ok {
205-
return nil
225+
226+
if xid, fallbackDeviceUUID := extractFallenOffBusXidInfo(line); xid != 0 {
227+
// NVIDIA docs map "GPU has fallen off the bus" to XID 79 even when newer
228+
// driver logs omit the explicit "Xid (...): 79" prefix.
229+
detail, ok := GetDetail(xid)
230+
if !ok {
231+
return nil
232+
}
233+
return &XidError{
234+
Xid: xid,
235+
DeviceUUID: fallbackDeviceUUID,
236+
Detail: detail,
237+
}
238+
}
239+
240+
return nil
241+
}
242+
243+
func extractFallenOffBusXidInfo(line string) (int, string) {
244+
if match := compiledRegexNVRMFallenOffBusSingleLine.FindStringSubmatch(line); len(match) == 2 {
245+
return 79, normalizePCIBDF(match[1])
246+
}
247+
248+
if match := compiledRegexNVRMFallenOffBusMultiline.FindStringSubmatch(line); len(match) == 2 {
249+
return 79, normalizePCIBDF(match[1])
250+
}
251+
252+
return 0, ""
253+
}
254+
255+
func normalizePCIBDF(deviceUUID string) string {
256+
normalized := strings.TrimSpace(strings.TrimPrefix(deviceUUID, "PCI:"))
257+
if strings.Count(normalized, ":") == 1 {
258+
normalized = "0000:" + normalized
206259
}
207-
return &XidError{
208-
Xid: extractedID,
209-
DeviceUUID: deviceUUID,
210-
Detail: detail,
260+
if normalized == "" {
261+
return ""
211262
}
263+
return "PCI:" + normalized
212264
}
213265

214266
// MessageToInject represents a synthetic kernel message snippet and its log priority.

components/accelerator/nvidia/xid/kmsg_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,34 @@ func TestMatch(t *testing.T) {
118118
expectedXid: 79,
119119
expectedDevice: "PCI:0000:05:00",
120120
},
121+
{
122+
name: "multiline fallen-off-bus fallback without explicit Xid",
123+
input: "NVRM: The NVIDIA GPU 0000:18:00.0\n" +
124+
"NVRM: (PCI ID: 10de:2901) installed in this system has\n" +
125+
"NVRM: fallen off the bus and is not responding to commands.",
126+
expectNil: false,
127+
expectedXid: 79,
128+
expectedDevice: "PCI:0000:18:00",
129+
},
130+
{
131+
name: "single-line fallen-off-bus fallback without explicit Xid",
132+
input: "NVRM: GPU 0000:29:00.0: GPU has fallen off the bus.",
133+
expectNil: false,
134+
expectedXid: 79,
135+
expectedDevice: "PCI:0000:29:00",
136+
},
137+
{
138+
name: "single-line fallen-off-bus fallback normalizes missing PCI domain",
139+
input: "NVRM: GPU 18:00.0: GPU has fallen off the bus.",
140+
expectNil: false,
141+
expectedXid: 79,
142+
expectedDevice: "PCI:0000:18:00",
143+
},
144+
{
145+
name: "fallen-off-bus text without valid BDF is ignored",
146+
input: "NVRM: The NVIDIA GPU invalid-bdf.0\nNVRM: fallen off the bus and is not responding to commands.",
147+
expectNil: true,
148+
},
121149
{
122150
name: "valid XID error without PCI prefix",
123151
input: "[...] NVRM: Xid (0000:03:00): 14, Channel 00000001",
@@ -257,6 +285,74 @@ func TestMatchDmesgWithXid119(t *testing.T) {
257285
}
258286
}
259287

288+
func TestMatchReturnsNilWhenXidDetailMissing(t *testing.T) {
289+
t.Parallel()
290+
291+
result := Match("NVRM: Xid (PCI:0000:05:00): 99999, unknown error")
292+
require.Nil(t, result)
293+
}
294+
295+
func TestMatchReturnsNilWhenFallbackDetailMissing(t *testing.T) {
296+
originalDetails := details
297+
298+
clonedDetails := make(map[int]Detail, len(details))
299+
for xid, detail := range details {
300+
clonedDetails[xid] = detail
301+
}
302+
delete(clonedDetails, 79)
303+
details = clonedDetails
304+
305+
t.Cleanup(func() {
306+
details = originalDetails
307+
})
308+
309+
result := Match("NVRM: GPU 0000:29:00.0: GPU has fallen off the bus.")
310+
require.Nil(t, result)
311+
}
312+
313+
func TestNormalizePCIBDF(t *testing.T) {
314+
t.Parallel()
315+
316+
tests := []struct {
317+
name string
318+
input string
319+
expected string
320+
}{
321+
{
322+
name: "already normalized",
323+
input: "PCI:0000:29:00",
324+
expected: "PCI:0000:29:00",
325+
},
326+
{
327+
name: "adds domain when missing",
328+
input: "18:00",
329+
expected: "PCI:0000:18:00",
330+
},
331+
{
332+
name: "trims whitespace and prefix",
333+
input: "PCI:0000:18:00 ",
334+
expected: "PCI:0000:18:00",
335+
},
336+
{
337+
name: "empty input",
338+
input: "",
339+
expected: "",
340+
},
341+
{
342+
name: "empty after PCI prefix removal",
343+
input: "PCI:",
344+
expected: "",
345+
},
346+
}
347+
348+
for _, tt := range tests {
349+
tt := tt
350+
t.Run(tt.name, func(t *testing.T) {
351+
assert.Equal(t, tt.expected, normalizePCIBDF(tt.input))
352+
})
353+
}
354+
}
355+
260356
func TestGetMessageToInject(t *testing.T) {
261357
t.Parallel()
262358

docs/INSTALL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ curl -fsSL https://pkg.gpud.dev/install.sh | sh
77
gpud up
88
```
99

10-
To specify a version
10+
To install the latest published version explicitly:
1111

1212
```bash
13-
curl -fsSL https://pkg.gpud.dev/install.sh | sh -s v0.10.0
13+
curl -fsSL https://pkg.gpud.dev/install.sh | sh -s $(curl -fsSL https://pkg.gpud.dev/unstable_latest.txt)
1414
```
1515

1616
Then open [localhost:15132](https://localhost:15132) for the local web UI.

docs/TUTORIALS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ What is [GPUd](https://github.com/leptonai/gpud)? GPUd is designed to ensure GPU
77
*(see [Install GPUd](./INSTALL.md) for more and [release page](https://github.com/leptonai/gpud/releases) for latest versions)*
88

99
```bash
10-
curl -fsSL https://pkg.gpud.dev/install.sh | sh -s v0.9.1
10+
curl -fsSL https://pkg.gpud.dev/install.sh | sh -s $(curl -fsSL https://pkg.gpud.dev/unstable_latest.txt)
1111

1212
gpud --version
1313
```
@@ -34,7 +34,7 @@ sudo systemctl stop gpud.service
3434
sudo rm -f /usr/sbin/gpud
3535
sudo rm -f /usr/local/bin/gpud
3636

37-
curl -fsSL https://pkg.gpud.dev/install.sh | sh -s v0.10.0
37+
curl -fsSL https://pkg.gpud.dev/install.sh | sh -s $(curl -fsSL https://pkg.gpud.dev/unstable_latest.txt)
3838
which gpud
3939

4040
# (optional) if you already installed an old GPUd

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/leptonai/gpud
22

3-
go 1.24.7
3+
go 1.25.7
44

55
require (
66
github.com/NVIDIA/go-nvlib v0.8.1

0 commit comments

Comments
 (0)