|
3 | 3 | package cudaverify |
4 | 4 |
|
5 | 5 | import ( |
| 6 | + "bytes" |
6 | 7 | "context" |
7 | 8 | "flag" |
8 | 9 | "math" |
9 | 10 | "os" |
10 | 11 | "testing" |
11 | 12 | "time" |
| 13 | + "unsafe" |
12 | 14 |
|
| 15 | + "github.com/cilium/ebpf/perf" |
13 | 16 | "github.com/stretchr/testify/require" |
14 | 17 |
|
15 | 18 | "go.opentelemetry.io/ebpf-profiler/interpreter" |
| 19 | + "go.opentelemetry.io/ebpf-profiler/interpreter/gpu" |
16 | 20 | "go.opentelemetry.io/ebpf-profiler/libpf" |
17 | 21 | "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" |
18 | 22 | "go.opentelemetry.io/ebpf-profiler/tracer" |
@@ -203,3 +207,146 @@ func TestCUDAVerifierMultiProbe(t *testing.T) { |
203 | 207 |
|
204 | 208 | t.Log("MultiProbe: all CUDA eBPF programs passed the BPF verifier") |
205 | 209 | } |
| 210 | + |
| 211 | +// runEndToEnd is the shared implementation for end-to-end CUDA USDT probe tests. |
| 212 | +// It loads eBPF programs, attaches probes, simulates kernel launches via the mock |
| 213 | +// CUPTI layer, and verifies that timing events arrive on the cuda_timing_events |
| 214 | +// perf buffer with expected data. |
| 215 | +func runEndToEnd(t *testing.T, multiProbe bool) { |
| 216 | + t.Helper() |
| 217 | + |
| 218 | + probes := parseProbes(t) |
| 219 | + tr, ebpfHandler, cancel := createTracer(t) |
| 220 | + defer tr.Close() |
| 221 | + defer cancel() |
| 222 | + |
| 223 | + if !multiProbe { |
| 224 | + noMulti := false |
| 225 | + util.SetTestOnlyMultiUprobeSupport(&noMulti) |
| 226 | + defer util.SetTestOnlyMultiUprobeSupport(nil) |
| 227 | + } |
| 228 | + |
| 229 | + cookies, progNames := buildCookiesAndProgNames(probes) |
| 230 | + |
| 231 | + // For multi-probe, set up the tail-call prog array. |
| 232 | + if multiProbe { |
| 233 | + for _, probe := range probes { |
| 234 | + if probe.Name == "activity_batch" { |
| 235 | + err := ebpfHandler.UpdateProgArray("cuda_progs", 0, "cuda_activity_batch_tail") |
| 236 | + require.NoError(t, err, "UpdateProgArray failed for cuda_activity_batch") |
| 237 | + break |
| 238 | + } |
| 239 | + } |
| 240 | + } |
| 241 | + |
| 242 | + // Determine multi-probe program name. |
| 243 | + multiProgName := "" |
| 244 | + if multiProbe { |
| 245 | + multiProgName = "cuda_probe" |
| 246 | + } |
| 247 | + |
| 248 | + lc, err := ebpfHandler.AttachUSDTProbes( |
| 249 | + libpf.PID(os.Getpid()), |
| 250 | + *soPath, |
| 251 | + multiProgName, |
| 252 | + probes, |
| 253 | + cookies, |
| 254 | + progNames, |
| 255 | + ) |
| 256 | + require.NoError(t, err, "AttachUSDTProbes failed") |
| 257 | + defer lc.Unload() |
| 258 | + |
| 259 | + // Set up perf reader on the cuda_timing_events map. |
| 260 | + timingMap := tr.GetEbpfMaps()["cuda_timing_events"] |
| 261 | + require.NotNil(t, timingMap, "cuda_timing_events map not found") |
| 262 | + |
| 263 | + reader, err := perf.NewReader(timingMap, 1024*1024) |
| 264 | + require.NoError(t, err, "perf.NewReader failed") |
| 265 | + defer reader.Close() |
| 266 | + |
| 267 | + // Load the .so and call InitializeInjection via mock CUPTI. |
| 268 | + rc := cInitParcaGPU(*soPath) |
| 269 | + require.Equal(t, 0, rc, "init_parcagpu failed") |
| 270 | + defer cCleanupParcaGPU() |
| 271 | + |
| 272 | + // Simulate a kernel launch (fires cuda_correlation USDT). |
| 273 | + cSimulateKernelLaunch(42) |
| 274 | + |
| 275 | + // Simulate buffer completion (fires kernel_executed + activity_batch USDTs). |
| 276 | + cSimulateBufferCompletion(42, 0, 7, "testKernel") |
| 277 | + |
| 278 | + // Poll perf reader for timing events. |
| 279 | + var events []gpu.CuptiTimingEvent |
| 280 | + deadline := time.After(5 * time.Second) |
| 281 | + var rec perf.Record |
| 282 | + |
| 283 | + for { |
| 284 | + // Set a read deadline so we don't block forever. |
| 285 | + reader.SetDeadline(time.Now().Add(200 * time.Millisecond)) |
| 286 | + err := reader.ReadInto(&rec) |
| 287 | + if err != nil { |
| 288 | + select { |
| 289 | + case <-deadline: |
| 290 | + goto done |
| 291 | + default: |
| 292 | + continue |
| 293 | + } |
| 294 | + } |
| 295 | + if rec.LostSamples != 0 || len(rec.RawSample) == 0 { |
| 296 | + continue |
| 297 | + } |
| 298 | + ev := (*gpu.CuptiTimingEvent)(unsafe.Pointer(&rec.RawSample[0])) |
| 299 | + events = append(events, *ev) |
| 300 | + t.Logf("Received timing event: pid=%d id=%d dev=%d stream=%d kernel=%s", |
| 301 | + ev.Pid, ev.Id, ev.Dev, ev.Stream, |
| 302 | + string(ev.KernelName[:bytes.IndexByte(ev.KernelName[:], 0)])) |
| 303 | + } |
| 304 | +done: |
| 305 | + |
| 306 | + require.NotEmpty(t, events, "no timing events received from cuda_timing_events perf buffer") |
| 307 | + |
| 308 | + // Verify at least one event matches our simulated kernel. |
| 309 | + found := false |
| 310 | + for _, ev := range events { |
| 311 | + nameBytes := ev.KernelName[:] |
| 312 | + if idx := bytes.IndexByte(nameBytes, 0); idx >= 0 { |
| 313 | + nameBytes = nameBytes[:idx] |
| 314 | + } |
| 315 | + if ev.Id == 42 && ev.Dev == 0 && ev.Stream == 7 && |
| 316 | + string(nameBytes) == "testKernel" { |
| 317 | + found = true |
| 318 | + break |
| 319 | + } |
| 320 | + } |
| 321 | + require.True(t, found, |
| 322 | + "expected timing event with correlation_id=42, device_id=0, stream_id=7, kernel_name=testKernel; got %+v", events) |
| 323 | +} |
| 324 | + |
| 325 | +// TestCUDAEndToEndSingleShot verifies that CUDA USDT probes fire correctly |
| 326 | +// using individual per-probe attachment (kernel 5.15+). |
| 327 | +func TestCUDAEndToEndSingleShot(t *testing.T) { |
| 328 | + if os.Getuid() != 0 { |
| 329 | + t.Skip("requires root to load eBPF programs") |
| 330 | + } |
| 331 | + if !util.HasBpfGetAttachCookie() { |
| 332 | + t.Skip("requires kernel support for bpf_get_attach_cookie (5.15+)") |
| 333 | + } |
| 334 | + |
| 335 | + runEndToEnd(t, false) |
| 336 | +} |
| 337 | + |
| 338 | +// TestCUDAEndToEndMultiProbe verifies that CUDA USDT probes fire correctly |
| 339 | +// using multi-uprobe attachment with tail calls (kernel 6.6+). |
| 340 | +func TestCUDAEndToEndMultiProbe(t *testing.T) { |
| 341 | + if os.Getuid() != 0 { |
| 342 | + t.Skip("requires root to load eBPF programs") |
| 343 | + } |
| 344 | + if !util.HasBpfGetAttachCookie() { |
| 345 | + t.Skip("requires kernel support for bpf_get_attach_cookie (5.15+)") |
| 346 | + } |
| 347 | + if !util.HasMultiUprobeSupport() { |
| 348 | + t.Skip("requires kernel support for uprobe multi-attach (6.6+)") |
| 349 | + } |
| 350 | + |
| 351 | + runEndToEnd(t, true) |
| 352 | +} |
0 commit comments