@@ -83,6 +83,7 @@ struct cpu_and_wall_time_worker_state {
83
83
bool allocation_counting_enabled ;
84
84
bool no_signals_workaround_enabled ;
85
85
bool dynamic_sampling_rate_enabled ;
86
+ int allocation_sample_every ; // Temporarily used for development/testing of allocation profiling
86
87
VALUE self_instance ;
87
88
VALUE thread_context_collector_instance ;
88
89
VALUE idle_sampling_helper_instance ;
@@ -150,7 +151,8 @@ static VALUE _native_initialize(
150
151
VALUE idle_sampling_helper_instance ,
151
152
VALUE allocation_counting_enabled ,
152
153
VALUE no_signals_workaround_enabled ,
153
- VALUE dynamic_sampling_rate_enabled
154
+ VALUE dynamic_sampling_rate_enabled ,
155
+ VALUE allocation_sample_every
154
156
);
155
157
static void cpu_and_wall_time_worker_typed_data_mark (void * state_ptr );
156
158
static VALUE _native_sampling_loop (VALUE self , VALUE instance );
@@ -183,9 +185,10 @@ static void grab_gvl_and_sample(void);
183
185
static void reset_stats (struct cpu_and_wall_time_worker_state * state );
184
186
static void sleep_for (uint64_t time_ns );
185
187
static VALUE _native_allocation_count (DDTRACE_UNUSED VALUE self );
186
- static void on_newobj_event (DDTRACE_UNUSED VALUE tracepoint_data , DDTRACE_UNUSED void * unused );
188
+ static void on_newobj_event (VALUE tracepoint_data , DDTRACE_UNUSED void * unused );
187
189
static void disable_tracepoints (struct cpu_and_wall_time_worker_state * state );
188
190
static VALUE _native_with_blocked_sigprof (DDTRACE_UNUSED VALUE self );
191
+ static VALUE rescued_sample_allocation (VALUE tracepoint_data );
189
192
190
193
// Note on sampler global state safety:
191
194
//
@@ -223,7 +226,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
223
226
// https://bugs.ruby-lang.org/issues/18007 for a discussion around this.
224
227
rb_define_alloc_func (collectors_cpu_and_wall_time_worker_class , _native_new );
225
228
226
- rb_define_singleton_method (collectors_cpu_and_wall_time_worker_class , "_native_initialize" , _native_initialize , 7 );
229
+ rb_define_singleton_method (collectors_cpu_and_wall_time_worker_class , "_native_initialize" , _native_initialize , 8 );
227
230
rb_define_singleton_method (collectors_cpu_and_wall_time_worker_class , "_native_sampling_loop" , _native_sampling_loop , 1 );
228
231
rb_define_singleton_method (collectors_cpu_and_wall_time_worker_class , "_native_stop" , _native_stop , 2 );
229
232
rb_define_singleton_method (collectors_cpu_and_wall_time_worker_class , "_native_reset_after_fork" , _native_reset_after_fork , 1 );
@@ -261,6 +264,7 @@ static VALUE _native_new(VALUE klass) {
261
264
state -> allocation_counting_enabled = false;
262
265
state -> no_signals_workaround_enabled = false;
263
266
state -> dynamic_sampling_rate_enabled = true;
267
+ state -> allocation_sample_every = 0 ;
264
268
state -> thread_context_collector_instance = Qnil ;
265
269
state -> idle_sampling_helper_instance = Qnil ;
266
270
state -> owner_thread = Qnil ;
@@ -287,12 +291,14 @@ static VALUE _native_initialize(
287
291
VALUE idle_sampling_helper_instance ,
288
292
VALUE allocation_counting_enabled ,
289
293
VALUE no_signals_workaround_enabled ,
290
- VALUE dynamic_sampling_rate_enabled
294
+ VALUE dynamic_sampling_rate_enabled ,
295
+ VALUE allocation_sample_every
291
296
) {
292
297
ENFORCE_BOOLEAN (gc_profiling_enabled );
293
298
ENFORCE_BOOLEAN (allocation_counting_enabled );
294
299
ENFORCE_BOOLEAN (no_signals_workaround_enabled );
295
300
ENFORCE_BOOLEAN (dynamic_sampling_rate_enabled );
301
+ ENFORCE_TYPE (allocation_sample_every , T_FIXNUM );
296
302
297
303
struct cpu_and_wall_time_worker_state * state ;
298
304
TypedData_Get_Struct (self_instance , struct cpu_and_wall_time_worker_state , & cpu_and_wall_time_worker_typed_data , state );
@@ -301,6 +307,12 @@ static VALUE _native_initialize(
301
307
state -> allocation_counting_enabled = (allocation_counting_enabled == Qtrue );
302
308
state -> no_signals_workaround_enabled = (no_signals_workaround_enabled == Qtrue );
303
309
state -> dynamic_sampling_rate_enabled = (dynamic_sampling_rate_enabled == Qtrue );
310
+ state -> allocation_sample_every = NUM2INT (allocation_sample_every );
311
+
312
+ if (state -> allocation_sample_every < 0 ) {
313
+ rb_raise (rb_eArgError , "Unexpected value for allocation_sample_every: %d. This value must be >= 0." , state -> allocation_sample_every );
314
+ }
315
+
304
316
state -> thread_context_collector_instance = enforce_thread_context_collector_instance (thread_context_collector_instance );
305
317
state -> idle_sampling_helper_instance = idle_sampling_helper_instance ;
306
318
state -> gc_tracepoint = rb_tracepoint_new (Qnil , RUBY_INTERNAL_EVENT_GC_ENTER | RUBY_INTERNAL_EVENT_GC_EXIT , on_gc_event , NULL /* unused */ );
@@ -880,7 +892,7 @@ static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self) {
880
892
881
893
// Implements memory-related profiling events. This function is called by Ruby via the `object_allocation_tracepoint`
882
894
// when the RUBY_INTERNAL_EVENT_NEWOBJ event is triggered.
883
- static void on_newobj_event (DDTRACE_UNUSED VALUE tracepoint_data , DDTRACE_UNUSED void * unused ) {
895
+ static void on_newobj_event (VALUE tracepoint_data , DDTRACE_UNUSED void * unused ) {
884
896
// Update thread-local allocation count
885
897
if (RB_UNLIKELY (allocation_count == UINT64_MAX )) {
886
898
allocation_count = 0 ;
@@ -907,7 +919,12 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE tracepoint_data, DDTRACE_UNUSED
907
919
// defined as not being able to allocate) sets this.
908
920
state -> during_sample = true;
909
921
910
- // TODO: Sampling goes here (calling into `thread_context_collector_sample_allocation`)
922
+ // TODO: This is a placeholder sampling decision strategy. We plan to replace it with a better one soon (e.g. before
923
+ // beta), and having something here allows us to test the rest of feature, sampling decision aside.
924
+ if (state -> allocation_sample_every > 0 && ((allocation_count % state -> allocation_sample_every ) == 0 )) {
925
+ // Rescue against any exceptions that happen during sampling
926
+ safely_call (rescued_sample_allocation , tracepoint_data , state -> self_instance );
927
+ }
911
928
912
929
state -> during_sample = false;
913
930
}
@@ -929,3 +946,18 @@ static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self) {
929
946
return result ;
930
947
}
931
948
}
949
+
950
+ static VALUE rescued_sample_allocation (VALUE tracepoint_data ) {
951
+ struct cpu_and_wall_time_worker_state * state = active_sampler_instance_state ; // Read from global variable, see "sampler global state safety" note above
952
+
953
+ // This should not happen in a normal situation because on_newobj_event already checked for this, but just in case...
954
+ if (state == NULL ) return Qnil ;
955
+
956
+ rb_trace_arg_t * data = rb_tracearg_from_tracepoint (tracepoint_data );
957
+ VALUE new_object = rb_tracearg_object (data );
958
+
959
+ thread_context_collector_sample_allocation (state -> thread_context_collector_instance , state -> allocation_sample_every , new_object );
960
+
961
+ // Return a dummy VALUE because we're called from rb_rescue2 which requires it
962
+ return Qnil ;
963
+ }
0 commit comments