Skip to content

Commit 8f0ecee

Browse files
committed
End to end tests for gpu profiler
1 parent 7698ea6 commit 8f0ecee

3 files changed

Lines changed: 503 additions & 0 deletions

File tree

test/cudaverify/cuda_verifier_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@
33
package cudaverify
44

55
import (
6+
"bytes"
67
"context"
78
"flag"
89
"math"
910
"os"
1011
"testing"
1112
"time"
13+
"unsafe"
1214

15+
"github.com/cilium/ebpf/perf"
1316
"github.com/stretchr/testify/require"
1417

1518
"go.opentelemetry.io/ebpf-profiler/interpreter"
19+
"go.opentelemetry.io/ebpf-profiler/interpreter/gpu"
1620
"go.opentelemetry.io/ebpf-profiler/libpf"
1721
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
1822
"go.opentelemetry.io/ebpf-profiler/tracer"
@@ -203,3 +207,146 @@ func TestCUDAVerifierMultiProbe(t *testing.T) {
203207

204208
t.Log("MultiProbe: all CUDA eBPF programs passed the BPF verifier")
205209
}
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

Comments
 (0)