datadog 2.41.0 → 2.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -2
  3. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +110 -61
  5. data/ext/datadog_profiling_native_extension/collectors_stack.c +11 -3
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +141 -103
  7. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +7 -3
  8. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -10
  9. data/ext/datadog_profiling_native_extension/extconf.rb +52 -93
  10. data/ext/datadog_profiling_native_extension/heap_recorder.c +338 -255
  11. data/ext/datadog_profiling_native_extension/heap_recorder.h +46 -31
  12. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -24
  13. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +509 -440
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +15 -3
  15. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  16. data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -79
  17. data/ext/datadog_profiling_native_extension/ruby_helpers.h +0 -7
  18. data/ext/datadog_profiling_native_extension/stack_recorder.c +93 -61
  19. data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
  20. data/ext/libdatadog_api/datadog_ruby_common.h +0 -10
  21. data/ext/libdatadog_api/di.c +10 -0
  22. data/ext/libdatadog_api/extconf.rb +3 -0
  23. data/ext/libdatadog_api/init.c +2 -0
  24. data/ext/libdatadog_api/otel_thread_context.c +232 -0
  25. data/ext/libdatadog_api/otel_thread_context.h +5 -0
  26. data/ext/libdatadog_extconf_helpers.rb +1 -1
  27. data/lib/datadog/appsec/assets/blocked.html +1 -108
  28. data/lib/datadog/core/configuration/components.rb +1 -0
  29. data/lib/datadog/core/crashtracking/component.rb +5 -1
  30. data/lib/datadog/data_streams/pathway_context.rb +20 -22
  31. data/lib/datadog/data_streams/processor.rb +31 -0
  32. data/lib/datadog/di/instrumenter.rb +41 -1
  33. data/lib/datadog/di/logger.rb +2 -2
  34. data/lib/datadog/di/probe.rb +9 -1
  35. data/lib/datadog/di/probe_notification_builder.rb +1 -0
  36. data/lib/datadog/di/remote.rb +3 -3
  37. data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
  38. data/lib/datadog/open_feature/exposures/event.rb +10 -3
  39. data/lib/datadog/open_feature/ext.rb +19 -0
  40. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
  41. data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
  42. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
  43. data/lib/datadog/open_feature/native_evaluator.rb +33 -6
  44. data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
  45. data/lib/datadog/open_feature/provider.rb +11 -2
  46. data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
  47. data/lib/datadog/opentelemetry/trace.rb +3 -0
  48. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -0
  49. data/lib/datadog/profiling/collectors/thread_context.rb +0 -4
  50. data/lib/datadog/profiling/component.rb +8 -16
  51. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
  52. data/lib/datadog/tracing/distributed/baggage.rb +0 -1
  53. data/lib/datadog/tracing/distributed/datadog.rb +3 -3
  54. data/lib/datadog/tracing/distributed/propagation.rb +3 -0
  55. data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
  56. data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
  57. data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
  58. data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
  59. data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
  60. data/lib/datadog/tracing/otel_thread_context.rb +30 -0
  61. data/lib/datadog/tracing/remote.rb +195 -27
  62. data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
  63. data/lib/datadog/tracing/trace_digest.rb +22 -4
  64. data/lib/datadog/tracing/trace_operation.rb +22 -10
  65. data/lib/datadog/tracing/tracer.rb +5 -5
  66. data/lib/datadog/version.rb +1 -1
  67. metadata +14 -8
  68. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
@@ -74,6 +74,11 @@
74
74
  #define THREAD_ID_LIMIT_CHARS 44 // Why 44? "#{2**64} (#{2**64})".size + 1 for \0
75
75
  #define THREAD_INVOKE_LOCATION_LIMIT_CHARS 512
76
76
  #define MISSING_TRACER_CONTEXT_KEY 0
77
+
78
+ // Used as markers in the code
79
+ #define OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED INT2FIX(1)
80
+ #define OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED INT2FIX(2)
81
+
77
82
  #define TIME_BETWEEN_GC_EVENTS_NS MILLIS_AS_NS(10)
78
83
  #define GVL_SUSPENDED ((uint64_t)1)
79
84
  #define GVL_RUNNING ((uint64_t)0)
@@ -106,7 +111,7 @@ static ID otel_fiber_context_storage_id; // id of :@opentelemetry_context in Rub
106
111
 
107
112
  // This is mutable and gets set last-writer-wins style by
108
113
  // `thread_context_collector_reset_all_per_thread_contexts`, which is called whenever
109
- // profiling is starting or restating.
114
+ // profiling is starting or restarting.
110
115
  //
111
116
  // Note: We must be careful to not change this value while the profiler is still running,
112
117
  // otherwise new threads can get the new value and cause profiling to stop with an
