@@ -39,10 +39,15 @@ struct kernel_timing {
3939// Per-CPU scratch space for large structs that exceed the BPF 512-byte stack limit.
4040// Used by cuda_kernel_exec and cuda_activity_batch (mutually exclusive on same CPU).
4141#define MAX_BATCH_SIZE 128
42+ #define PTR_BATCH 16
4243struct cuda_scratch {
4344 struct kernel_timing timing ;
4445 struct cupti_activity_kernel5 rec ;
45- u64 ptrs [MAX_BATCH_SIZE ];
46+ // Pre-parsed activity_batch USDT args, set by cuda_probe before tail call.
47+ // bpf_get_attach_cookie does not return the correct cookie after bpf_tail_call,
48+ // so we parse args in cuda_probe and pass them via scratch.
49+ u64 ab_ptrs_base ;
50+ u32 ab_num_activities ;
4651};
4752
4853bpf_map_def SEC ("maps" ) cuda_scratch_heap = {
@@ -100,6 +105,8 @@ int BPF_USDT(
100105 timing -> kernel_name [3 ] = '\0' ;
101106 }
102107
108+ DEBUG_PRINT ("cuda_kernel_exec: pid=%u corr_id=%u dev=%u" , pid , correlation_id , device_id );
109+
103110 bpf_perf_event_output (ctx , & cuda_timing_events , BPF_F_CURRENT_CPU , timing , sizeof (* timing ));
104111
105112 return 0 ;
@@ -119,64 +126,100 @@ int BPF_USDT(cuda_activity_batch, u64 ptrs_base, u32 num_activities)
119126 struct kernel_timing * timing = & scratch -> timing ;
120127 struct cupti_activity_kernel5 * rec = & scratch -> rec ;
121128
129+ DEBUG_PRINT ("cuda_activity_batch: pid=%u num=%u" , pid , (u32 )num_activities );
130+ DEBUG_PRINT ("cuda_activity_batch: ptrs_base=0x%llx" , ptrs_base );
131+
122132 if (num_activities > MAX_BATCH_SIZE ) {
123133 num_activities = MAX_BATCH_SIZE ;
124134 }
125135
126- // Read all activity pointers into scratch space in one shot.
127- if (bpf_probe_read_user (scratch -> ptrs , num_activities * sizeof (u64 ), (void * )ptrs_base ) != 0 ) {
128- return 0 ;
129- }
136+ // Stack-local pointer batch — small enough for the BPF stack.
137+ u64 ptrs [PTR_BATCH ] = {};
130138
131- for (u32 i = 0 ; i < num_activities && i < MAX_BATCH_SIZE ; i ++ ) {
132- u64 rec_ptr = scratch -> ptrs [i & (MAX_BATCH_SIZE - 1 )];
133- if (rec_ptr == 0 ) {
134- continue ;
139+ // Nested loop: outer iterates over batches of PTR_BATCH pointers,
140+ // inner processes each pointer in the batch. This keeps the verifier's
141+ // jump-sequence count well under BPF_COMPLEXITY_LIMIT_JMP_SEQ (8192).
142+ for (u32 batch = 0 ; batch < MAX_BATCH_SIZE / PTR_BATCH ; batch ++ ) {
143+ u32 base = batch * PTR_BATCH ;
144+ if (base >= num_activities ) {
145+ break ;
135146 }
136147
137- // Read the full activity record and filter by kind.
138- if (bpf_probe_read_user (rec , sizeof (* rec ), (void * )rec_ptr ) != 0 ) {
139- continue ;
140- }
141- if (
142- rec -> kind != CUPTI_ACTIVITY_KIND_KERNEL &&
143- rec -> kind != CUPTI_ACTIVITY_KIND_CONCURRENT_KERNEL ) {
144- continue ;
148+ if (bpf_probe_read_user (ptrs , sizeof (ptrs ), (void * )(ptrs_base + base * sizeof (u64 ))) != 0 ) {
149+ break ;
145150 }
146151
147- timing -> pid = pid ;
148- timing -> correlation_id = rec -> correlation_id ;
149- timing -> start = rec -> start ;
150- timing -> end = rec -> end ;
151- timing -> graph_node_id = rec -> graph_node_id ;
152- timing -> device_id = rec -> device_id ;
153- timing -> stream_id = rec -> stream_id ;
154- timing -> graph_id = rec -> graph_id ;
155-
156- const char * name = (const char * )rec -> name_ptr ;
157- int chars =
158- bpf_probe_read_user_str ((char * )& timing -> kernel_name , sizeof (timing -> kernel_name ), name );
159- if (chars <= 0 ) {
160- timing -> kernel_name [0 ] = 'e' ;
161- timing -> kernel_name [1 ] = 'r' ;
162- timing -> kernel_name [2 ] = 'r' ;
163- timing -> kernel_name [3 ] = '\0' ;
164- }
152+ for (u32 j = 0 ; j < PTR_BATCH ; j ++ ) {
153+ if (base + j >= num_activities ) {
154+ break ;
155+ }
156+
157+ u64 rec_ptr = ptrs [j ];
158+
159+ // Read the full activity record and filter by kind.
160+ if (bpf_probe_read_user (rec , sizeof (* rec ), (void * )rec_ptr ) != 0 ) {
161+ continue ;
162+ }
163+ if (
164+ rec -> kind != CUPTI_ACTIVITY_KIND_KERNEL &&
165+ rec -> kind != CUPTI_ACTIVITY_KIND_CONCURRENT_KERNEL ) {
166+ continue ;
167+ }
168+
169+ timing -> pid = pid ;
170+ timing -> correlation_id = rec -> correlation_id ;
171+ timing -> start = rec -> start ;
172+ timing -> end = rec -> end ;
173+ timing -> graph_node_id = rec -> graph_node_id ;
174+ timing -> device_id = rec -> device_id ;
175+ timing -> stream_id = rec -> stream_id ;
176+ timing -> graph_id = rec -> graph_id ;
177+
178+ const char * name = (const char * )rec -> name_ptr ;
179+ int chars =
180+ bpf_probe_read_user_str ((char * )& timing -> kernel_name , sizeof (timing -> kernel_name ), name );
181+ if (chars <= 0 ) {
182+ timing -> kernel_name [0 ] = 'e' ;
183+ timing -> kernel_name [1 ] = 'r' ;
184+ timing -> kernel_name [2 ] = 'r' ;
185+ timing -> kernel_name [3 ] = '\0' ;
186+ }
187+
188+ DEBUG_PRINT (
189+ "cuda_activity_batch: corr_id=%u kind=%u dev=%u" ,
190+ rec -> correlation_id ,
191+ rec -> kind ,
192+ rec -> device_id );
165193
166- bpf_perf_event_output (ctx , & cuda_timing_events , BPF_F_CURRENT_CPU , timing , sizeof (* timing ));
194+ bpf_perf_event_output (ctx , & cuda_timing_events , BPF_F_CURRENT_CPU , timing , sizeof (* timing ));
195+ }
167196 }
168197
169198 return 0 ;
170199}
171200
201+ // Tail-call entry point for cuda_activity_batch. Reads pre-parsed USDT args
202+ // from the scratch map (set by cuda_probe before bpf_tail_call) and forwards
203+ // them to the inline body generated by BPF_USDT.
204+ SEC ("usdt/cuda_activity_batch_tail" )
205+ int cuda_activity_batch_tail (struct pt_regs * ctx )
206+ {
207+ u32 zero = 0 ;
208+ struct cuda_scratch * scratch = bpf_map_lookup_elem (& cuda_scratch_heap , & zero );
209+ if (!scratch ) {
210+ return 0 ;
211+ }
212+ return ____cuda_activity_batch (ctx , scratch -> ab_ptrs_base , scratch -> ab_num_activities );
213+ }
214+
172215// Cookie values for the cuda_probe multi-probe dispatcher.
173216// Must match the cookie values set in cuda.go.
174217#define CUDA_PROG_CORRELATION 0
175218#define CUDA_PROG_KERNEL_EXEC 1
176219#define CUDA_PROG_ACTIVITY_BATCH 2
177220
178221// Tail-call prog array for cuda_probe. Contains a single entry at key 0
179- // for cuda_activity_batch , whose batch loop pushes past the BPF verifier's
222+ // for cuda_activity_batch_tail , whose batch loop pushes past the BPF verifier's
180223// BPF_COMPLEXITY_LIMIT_JMP_SEQ (8192) limit. cuda_correlation and
181224// cuda_kernel_exec are inlined directly in cuda_probe.
182225bpf_map_def SEC ("maps" ) cuda_progs = {
@@ -205,7 +248,19 @@ int cuda_probe(struct pt_regs *ctx)
205248 graph_id ,
206249 graph_node_id ,
207250 name );
208- case CUDA_PROG_ACTIVITY_BATCH : bpf_tail_call (ctx , & cuda_progs , 0 ); break ;
251+ case CUDA_PROG_ACTIVITY_BATCH : {
252+ // Parse USDT args before the tail call — bpf_get_attach_cookie does not
253+ // return the correct cookie after bpf_tail_call.
254+ u32 zero = 0 ;
255+ struct cuda_scratch * scratch = bpf_map_lookup_elem (& cuda_scratch_heap , & zero );
256+ if (!scratch ) {
257+ break ;
258+ }
259+ scratch -> ab_ptrs_base = (u64 )bpf_usdt_arg0 (ctx );
260+ scratch -> ab_num_activities = (u32 )bpf_usdt_arg1 (ctx );
261+ bpf_tail_call (ctx , & cuda_progs , 0 );
262+ break ;
263+ }
209264 default : DEBUG_PRINT ("cuda_probe: unknown cookie %u" , cookie ); break ;
210265 }
211266 return 0 ;
0 commit comments