Skip to content

Commit fed451a

Browse files
authored
[EBPF] gpu: add NVLink fabric topology tags (#53721)
### What does this PR do? Adds `gpu_fabric_cluster_uuid` and `gpu_fabric_clique_id` tags to GPU metrics when NVLink fabric registration is complete. ### Motivation Allow grouping GPU workloads by NVLink P2P topology. ### Describe how you validated your changes Unit tests added. Due to the API design of this function, exact mocking is not possible. We also tested on a NVlink-enabled node but it did not have a cluster UUID as it was just a single node. ### Additional Notes Co-authored-by: guillermo.julian <guillermo.julian@datadoghq.com>
1 parent 0196090 commit fed451a

13 files changed

Lines changed: 152 additions & 5 deletions

File tree

comp/core/tagger/collectors/workloadmeta_extract.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,10 @@ func ExtractGPUTags(gpu *workloadmeta.GPU, tagList *taglist.TagList) {
915915
tagList.AddLow(tags.GPUArchitecture, strings.ToLower(gpu.Architecture))
916916
tagList.AddLow(tags.GPUSlicingMode, gpu.SlicingMode())
917917
tagList.AddLow(tags.GPUPCIBusID, strings.ToLower(gpu.PCIBusID))
918+
if gpu.FabricClusterUUID != "" {
919+
tagList.AddLow(tags.GPUFabricClusterUUID, strings.ToLower(gpu.FabricClusterUUID))
920+
tagList.AddLow(tags.GPUFabricCliqueID, strconv.FormatUint(uint64(gpu.FabricCliqueID), 10))
921+
}
918922
if gpu.GPUType != "" {
919923
tagList.AddLow(tags.GPUType, strings.ToLower(gpu.GPUType))
920924
}

comp/core/tagger/collectors/workloadmeta_test.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3847,10 +3847,12 @@ func TestHandleGPU(t *testing.T) {
38473847
EntityMeta: workloadmeta.EntityMeta{
38483848
Name: entityID.ID,
38493849
},
3850-
Vendor: "nvidia",
3851-
Device: "tesla-v100",
3852-
GPUType: "v100",
3853-
PCIBusID: "0000:00:1e.0",
3850+
Vendor: "nvidia",
3851+
Device: "tesla-v100",
3852+
GPUType: "v100",
3853+
PCIBusID: "0000:00:1e.0",
3854+
FabricClusterUUID: "00112233-4455-6677-8899-aabbccddeeff",
3855+
FabricCliqueID: 7,
38543856
},
38553857
expected: []*types.TagInfo{
38563858
{
@@ -3866,6 +3868,8 @@ func TestHandleGPU(t *testing.T) {
38663868
"gpu_slicing_mode:none",
38673869
"gpu_parent_uuid:gpu-1234",
38683870
"gpu_pci_bus_id:0000:00:1e.0",
3871+
"gpu_fabric_cluster_uuid:00112233-4455-6677-8899-aabbccddeeff",
3872+
"gpu_fabric_clique_id:7",
38693873
},
38703874
StandardTags: []string{},
38713875
},

comp/core/tagger/tags/tags.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ const (
155155
GPUParentGPUUUID = "gpu_parent_uuid"
156156
// GPUPCIBusID is the tag for the PCI bus ID of the GPU
157157
GPUPCIBusID = "gpu_pci_bus_id"
158+
// GPUFabricClusterUUID is the tag for the NVLink fabric cluster UUID of the GPU
159+
GPUFabricClusterUUID = "gpu_fabric_cluster_uuid"
160+
// GPUFabricCliqueID is the tag for the NVLink fabric clique ID of the GPU
161+
GPUFabricCliqueID = "gpu_fabric_clique_id"
158162

159163
// KubeArgoRollout is the tag for the Argo Rollout name
160164
KubeArgoRollout = "kube_argo_rollout"

comp/core/workloadmeta/collectors/internal/nvml/nvml.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ func (c *collector) fillNVMLAttributes(gpuDeviceInfo *workloadmeta.GPU, device d
129129
gpuDeviceInfo.PCIBusID = pciBusIDFromNVMLInfo(pciInfo)
130130
}
131131

132+
fabricInfo, err := physicalDevice.GetGpuFabricInfo()
133+
if err == nil {
134+
if clusterUUID, cliqueID, ok := fabricInfoToTags(fabricInfo); ok {
135+
gpuDeviceInfo.FabricClusterUUID = clusterUUID
136+
gpuDeviceInfo.FabricCliqueID = cliqueID
137+
}
138+
}
139+
132140
// Do not generate errors for vGPU devices, we already know that they don't support max clock info
133141
if virtMode != nvml.GPU_VIRTUALIZATION_MODE_VGPU {
134142
maxSMClock, err := physicalDevice.GetMaxClockInfo(nvml.CLOCK_SM)
@@ -163,6 +171,20 @@ func pciBusIDFromNVMLInfo(pciInfo nvml.PciInfo) string {
163171
return strings.ToLower(fmt.Sprintf("%04x:%02x:%02x.0", pciInfo.Domain, pciInfo.Bus, pciInfo.Device))
164172
}
165173

174+
func fabricClusterUUIDFromNVMLInfo(clusterUUID [16]uint8) string {
175+
return fmt.Sprintf("%x-%x-%x-%x-%x", clusterUUID[0:4], clusterUUID[4:6], clusterUUID[6:8], clusterUUID[8:10], clusterUUID[10:16])
176+
}
177+
178+
func fabricInfoToTags(fabricInfo nvml.GpuFabricInfo_v2) (string, uint32, bool) {
179+
if fabricInfo.State != nvml.GPU_FABRIC_STATE_COMPLETED ||
180+
nvml.Return(fabricInfo.Status) != nvml.SUCCESS ||
181+
fabricInfo.ClusterUuid == [16]uint8{} {
182+
return "", 0, false
183+
}
184+
185+
return fabricClusterUUIDFromNVMLInfo(fabricInfo.ClusterUuid), fabricInfo.CliqueId, true
186+
}
187+
166188
func (c *collector) fillProcesses(gpuDeviceInfo *workloadmeta.GPU, device ddnvml.Device) {
167189
seenPIDs := make(map[int]struct{})
168190
procs, err := device.GetComputeRunningProcesses()

comp/core/workloadmeta/collectors/internal/nvml/nvml_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func TestPull(t *testing.T) {
6464
require.ElementsMatch(t, expectedGPUActivePIDs, gpu.ActivePIDs)
6565
require.Equal(t, "none", gpu.VirtualizationMode)
6666
require.Equal(t, "0000:00:1e.0", gpu.PCIBusID)
67+
require.Empty(t, gpu.FabricClusterUUID)
6768
}
6869

6970
for _, uuid := range testutil.GPUUUIDs {
@@ -77,6 +78,71 @@ func TestPull(t *testing.T) {
7778
}
7879
}
7980

81+
func TestFabricInfoToTags(t *testing.T) {
82+
clusterUUID := [16]uint8{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
83+
tests := []struct {
84+
name string
85+
fabricInfo nvml.GpuFabricInfo_v2
86+
expectedClusterID string
87+
expectedCliqueID uint32
88+
expectedAvailable bool
89+
}{
90+
{
91+
name: "completed fabric with cluster UUID",
92+
fabricInfo: nvml.GpuFabricInfo_v2{
93+
State: nvml.GPU_FABRIC_STATE_COMPLETED,
94+
Status: uint32(nvml.SUCCESS),
95+
CliqueId: 42,
96+
ClusterUuid: clusterUUID,
97+
},
98+
expectedClusterID: "00112233-4455-6677-8899-aabbccddeeff",
99+
expectedCliqueID: 42,
100+
expectedAvailable: true,
101+
},
102+
{
103+
name: "fabric initialization incomplete",
104+
fabricInfo: nvml.GpuFabricInfo_v2{
105+
State: nvml.GPU_FABRIC_STATE_IN_PROGRESS,
106+
Status: uint32(nvml.SUCCESS),
107+
CliqueId: 42,
108+
ClusterUuid: clusterUUID,
109+
},
110+
},
111+
{
112+
name: "fabric status failed",
113+
fabricInfo: nvml.GpuFabricInfo_v2{
114+
State: nvml.GPU_FABRIC_STATE_COMPLETED,
115+
Status: uint32(nvml.ERROR_UNKNOWN),
116+
CliqueId: 42,
117+
ClusterUuid: clusterUUID,
118+
},
119+
},
120+
{
121+
name: "cluster UUID is unavailable",
122+
fabricInfo: nvml.GpuFabricInfo_v2{
123+
State: nvml.GPU_FABRIC_STATE_COMPLETED,
124+
Status: uint32(nvml.SUCCESS),
125+
CliqueId: 42,
126+
},
127+
},
128+
}
129+
130+
for _, tt := range tests {
131+
t.Run(tt.name, func(t *testing.T) {
132+
clusterID, cliqueID, available := fabricInfoToTags(tt.fabricInfo)
133+
require.Equal(t, tt.expectedClusterID, clusterID)
134+
require.Equal(t, tt.expectedCliqueID, cliqueID)
135+
require.Equal(t, tt.expectedAvailable, available)
136+
})
137+
}
138+
}
139+
140+
func TestFabricClusterUUIDFromNVMLInfo(t *testing.T) {
141+
clusterUUID := [16]uint8{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
142+
143+
require.Equal(t, "00112233-4455-6677-8899-aabbccddeeff", fabricClusterUUIDFromNVMLInfo(clusterUUID))
144+
}
145+
80146
func TestPCIBusIDFromNVMLInfo(t *testing.T) {
81147
tests := []struct {
82148
name string

comp/core/workloadmeta/def/types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2315,6 +2315,14 @@ type GPU struct {
23152315
// PCIBusID is the PCI bus ID of the GPU in domain:bus:device.function format.
23162316
PCIBusID string
23172317

2318+
// FabricClusterUUID identifies the NVLink fabric cluster that contains the GPU.
2319+
// Empty when the GPU is not registered with a fabric cluster.
2320+
FabricClusterUUID string
2321+
2322+
// FabricCliqueID identifies the P2P clique within the NVLink fabric cluster.
2323+
// It is meaningful only when FabricClusterUUID is set.
2324+
FabricCliqueID uint32
2325+
23182326
// DeviceType identifies if this is a physical or virtual device (e.g. MIG)
23192327
DeviceType GPUDeviceType
23202328

pkg/gpu/safenvml/device.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ type SafeDevice interface {
5252
GetGpuInstanceId() (int, error)
5353
// GetGpuInstanceProfileInfo returns the profile info for the given GPU instance profile ID
5454
GetGpuInstanceProfileInfo(profile int) (nvml.GpuInstanceProfileInfo, error)
55+
// GetGpuFabricInfo returns the NVLink fabric information for the device.
56+
GetGpuFabricInfo() (nvml.GpuFabricInfo_v2, error)
5557
// GetIndex returns the index of the device
5658
GetIndex() (int, error)
5759
// GetMaxClockInfo returns the maximum clock speed for the given clock type

pkg/gpu/safenvml/device_impl.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,17 @@ func (d *safeDeviceImpl) GetGpuInstanceProfileInfo(profile int) (nvml.GpuInstanc
139139
return info, NewNvmlAPIErrorOrNil("GetGpuInstanceProfileInfo", ret)
140140
}
141141

142+
func (d *safeDeviceImpl) GetGpuFabricInfo() (nvml.GpuFabricInfo_v2, error) {
143+
if err := d.lib.lookup(toNativeName("GetGpuFabricInfoV")); err != nil {
144+
return nvml.GpuFabricInfo_v2{}, err
145+
}
146+
info, ret := d.nvmlDevice.GetGpuFabricInfoV().V2()
147+
if err := NewNvmlAPIErrorOrNil("GetGpuFabricInfoV", ret); err != nil {
148+
return nvml.GpuFabricInfo_v2{}, err
149+
}
150+
return info, nil
151+
}
152+
142153
func (d *safeDeviceImpl) GetIndex() (int, error) {
143154
if err := d.lib.lookup(toNativeName("GetIndex")); err != nil {
144155
return 0, err

pkg/gpu/safenvml/device_impl_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,16 @@ func TestDeviceSafeMethodSuccess(t *testing.T) {
141141
require.NoError(t, err)
142142
require.Equal(t, testutil.DefaultGpuCores, cores)
143143
}
144+
145+
func TestGetGpuFabricInfoRequiresVersionedAPISymbol(t *testing.T) {
146+
symbols := maps.Clone(allSymbols)
147+
delete(symbols, toNativeName("GetGpuFabricInfoV"))
148+
149+
device := &safeDeviceImpl{
150+
lib: &safeNvml{capabilities: symbols},
151+
}
152+
153+
_, err := device.GetGpuFabricInfo()
154+
require.Error(t, err)
155+
require.True(t, IsUnsupported(err))
156+
}

pkg/gpu/safenvml/lib.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ func getNonCriticalAPIs() []string {
7171
toNativeName("GetFanSpeed_v2"),
7272
toNativeName("GetFieldValues"),
7373
"nvmlDeviceReadWritePRM_v1",
74+
toNativeName("GetGpuFabricInfoV"),
7475
toNativeName("GetGpuInstanceId"),
7576
toNativeName("GetGpuInstanceProfileInfo"),
7677
toNativeName("GetMaxClockInfo"),

0 commit comments

Comments
 (0)