@@ -148,8 +153,9 @@ typedef struct {
148
153
  // Used to identify the main thread, to give it a fallback name
149
154
  VALUE main_thread;
150
155
  // Used when extracting trace identifiers from otel spans. Lazily initialized.
151
- // Qtrue serves as a marker we've not yet extracted it; when we try to extract it, we set it to an object if
152
- // successful and Qnil if not.
156
+ // * `OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED`: we've not needed it yet
157
+ // * `OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED`: a sample needed it, and it's waiting to be extracted
158
+ // * anything else: the extracted value, or Qnil if we tried to extract it and failed
153
159
  VALUE otel_current_span_key;
154
160
  // Used to enable native filenames in stack traces
155
161
  bool native_filenames_enabled;
@@ -159,8 +165,6 @@ typedef struct {
159
165
  st_table *native_filenames_cache;
160
166
  // Used to attribute overhead during sampling to this component
161
167
  VALUE overhead_filename;
162
- // Minimum duration of a "Waiting for GVL" period to trigger a sample
163
- uint32_t waiting_for_gvl_threshold_ns;
164
168
 
165
169
  struct stats {
166
170
  // Track how many regular samples we've taken. Does not include garbage collection samples.
@@ -169,6 +173,8 @@ typedef struct {
169
173
  unsigned int gc_samples;
170
174
  // See thread_context_collector_on_gc_start for details
171
175
  unsigned int gc_samples_missed_due_to_missing_context;
176
+ // See thread_context_collector_sample_after_gc for details
177
+ unsigned int gc_samples_skipped_nothing_to_flush;
172
178
  // How many per-thread samples were skipped because the thread has been continuously suspended
173
179
  // (no GVL) since its previous sample, so its Ruby stack cannot have changed.
174
180
  unsigned int inactive_thread_samples_skipped;
@@ -300,7 +306,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
300
306
  static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE allow_exception);
301
307
  static VALUE _native_on_gc_start(VALUE self, VALUE collector_instance);
302
308
  static VALUE _native_on_gc_finish(VALUE self, VALUE collector_instance);
303
- static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE allow_exception);
309
+ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
304
310
  static void update_metrics_and_sample(
305
311
  thread_context_collector_state *state,
306
312
  VALUE thread_being_sampled,
@@ -317,8 +323,7 @@ static void trigger_sample_for_thread(
317
323
  long current_monotonic_wall_time_ns,
318
324
  ddog_CharSlice *ruby_vm_type,
319
325
  ddog_CharSlice *class_name,
320
- bool is_gvl_waiting_state,
321
- bool is_safe_to_allocate_objects
326
+ bool is_gvl_waiting_state
322
327
  );
323
328
  static VALUE _native_thread_list(VALUE self);
324
329
  static void check_frozen_thread(VALUE thread);
@@ -338,8 +343,7 @@ static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
338
343
  static void trace_identifiers_for(
339
344
  thread_context_collector_state *state,
340
345
  VALUE thread,
341
- trace_identifiers *trace_identifiers_result,
342
- bool is_safe_to_allocate_objects
346
+ trace_identifiers *trace_identifiers_result
343
347
  );
344
348
  static bool should_collect_resource(VALUE root_span);
345
349
  static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
@@ -353,8 +357,7 @@ static void ddtrace_otel_trace_identifiers_for(
353
357
  VALUE *root_span,
354
358
  VALUE *numeric_span_id,
355
359
  VALUE active_span,
356
- VALUE otel_values,
357
- bool is_safe_to_allocate_objects
360
+ VALUE otel_values
358
361
  );
359
362
  static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples);
360
363
  static bool handle_gvl_waiting(
@@ -367,17 +370,17 @@ static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread);
367
370
  static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread);
368
371
  #ifndef NO_GVL_INSTRUMENTATION
369
372
  static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread);
370
- static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
371
- static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE allow_exception);
373
+ static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE waiting_for_gvl_threshold_ns);
374
+ static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
372
375
  #endif
373
376
  static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns);
374
377
  static void otel_without_ddtrace_trace_identifiers_for(
375
378
  thread_context_collector_state *state,
376
379
  VALUE thread,
377
- trace_identifiers *trace_identifiers_result,
378
- bool is_safe_to_allocate_objects
380
+ trace_identifiers *trace_identifiers_result
379
381
  );
380
382
  static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
383
+ static bool is_otel_current_span_key_needed(thread_context_collector_state *state);
381
384
  static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
382
385
  static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
383
386
  static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
@@ -412,7 +415,7 @@ void collectors_thread_context_init(VALUE profiling_module) {
412
415
  rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
413
416
  rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
414
417
  rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
415
- rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 2);
418
+ rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 1);
416
419
  rb_define_singleton_method(testing_module, "_native_thread_list", _native_thread_list, 0);
417
420
  rb_define_singleton_method(testing_module, "_native_per_thread_context", _native_per_thread_context, 1);
418
421
  rb_define_singleton_method(testing_module, "_native_stats", _native_stats, 1);
@@ -429,7 +432,7 @@ void collectors_thread_context_init(VALUE profiling_module) {
429
432
  #ifndef NO_GVL_INSTRUMENTATION
430
433
  rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
431
434
  rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 2);
432
- rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 3);
435
+ rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 2);
433
436
  #endif
434
437
  rb_define_singleton_method(testing_module, "_native_apply_delta_to_cpu_time_at_previous_sample_ns", _native_apply_delta_to_cpu_time_at_previous_sample_ns, 2);
435
438
 
@@ -565,7 +568,7 @@ static VALUE _native_new(VALUE klass) {
565
568
  state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
566
569
  VALUE main_thread = rb_thread_main();
567
570
  state->main_thread = main_thread;
568
- state->otel_current_span_key = Qtrue;
571
+ state->otel_current_span_key = OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED;
569
572
  state->gc_tracking.wall_time_at_previous_gc_ns = INVALID_TIME;
570
573
  state->gc_tracking.wall_time_at_last_flushed_gc_event_ns = 0;
571
574
 
@@ -591,7 +594,6 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
591
594
  VALUE max_frames = rb_hash_fetch(options, ID2SYM(rb_intern("max_frames")));
592
595
  VALUE tracer_context_key = rb_hash_fetch(options, ID2SYM(rb_intern("tracer_context_key")));
593
596
  VALUE endpoint_collection_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("endpoint_collection_enabled")));
594
- VALUE waiting_for_gvl_threshold_ns = rb_hash_fetch(options, ID2SYM(rb_intern("waiting_for_gvl_threshold_ns")));
595
597
  VALUE otel_context_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("otel_context_enabled")));
596
598
  VALUE native_filenames_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("native_filenames_enabled")));
597
599
  VALUE show_classes = rb_hash_fetch(options, ID2SYM(rb_intern("show_classes")));
@@ -599,7 +601,6 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
599
601
 
600
602
  ENFORCE_TYPE(max_frames, T_FIXNUM);
601
603
  ENFORCE_BOOLEAN(endpoint_collection_enabled);
602
- ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
603
604
  ENFORCE_BOOLEAN(native_filenames_enabled);
604
605
  ENFORCE_BOOLEAN(show_classes);
605
606
  ENFORCE_TYPE(overhead_filename, T_STRING);
@@ -627,8 +628,6 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
627
628
  raise_error(rb_eArgError, "Unexpected value for otel_context_enabled: %+" PRIsVALUE, otel_context_enabled);
628
629
  }
629
630
 
630
- state->waiting_for_gvl_threshold_ns = NUM2UINT(waiting_for_gvl_threshold_ns);
631
-
632
631
  if (RTEST(tracer_context_key)) {
633
632
  ENFORCE_TYPE(tracer_context_key, T_SYMBOL);
634
633
  // Note about rb_to_id and dynamic symbols: calling `rb_to_id` prevents symbols from ever being garbage collected.
@@ -652,10 +651,14 @@ static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance
652
651
 
653
652
  if (allow_exception == Qfalse) debug_enter_unsafe_context();
654
653
 
655
- thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE));
654
+ bool needs_otel_span_key =
655
+ thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE));
656
656
 
657
657
  if (allow_exception == Qfalse) debug_leave_unsafe_context();
658
658
 
659
+ // Same as the CpuAndWallTimeWorker does: this can't happen during a sample (and thus not inside the unsafe context)
660
+ if (needs_otel_span_key) thread_context_collector_resolve_otel_span_key_may_lose_gvl(collector_instance);
661
+
659
662
  return Qtrue;
660
663
  }
661
664
 
@@ -683,14 +686,12 @@ static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_ins
683
686
  return Qtrue;
684
687
  }
685
688
 
686
- static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE allow_exception) {
687
- ENFORCE_BOOLEAN(allow_exception);
688
-
689
- if (allow_exception == Qfalse) debug_enter_unsafe_context();
689
+ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
690
+ debug_enter_unsafe_context();
690
691
 
691
692
  thread_context_collector_sample_after_gc(collector_instance);
692
693
 
693
- if (allow_exception == Qfalse) debug_leave_unsafe_context();
694
+ debug_leave_unsafe_context();
694
695
 
695
696
  return Qtrue;
696
697
  }
@@ -741,7 +742,7 @@ static void record_sampling_overhead(thread_context_collector_state *state, per_
741
742
  // Assumption 4: This function IS NOT called in a reentrant way.
742
743
  // Assumption 5: This function is called from the main Ractor (if Ruby has support for Ractors).
743
744
  //
744
- void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns) {
745
+ bool thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns) {
745
746
  thread_context_collector_state *state;
746
747
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
747
748
 
@@ -781,6 +782,8 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
781
782
  if (!current_thread_context->is_profiler_internal_thread) {
782
783
  record_sampling_overhead(state, current_thread_context);
783
784
  }
785
+
786
+ return is_otel_current_span_key_needed(state);
784
787
  }
785
788
 
786
789
  static void update_metrics_and_sample(
@@ -824,8 +827,7 @@ static void update_metrics_and_sample(
824
827
  current_monotonic_wall_time_ns,
825
828
  NULL,
826
829
  NULL,
827
- is_gvl_waiting_state,
828
- /* is_safe_to_allocate_objects: */ true // We called from a context that's safe to run any regular code, including allocations
830
+ is_gvl_waiting_state
829
831
  );
830
832
  }
831
833
 
@@ -983,7 +985,14 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
983
985
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
984
986
 
985
987
  if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
986
- raise_error(rb_eRuntimeError, "BUG: Unexpected call to sample_after_gc without valid GC information available");
988
+ // Rarely, we might be called with nothing to do, as an earlier call already flushed the needed info.
989
+ // This is because Ruby clears the "pending" flag for an entire batch of postponed jobs BEFORE running any of them
990
+ // (see `rb_postponed_job_flush`). Thus, if another GC happens while an earlier job in the postponed batch is
991
+ // running (and other parts of the profiler such as the heap profiler can trigger this)
992
+ // then `on_gc_finish` records that data and asks for a second flush, even though Ruby has not yet run the
993
+ // first one. Because of this, Ruby can call us once more with nothing left to do.
994
+ state->stats.gc_samples_skipped_nothing_to_flush++;
995
+ return Qnil;
987
996
  }
988
997
 
989
998
  int max_labels_needed_for_gc = 7; // Magic number gets validated inside gc_profiling_set_metadata
@@ -1017,13 +1026,23 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
1017
1026
 
1018
1027
  state->stats.gc_samples++;
1019
1028
 
1020
- // Let recorder do any cleanup/updates it requires after a GC step.
1021
- recorder_after_gc_step(state->recorder_instance);
1022
-
1023
1029
  // Return a VALUE to make it easier to call this function from Ruby APIs that expect a return value (such as rb_rescue2)
1024
1030
  return Qnil;
1025
1031
  }
1026
1032
 
1033
+ // Let the recorder do any cleanup/updates it requires after a GC step.
1034
+ //
1035
+ // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
1036
+ // Assumption 2: This function is allowed to raise exceptions. Caller is responsible for handling them, if needed.
1037
+ VALUE thread_context_collector_heap_update_may_lose_gvl(VALUE self_instance) {
1038
+ thread_context_collector_state *state;
1039
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1040
+
1041
+ recorder_heap_update_may_lose_gvl(state->recorder_instance);
1042
+
1043
+ return Qnil; // for rb_rescue2
1044
+ }
1045
+
1027
1046
  static void trigger_sample_for_thread(
1028
1047
  thread_context_collector_state *state,
1029
1048
  VALUE thread_being_sampled,
@@ -1033,10 +1052,7 @@ static void trigger_sample_for_thread(
1033
1052
  // These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
1034
1053
  ddog_CharSlice *ruby_vm_type,
1035
1054
  ddog_CharSlice *class_name,
1036
- bool is_gvl_waiting_state,
1037
- // If the Ruby VM is at a state that can allocate objects safely, or not. Added for allocation profiling: we're not
1038
- // allowed to allocate objects (or raise exceptions) when inside the NEWOBJ tracepoint.
1039
- bool is_safe_to_allocate_objects
1055
+ bool is_gvl_waiting_state
1040
1056
  ) {
1041
1057
  int max_label_count =
1042
1058
  1 + // thread id
@@ -1074,11 +1090,11 @@ static void trigger_sample_for_thread(
1074
1090
  }
1075
1091
 
1076
1092
  trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
1077
- trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result, is_safe_to_allocate_objects);
1093
+ trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result);
1078
1094
 
1079
1095
  if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
1080
1096
  // If we couldn't get something with ddtrace, let's see if we can get some trace identifiers from opentelemetry directly
1081
- otel_without_ddtrace_trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result, is_safe_to_allocate_objects);
1097
+ otel_without_ddtrace_trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result);
1082
1098
  }
1083
1099
 
1084
1100
  if (trace_identifiers_result.valid) {
@@ -1345,7 +1361,6 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
1345
1361
  rb_str_concat(result, rb_sprintf(" main_thread=%"PRIsVALUE, state->main_thread));
1346
1362
  rb_str_concat(result, rb_sprintf(" gc_tracking=%"PRIsVALUE, gc_tracking_as_ruby_hash(state)));
1347
1363
  rb_str_concat(result, rb_sprintf(" otel_current_span_key=%"PRIsVALUE, state->otel_current_span_key));
1348
- rb_str_concat(result, rb_sprintf(" waiting_for_gvl_threshold_ns=%u", state->waiting_for_gvl_threshold_ns));
1349
1364
 
1350
1365
  return result;
1351
1366
  }
@@ -1385,6 +1400,7 @@ static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE has
1385
1400
  ID2SYM(rb_intern("sample_count")), /* => */ UINT2NUM(state->stats.sample_count),
1386
1401
  ID2SYM(rb_intern("gc_samples")), /* => */ UINT2NUM(state->stats.gc_samples),
1387
1402
  ID2SYM(rb_intern("gc_samples_missed_due_to_missing_context")), /* => */ UINT2NUM(state->stats.gc_samples_missed_due_to_missing_context),
1403
+ ID2SYM(rb_intern("gc_samples_skipped_nothing_to_flush")), /* => */ UINT2NUM(state->stats.gc_samples_skipped_nothing_to_flush),
1388
1404
  ID2SYM(rb_intern("inactive_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.inactive_thread_samples_skipped),
1389
1405
  ID2SYM(rb_intern("profiler_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.profiler_thread_samples_skipped),
1390
1406
  };
@@ -1515,14 +1531,14 @@ VALUE enforce_thread_context_collector_instance(VALUE object) {
1515
1531
  return object;
1516
1532
  }
1517
1533
 
1518
- // Finalize any pending heap allocation recordings.
1519
- // On Ruby 4+, heap allocations are recorded in two phases: during on_newobj_event we capture
1520
- // the object reference, then later we safely call rb_obj_id() to get the object ID.
1521
- void thread_context_collector_after_allocation(VALUE self_instance) {
1534
+ // Commit any pending heap allocation recordings.
1535
+ // Recording an allocation only puts it on a pending list, because taking the weak reference the heap profiler needs
1536
+ // can't be done from inside the NEWOBJ tracepoint -- see `heap_recorder_commit_recordings_may_lose_gvl`.
1537
+ void thread_context_collector_commit_heap_recordings_may_lose_gvl(VALUE self_instance) {
1522
1538
  thread_context_collector_state *state;
1523
1539
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1524
1540
 
1525
- recorder_after_sample(state->recorder_instance);
1541
+ recorder_commit_heap_recordings_may_lose_gvl(state->recorder_instance);
1526
1542
  }
1527
1543
 
1528
1544
  // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
@@ -1547,8 +1563,7 @@ static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_ins
1547
1563
  static void trace_identifiers_for(
1548
1564
  thread_context_collector_state *state,
1549
1565
  VALUE thread,
1550
- trace_identifiers *trace_identifiers_result,
1551
- bool is_safe_to_allocate_objects
1566
+ trace_identifiers *trace_identifiers_result
1552
1567
  ) {
1553
1568
  if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
1554
1569
  if (state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY) return;
@@ -1569,7 +1584,7 @@ static void trace_identifiers_for(
1569
1584
  VALUE numeric_span_id = Qnil;
1570
1585
 
1571
1586
  if (otel_values != Qnil) {
1572
- ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values, is_safe_to_allocate_objects);
1587
+ ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values);
1573
1588
  }
1574
1589
 
1575
1590
  if (root_span == Qnil || (active_span == Qnil && numeric_span_id == Qnil)) return;
@@ -1665,11 +1680,11 @@ bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
1665
1680
  return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
1666
1681
  }
1667
1682
 
1668
- // This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should never allocate in the
1669
- // Ruby heap.
1683
+ // This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
1684
+ // Ruby heap nor release the GVL (https://github.com/DataDog/dd-trace-rb/pull/4240).
1670
1685
  //
1671
- // Returns true if the after_allocation needs to be called (to do work that can't be done from inside the
1672
- // tracepoint, such as allocate new objects), and false if it doesn't
1686
+ // Returns true if `thread_context_collector_commit_heap_recordings_may_lose_gvl` needs to be called (to do work
1687
+ // that can't be done from inside the tracepoint, such as allocate new objects), and false if it doesn't
1673
1688
  //
1674
1689
  // The callers must ensure thread_context is non-NULL.
1675
1690
  bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object) {
@@ -1714,13 +1729,10 @@ bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_
1714
1729
  // Thus, we need to make sure there's actually a class before getting its name.
1715
1730
 
1716
1731
  if (klass != 0) {
1717
- const char *name = rb_class2name(klass);
1718
- size_t name_length = name != NULL ? strlen(name) : 0;
1719
-
1720
- if (name_length > 0) {
1721
- class_name = (ddog_CharSlice) {.ptr = name, .len = name_length};
1732
+ VALUE name = ddtrace_alloc_free_rb_mod_name(klass);
1733
+ if (name != Qnil) {
1734
+ class_name = char_slice_from_ruby_string(name);
1722
1735
  } else {
1723
- // @ivoanjo: I'm not sure this can ever happen, but just-in-case
1724
1736
  class_name = ruby_value_type_to_class_name(type);
1725
1737
  }
1726
1738
  } else {
@@ -1740,23 +1752,22 @@ bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_
1740
1752
  class_name = ruby_vm_type; // For other weird internal things we just use the VM type
1741
1753
  }
1742
1754
 
1743
- bool needs_after_allocation = track_object(state->recorder_instance, new_object, sample_weight, class_name);
1744
-
1745
1755
  VALUE current_thread = rb_thread_current();
1746
1756
 
1757
+ heap_sample_values heap_sample = {.new_object = new_object, .alloc_class = class_name};
1758
+
1747
1759
  trigger_sample_for_thread(
1748
1760
  state,
1749
1761
  current_thread,
1750
1762
  thread_context,
1751
- (sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
1763
+ (sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = &heap_sample},
1752
1764
  INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
1753
1765
  &ruby_vm_type,
1754
1766
  &class_name,
1755
- /* is_gvl_waiting_state: */ false,
1756
- /* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
1767
+ /* is_gvl_waiting_state: */ false
1757
1768
  );
1758
1769
 
1759
- return needs_after_allocation;
1770
+ return heap_sample.out_needs_commit;
1760
1771
  }
1761
1772
 
1762
1773
  // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
@@ -1769,13 +1780,13 @@ static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collecto
1769
1780
 
1770
1781
  debug_enter_unsafe_context();
1771
1782
 
1772
- bool needs_after_allocation = thread_context_collector_sample_allocation(collector_instance, thread_context, NUM2UINT(sample_weight), new_object);
1783
+ bool needs_commit = thread_context_collector_sample_allocation(collector_instance, thread_context, NUM2UINT(sample_weight), new_object);
1773
1784
 
1774
1785
  debug_leave_unsafe_context();
1775
1786
 
1776
1787
  // We could instead choose to automatically trigger the after allocation here; yet, it seems kinda nice to keep it manual for
1777
1788
  // the tests so we can pull on each lever separately and observe "the sausage being made" in steps
1778
- return needs_after_allocation ? Qtrue : Qfalse;
1789
+ return needs_commit ? Qtrue : Qfalse;
1779
1790
  }
1780
1791
 
1781
1792
  static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
@@ -1825,26 +1836,57 @@ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
1825
1836
  return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
1826
1837
  }
1827
1838
 
1828
- static VALUE get_otel_current_span_key(thread_context_collector_state *state, bool is_safe_to_allocate_objects) {
1829
- if (state->otel_current_span_key == Qtrue) { // Qtrue means we haven't tried to extract it yet
1830
- if (!is_safe_to_allocate_objects) {
1831
- // Calling read_otel_current_span_key_const below can trigger exceptions and arbitrary Ruby code running (e.g.
1832
- // `const_missing`, etc). Not safe to call in this situation, so we just skip otel info for this sample.
1833
- return Qnil;
1834
- }
1835
-
1836
- // If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
1837
- // gets refactored, we should not fall on our face)
1838
- VALUE span_key = rb_protect(read_otel_current_span_key_const, Qnil, NULL);
1839
- rb_set_errinfo(Qnil); // **Clear any pending exception after ignoring it**
1839
+ static VALUE otel_current_span_key_not_found(DDTRACE_UNUSED VALUE _unused, DDTRACE_UNUSED VALUE _exception) {
1840
+ return Qnil;
1841
+ }
1840
1842
 
1841
- // Note that this gets set to Qnil if we failed to extract the correct value, and thus we won't try to extract it again
1842
- state->otel_current_span_key = span_key;
1843
+ static VALUE get_otel_current_span_key(thread_context_collector_state *state) {
1844
+ if (state->otel_current_span_key == OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED) {
1845
+ // Extracting this needs calling into Ruby code, which we don't want to do in the middle of a sample.
1846
+ // So instead we signal that we want this, and let it be resolved separately.
1847
+ state->otel_current_span_key = OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED;
1848
+ return Qnil;
1843
1849
  }
1844
1850
 
1851
+ // We asked for it, but it hasn't been extracted yet
1852
+ if (is_otel_current_span_key_needed(state)) return Qnil;
1853
+
1845
1854
  return state->otel_current_span_key;
1846
1855
  }
1847
1856
 
1857
+ static bool is_otel_current_span_key_needed(thread_context_collector_state *state) {
1858
+ return state->otel_current_span_key == OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED;
1859
+ }
1860
+
1861
+ // Extracts `otel_current_span_key`, if a sample asked for it (see `get_otel_current_span_key`).
1862
+ // This is deliberately not done during sampling because we can lose the GVL.
1863
+ //
1864
+ // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
1865
+ void thread_context_collector_resolve_otel_span_key_may_lose_gvl(VALUE self_instance) {
1866
+ thread_context_collector_state *state;
1867
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1868
+
1869
+ if (!is_otel_current_span_key_needed(state)) return;
1870
+
1871
+ // If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
1872
+ // gets refactored, we should not fall on our face).
1873
+ //
1874
+ // Note that we use `rb_rescue2` and not `rb_protect` so that we only swallow exceptions: anything else that can
1875
+ // unwind through here (e.g. a `Thread#kill`) is not ours to swallow. `rb_rescue2` also takes care of restoring
1876
+ // any previously-pending exception for us.
1877
+ VALUE span_key = rb_rescue2(
1878
+ read_otel_current_span_key_const,
1879
+ Qnil,
1880
+ otel_current_span_key_not_found,
1881
+ Qnil,
1882
+ rb_eException, // rb_eException is the base class of all Ruby exceptions
1883
+ 0 // Required by API to be the last argument
1884
+ );
1885
+
1886
+ // Note that this gets set to Qnil if we failed to extract the correct value, and thus we won't try to extract it again
1887
+ state->otel_current_span_key = span_key;
1888
+ }
1889
+
1848
1890
  // This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
1849
1891
  // differently, and this codepath handles it.
1850
1892
  static void ddtrace_otel_trace_identifiers_for(
@@ -1853,8 +1895,7 @@ static void ddtrace_otel_trace_identifiers_for(
1853
1895
  VALUE *root_span,
1854
1896
  VALUE *numeric_span_id,
1855
1897
  VALUE active_span,
1856
- VALUE otel_values,
1857
- bool is_safe_to_allocate_objects
1898
+ VALUE otel_values
1858
1899
  ) {
1859
1900
  VALUE resolved_numeric_span_id =
1860
1901
  active_span == Qnil ?
@@ -1865,7 +1906,7 @@ static void ddtrace_otel_trace_identifiers_for(
1865
1906
 
1866
1907
  if (resolved_numeric_span_id == Qnil) return;
1867
1908
 
1868
- VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
1909
+ VALUE otel_current_span_key = get_otel_current_span_key(state);
1869
1910
  if (otel_current_span_key == Qnil) return;
1870
1911
  VALUE current_trace = *active_trace;
1871
1912
 
@@ -1985,15 +2026,14 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
1985
2026
  static void otel_without_ddtrace_trace_identifiers_for(
1986
2027
  thread_context_collector_state *state,
1987
2028
  VALUE thread,
1988
- trace_identifiers *trace_identifiers_result,
1989
- bool is_safe_to_allocate_objects
2029
+ trace_identifiers *trace_identifiers_result
1990
2030
  ) {
1991
2031
  VALUE context_storage = otel_context_storage_for(state, thread);
1992
2032
 
1993
2033
  // If it exists, context_storage is expected to be an Array[OpenTelemetry::Context]
1994
2034
  if (context_storage == Qnil || !RB_TYPE_P(context_storage, T_ARRAY)) return;
1995
2035
 
1996
- VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
2036
+ VALUE otel_current_span_key = get_otel_current_span_key(state);
1997
2037
  if (otel_current_span_key == Qnil) return;
1998
2038
 
1999
2039
  long active_context_index = RARRAY_LEN(context_storage) - 1;
@@ -2195,12 +2235,9 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2195
2235
  }
2196
2236
 
2197
2237
  #ifndef NO_GVL_INSTRUMENTATION
2198
- // This function runs on the passed thread and has the GVL because it gets called just after the Ruby thread acquired the GVL
2238
+ // We must only use async-signal-safe functions here, see notes in the caller
2199
2239
  __attribute__((warn_unused_result))
2200
- on_gvl_running_result thread_context_collector_on_gvl_running(VALUE self_instance, VALUE thread, per_thread_context *thread_context) {
2201
- thread_context_collector_state *state;
2202
- TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
2203
-
2240
+ on_gvl_running_result thread_context_collector_on_gvl_running(VALUE thread, per_thread_context *thread_context, uint32_t waiting_for_gvl_threshold_ns) {
2204
2241
  // Bump the event counter and clears the state bit to "running"
2205
2242
  uint64_t counter_portion = thread_context->gvl_state_change_count >> 1;
2206
2243
  thread_context->gvl_state_change_count = ((counter_portion + 1) << 1) | GVL_RUNNING;
@@ -2217,7 +2254,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2217
2254
 
2218
2255
  long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - gvl_waiting_at;
2219
2256
 
2220
- bool should_sample = waiting_for_gvl_duration_ns >= state->waiting_for_gvl_threshold_ns;
2257
+ bool should_sample = waiting_for_gvl_duration_ns >= waiting_for_gvl_threshold_ns;
2221
2258
 
2222
2259
  if (should_sample) {
2223
2260
  // We flip the gvl_waiting_at to negative to mark that the thread is now running and no longer waiting
@@ -2392,8 +2429,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2392
2429
  gvl_waiting_started_wall_time_ns,
2393
2430
  NULL,
2394
2431
  NULL,
2395
- /* is_gvl_waiting_state: */ false, // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
2396
- /* is_safe_to_allocate_objects: */ true // This is similar to a regular cpu/wall sample, so it's also safe
2432
+ /* is_gvl_waiting_state: */ false // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
2397
2433
  );
2398
2434
  }
2399
2435
 
@@ -2413,15 +2449,18 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2413
2449
  return result;
2414
2450
  }
2415
2451
 
2416
- static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
2452
+ static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE waiting_for_gvl_threshold_ns) {
2417
2453
  ENFORCE_THREAD(thread);
2454
+ ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
2418
2455
 
2419
2456
  debug_enter_unsafe_context();
2420
2457
 
2421
2458
  per_thread_context *thread_context = get_per_thread_context(thread);
2422
2459
  VALUE result;
2423
2460
  if (thread_context) {
2424
- result = thread_context_collector_on_gvl_running(collector_instance, thread, thread_context).action == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
2461
+ on_gvl_running_result gvl_running_result =
2462
+ thread_context_collector_on_gvl_running(thread, thread_context, NUM2UINT(waiting_for_gvl_threshold_ns));
2463
+ result = gvl_running_result.action == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
2425
2464
  } else {
2426
2465
  result = Qfalse;
2427
2466
  }
@@ -2431,11 +2470,10 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2431
2470
  return result;
2432
2471
  }
2433
2472
 
2434
- static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE allow_exception) {
2473
+ static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
2435
2474
  ENFORCE_THREAD(thread);
2436
- ENFORCE_BOOLEAN(allow_exception);
2437
2475
 
2438
- if (allow_exception == Qfalse) debug_enter_unsafe_context();
2476
+ debug_enter_unsafe_context();
2439
2477
 
2440
2478
  VALUE result = thread_context_collector_sample_after_gvl_running(
2441
2479
  collector_instance,
@@ -2443,7 +2481,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2443
2481
  monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
2444
2482
  );
2445
2483
 
2446
- if (allow_exception == Qfalse) debug_leave_unsafe_context();
2484
+ debug_leave_unsafe_context();
2447
2485
 
2448
2486
  return result;
2449
2487
  }
@@ -6,15 +6,19 @@
6
6
 
7
7
  #include "gvl_profiling_helper.h"
8
8
 
9
- void thread_context_collector_sample(
9
+ // Returns true if `thread_context_collector_resolve_otel_span_key_may_lose_gvl` needs to be called (which the caller
10
+ // must only do once the sample is over)
11
+ __attribute__((warn_unused_result)) bool thread_context_collector_sample(
10
12
  VALUE self_instance,
11
13
  long current_monotonic_wall_time_ns
12
14
  );
13
15
  __attribute__((warn_unused_result)) bool thread_context_collector_prepare_sample_inside_signal_handler(void);
14
16
  __attribute__((warn_unused_result)) bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object);
15
- void thread_context_collector_after_allocation(VALUE self_instance);
17
+ void thread_context_collector_commit_heap_recordings_may_lose_gvl(VALUE self_instance);
16
18
  void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples);
17
19
  VALUE thread_context_collector_sample_after_gc(VALUE self_instance);
20
+ VALUE thread_context_collector_heap_update_may_lose_gvl(VALUE self_instance);
21
+ void thread_context_collector_resolve_otel_span_key_may_lose_gvl(VALUE self_instance);
18
22
  void thread_context_collector_on_gc_start(VALUE self_instance);
19
23
  __attribute__((warn_unused_result)) bool thread_context_collector_on_gc_finish(VALUE self_instance);
20
24
  VALUE enforce_thread_context_collector_instance(VALUE object);
@@ -38,7 +42,7 @@ void thread_context_collector_profiler_internal_thread_done(VALUE self_instance)
38
42
  } on_gvl_running_result;
39
43
 
40
44
  void thread_context_collector_on_gvl_waiting(per_thread_context *thread_context);
41
- __attribute__((warn_unused_result)) on_gvl_running_result thread_context_collector_on_gvl_running(VALUE self_instance, VALUE thread, per_thread_context *thread_context);
45
+ __attribute__((warn_unused_result)) on_gvl_running_result thread_context_collector_on_gvl_running(VALUE thread, per_thread_context *thread_context, uint32_t waiting_for_gvl_threshold_ns);
42
46
  VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns);
43
47
  void thread_context_collector_on_gvl_released(per_thread_context *thread_context);
44
48
  #endif
@@ -97,13 +97,3 @@ static inline VALUE get_error_details_and_drop(ddog_Error *error) {
97
97
  // Returns the amount of characters written to string (which are necessarily
98
98
  // bounded by capacity - 1 since the string will be null-terminated).
99
99
  size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capacity);
100
-
101
- #define IMEMO_MASK 0x0f
102
-
103
- // Returns the imemo type of an imemo object.
104
- // This mask is the same between Ruby 2.5 and 3.3-preview3. Furthermore, the intention of this method is to be used
105
- // to call `rb_imemo_name` which correctly handles invalid numbers so even if the mask changes in the future, at most
106
- // we'll get incorrect results (and never a VM crash)
107
- static inline int ddtrace_imemo_type(VALUE imemo) {
108
- return (RBASIC(imemo)->flags >> FL_USHIFT) & IMEMO_MASK;
109
- }