datadog 2.43.0 → 2.45.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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +136 -132
  4. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -5
  5. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +0 -2
  6. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +19 -20
  7. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +5 -1
  8. data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
  9. data/ext/datadog_profiling_native_extension/collectors_stack.h +1 -1
  10. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +146 -109
  11. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -2
  12. data/ext/datadog_profiling_native_extension/extconf.rb +6 -2
  13. data/ext/datadog_profiling_native_extension/heap_recorder.c +2 -2
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +122 -53
  15. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +6 -2
  16. data/ext/datadog_profiling_native_extension/profiling.c +22 -23
  17. data/ext/datadog_profiling_native_extension/ruby_helpers.c +3 -1
  18. data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -0
  19. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +0 -2
  20. data/ext/datadog_profiling_native_extension/stack_recorder.c +10 -28
  21. data/ext/datadog_profiling_native_extension/stack_recorder.h +0 -1
  22. data/ext/datadog_profiling_native_extension/time_helpers.c +2 -4
  23. data/ext/datadog_profiling_native_extension/time_helpers.h +13 -14
  24. data/ext/libdatadog_api/di.c +8 -3
  25. data/ext/libdatadog_api/extconf.rb +1 -1
  26. data/ext/libdatadog_api/feature_flags.c +8 -0
  27. data/ext/libdatadog_api/init.c +3 -1
  28. data/ext/libdatadog_api/otel_thread_context.c +38 -17
  29. data/ext/libdatadog_api/trace_exporter.c +77 -11
  30. data/ext/libdatadog_extconf_helpers.rb +14 -9
  31. data/lib/datadog/ai_guard/component.rb +17 -8
  32. data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
  33. data/lib/datadog/ai_guard/configuration.rb +13 -0
  34. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
  35. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
  36. data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
  37. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
  38. data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
  39. data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
  40. data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
  41. data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
  42. data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
  43. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
  44. data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
  45. data/lib/datadog/ai_guard/evaluation/request.rb +4 -60
  46. data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
  47. data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
  48. data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
  49. data/lib/datadog/ai_guard/evaluation.rb +25 -11
  50. data/lib/datadog/ai_guard/ext.rb +1 -0
  51. data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +6 -4
  52. data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
  53. data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
  54. data/lib/datadog/ai_guard/redaction/result.rb +29 -0
  55. data/lib/datadog/ai_guard/redaction.rb +80 -0
  56. data/lib/datadog/ai_guard.rb +58 -57
  57. data/lib/datadog/core/configuration/components.rb +9 -1
  58. data/lib/datadog/core/configuration/settings.rb +5 -2
  59. data/lib/datadog/core/configuration/supported_configurations.rb +8 -0
  60. data/lib/datadog/core/remote/client/capabilities.rb +10 -4
  61. data/lib/datadog/core/remote/component.rb +9 -2
  62. data/lib/datadog/core/remote/worker.rb +8 -0
  63. data/lib/datadog/di/instrumenter.rb +1 -1
  64. data/lib/datadog/di/probe_manager.rb +10 -5
  65. data/lib/datadog/di/proc_responder.rb +1 -1
  66. data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
  67. data/lib/datadog/open_feature/agentless/parser.rb +43 -0
  68. data/lib/datadog/open_feature/agentless/response.rb +19 -0
  69. data/lib/datadog/open_feature/agentless/transport.rb +103 -0
  70. data/lib/datadog/open_feature/component.rb +57 -1
  71. data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
  72. data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
  73. data/lib/datadog/open_feature/configuration/source.rb +43 -0
  74. data/lib/datadog/open_feature/configuration.rb +118 -0
  75. data/lib/datadog/open_feature/native_evaluator.rb +1 -14
  76. data/lib/datadog/open_feature/remote.rb +5 -5
  77. data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
  78. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +7 -2
  79. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
  80. data/lib/datadog/profiling/component.rb +2 -3
  81. data/lib/datadog/profiling/exporter.rb +7 -5
  82. data/lib/datadog/symbol_database/extractor.rb +84 -102
  83. data/lib/datadog/tracing/component.rb +3 -1
  84. data/lib/datadog/tracing/configuration/ext.rb +1 -0
  85. data/lib/datadog/tracing/configuration/settings.rb +9 -2
  86. data/lib/datadog/tracing/context.rb +24 -2
  87. data/lib/datadog/tracing/context_provider.rb +9 -5
  88. data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
  89. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +6 -3
  90. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -0
  91. data/lib/datadog/tracing/metadata/ext.rb +3 -0
  92. data/lib/datadog/tracing/otel_thread_context.rb +76 -11
  93. data/lib/datadog/tracing/tracer.rb +15 -2
  94. data/lib/datadog/tracing/transport/native.rb +23 -22
  95. data/lib/datadog/tracing/transport/trace_formatter.rb +13 -3
  96. data/lib/datadog/tracing/transport/traces.rb +1 -1
  97. data/lib/datadog/version.rb +1 -1
  98. metadata +23 -7
  99. data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
@@ -13,6 +13,7 @@ __attribute__((warn_unused_result)) bool thread_context_collector_sample(
13
13
  long current_monotonic_wall_time_ns
14
14
  );
15
15
  __attribute__((warn_unused_result)) bool thread_context_collector_prepare_sample_inside_signal_handler(void);
16
+ void thread_context_collector_prepare_sample_on_gc_finish(void);
16
17
  __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);
17
18
  void thread_context_collector_commit_heap_recordings_may_lose_gvl(VALUE self_instance);
18
19
  void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples);
@@ -24,10 +25,10 @@ __attribute__((warn_unused_result)) bool thread_context_collector_on_gc_finish(V
24
25
  VALUE enforce_thread_context_collector_instance(VALUE object);
25
26
  void thread_context_collector_stats(VALUE self_instance, VALUE stats_hash);
26
27
  void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance);
27
- void thread_context_collector_on_serialize(VALUE self_instance);
28
+ VALUE thread_context_prepare_serialize(VALUE self_instance);
28
29
  void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance);
29
30
  void thread_context_collector_profiler_internal_thread_started(void);
30
- void thread_context_collector_profiler_internal_thread_done(VALUE self_instance);
31
+ VALUE thread_context_collector_profiler_internal_thread_done(VALUE self_instance);
31
32
 
32
33
  #ifndef NO_GVL_INSTRUMENTATION
33
34
  typedef enum {
@@ -139,6 +139,8 @@ end
139
139
 
140
140
  have_func "malloc_stats"
141
141
 
142
+ $defs << "-DHAVE_ZJIT_FRAME" if RUBY_VERSION >= "4.1"
143
+
142
144
  # On older Rubies, there was no primitive mutex and condition variable implemented in `thread_sync.rb` (internal)
143
145
  $defs << "-DNO_PRIMITIVE_MUTEX_AND_CONDITION_VARIABLE" if RUBY_VERSION < "4"
144
146
 
@@ -260,7 +262,9 @@ dir_config("ruby") # allow user to pass in non-standard core include directory
260
262
  #
261
263
  # To workaround this issue, we override `with_cppflags` for datadog-ruby_core_source to still include `$CPPFLAGS`.
262
264
  Datadog::RubyCoreSource.define_singleton_method(:with_cppflags) do |newflags, &block|
263
- super("#{newflags} #{$CPPFLAGS}", &block)
265
+ # Note: The `Shellwords.escape` is an additional workaround for ruby_core_source supplying
266
+ # one unescaped -I argument containing the complete header path, not the CPPFLAGS thing above.
267
+ super("#{Shellwords.escape(newflags)} #{$CPPFLAGS}", &block)
264
268
  end
265
269
 
266
270
  Datadog::RubyCoreSource
@@ -270,7 +274,7 @@ Datadog::RubyCoreSource
270
274
  have_header("vm_core.h") &&
271
275
  have_header("iseq.h", "vm_core.h") &&
272
276
  # These are only used on Ruby 3+
273
- (RUBY_VERSION < "3" || have_header("ractor_core.h") && have_header("internal/class.h"))
277
+ (RUBY_VERSION < "3" || have_header("ractor_core.h") && have_header("internal/class.h", "vm_core.h"))
274
278
 
275
279
  if headers_available
276
280
  # Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used.
@@ -627,7 +627,7 @@ static VALUE heap_recorder_update_locked(VALUE heap_recorder_update_locked_args_
627
627
  }
628
628
 
629
629
  size_t current_gc_gen = rb_gc_count();
630
- long now_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
630
+ long now_ns = monotonic_wall_time_now_ns();
631
631
 
632
632
  if (!full_update) {
633
633
  if (current_gc_gen == heap_recorder->update_gen) {
@@ -641,7 +641,7 @@ static VALUE heap_recorder_update_locked(VALUE heap_recorder_update_locked_args_
641
641
  return Qnil;
642
642
  }
643
643
 
644
- if (now_ns > 0 && (now_ns - heap_recorder->last_update_ns) < MIN_TIME_BETWEEN_HEAP_RECORDER_UPDATES_NS) {
644
+ if ((now_ns - heap_recorder->last_update_ns) < MIN_TIME_BETWEEN_HEAP_RECORDER_UPDATES_NS) {
645
645
  // We did an update not too long ago. Let's skip this one to avoid over-taxing the system.
646
646
  heap_recorder->stats_lifetime.updates_skipped_time++;
647
647
  return Qnil;
@@ -25,6 +25,9 @@
25
25
  #pragma GCC diagnostic push
26
26
  #pragma GCC diagnostic ignored "-Wunused-parameter"
27
27
  #include <iseq.h>
28
+ #ifdef HAVE_ZJIT_FRAME
29
+ #include <zjit.h>
30
+ #endif
28
31
  #pragma GCC diagnostic pop
29
32
 
30
33
  #ifndef NO_INTERNAL_CLASS_HEADER_INCLUDE
@@ -53,23 +56,45 @@
53
56
  #define PRIVATE_VM_API_ACCESS_SKIP_RUBY_INCLUDES
54
57
  #include "private_vm_api_access.h"
55
58
 
59
+ #ifdef HAVE_RUBY_RACTOR_H
60
+ typedef rb_ractor_t gvl_owner_context_t;
61
+ #else
62
+ typedef rb_vm_t gvl_owner_context_t;
63
+ #endif
64
+
56
65
  static inline const rb_callable_method_entry_t* get_cfunc_method_entry(const rb_control_frame_t *cfp);
57
66
  static const rb_callable_method_entry_t* safe_vm_frame_method_entry(const rb_control_frame_t *cfp);
67
+ static gvl_owner_context_t *get_gvl_owner_context(void);
68
+
69
+ typedef struct {
70
+ gvl_owner_context_t *main_ractor_gvl_owner_context;
71
+ const rb_data_type_t *thread_data_type;
72
+ } private_vm_api_global_t;
73
+
74
+ // Initialized once on the main ractor with the GVL held, before sampling starts; never rewritten.
75
+ // The native context stays at the same address across GC compaction and forks from the main ractor.
76
+ static private_vm_api_global_t private_vm_api_global;
77
+
78
+ void private_vm_api_access_init(void) {
79
+ private_vm_api_global.thread_data_type = RTYPEDDATA_TYPE(rb_thread_current());
80
+ private_vm_api_global.main_ractor_gvl_owner_context = get_gvl_owner_context();
81
+ }
82
+
83
+ void private_vm_api_access_self_test(void) {
84
+ if (private_vm_api_global.main_ractor_gvl_owner_context != get_gvl_owner_context()) {
85
+ rb_raise(rb_eRuntimeError, "BUG: main_ractor_gvl_owner_context changed unexpectedly");
86
+ }
87
+ }
58
88
 
59
89
  // MRI has a similar rb_thread_ptr() function which we can't call it directly
60
90
  // because Ruby does not expose the thread_data_type publicly.
61
- // Instead, we have our own version of that function, and we lazily initialize the thread_data_type pointer
91
+ // Instead, we have our own version of that function, and we initialize the thread_data_type pointer
62
92
  // from a known-correct object: the current thread.
63
93
  //
64
94
  // Note that beyond returning the rb_thread_struct*, rb_check_typeddata() raises an exception
65
95
  // if the argument passed in is not actually a `Thread` instance.
66
96
  static inline rb_thread_t *thread_struct_from_object(VALUE thread) {
67
- static const rb_data_type_t *thread_data_type = NULL;
68
- if (UNLIKELY(thread_data_type == NULL)) {
69
- thread_data_type = RTYPEDDATA_TYPE(rb_thread_current());
70
- }
71
-
72
- return (rb_thread_t *) rb_check_typeddata(thread, thread_data_type);
97
+ return (rb_thread_t *) rb_check_typeddata(thread, private_vm_api_global.thread_data_type);
73
98
  }
74
99
 
75
100
  rb_nativethread_id_t pthread_id_for(VALUE thread) {
@@ -86,19 +111,16 @@ rb_nativethread_id_t pthread_id_for(VALUE thread) {
86
111
  #endif
87
112
  }
88
113
 
89
- // Queries if the current thread is the owner of the global VM lock.
114
+ // Combines checking for main ractor and GVL in one check that can be done safely from a signal handler, even
115
+ // if the thread is not a Ruby thread or if we're in the middle of GC.
90
116
  //
91
- // @ivoanjo: Ruby has a similarly-named `ruby_thread_has_gvl_p` but that API is insufficient for our needs because it can
92
- // still return `true` even when a thread DOES NOT HAVE the global VM lock.
93
- // In particular, looking at the implementation, that API assumes that if a thread is not in a "blocking region" then it
94
- // will have the GVL which is probably true for the situations that API was designed to be called from BUT this assumption
95
- // does not hold true when calling `ruby_thread_has_gvl_p` from a signal handler. (Because the thread may have lost the
96
- // GVL due to a scheduler decision, not because it decided to block.)
97
- // I have also submitted https://bugs.ruby-lang.org/issues/19172 to discuss this with upstream Ruby developers.
117
+ // Note that Ruby has `ruby_thread_has_gvl_p`, but unfortunately it can return true in a signal
118
+ // handler even when the thread doesn't hold the GVL. It assumes that a thread outside a "blocking region" holds
119
+ // the GVL, but the scheduler can also take it away (https://bugs.ruby-lang.org/issues/19172).
98
120
  //
99
- // Thus we need our own gvl-checking method which actually looks at the gvl structure to determine if it is the owner.
100
- bool is_current_thread_holding_the_gvl(void) {
101
- current_gvl_owner owner = gvl_owner();
121
+ // This must not touch Ruby objects, since GC compaction may have protected their heap pages.
122
+ bool is_current_thread_in_main_ractor_and_holding_the_gvl(void) {
123
+ current_gvl_owner owner = main_ractor_gvl_owner();
102
124
  return owner.valid && pthread_equal(pthread_self(), owner.owner);
103
125
  }
104
126
 
@@ -112,23 +134,37 @@ static inline rb_ractor_t *ddtrace_get_ractor(void) {
112
134
  }
113
135
  #endif
114
136
 
137
+ static gvl_owner_context_t *get_gvl_owner_context(void) {
138
+ if (!ddtrace_rb_ractor_main_p()) {
139
+ rb_raise(rb_eRuntimeError, "BUG: get_gvl_owner_context must be called from the main ractor");
140
+ }
141
+
142
+ #ifdef HAVE_RUBY_RACTOR_H
143
+ return ddtrace_get_ractor();
144
+ #else
145
+ return GET_VM();
146
+ #endif
147
+ }
148
+
115
149
  #ifndef NO_GVL_OWNER // Ruby < 2.6 doesn't have the owner/running field
116
150
  // NOTE: Reading the owner in this is a racy read, because we're not grabbing the lock that Ruby uses to protect it.
117
151
  //
118
152
  // While we could potentially grab this lock, I (@ivoanjo) think we actually don't need it because:
119
- // * In the case where a thread owns the GVL and calls `gvl_owner`, it will always see the correct value. That's
153
+ // * In the case where a thread owns the GVL and calls `main_ractor_gvl_owner`, it will always see the correct value. That's
120
154
  // because every thread sets itself as the owner when it grabs the GVL and unsets itself at the end.
121
- // That means that `is_current_thread_holding_the_gvl` is always accurate.
155
+ // That means that `is_current_thread_in_main_ractor_and_holding_the_gvl` is always accurate.
122
156
  // * In a case where we observe a different thread, then this may change by the time we do something with this value
123
157
  // anyway. So unless we want to prevent the Ruby scheduler from switching threads, we need to deal with races here.
124
- current_gvl_owner gvl_owner(void) {
158
+ current_gvl_owner main_ractor_gvl_owner(void) {
159
+ if (private_vm_api_global.main_ractor_gvl_owner_context == NULL) return (current_gvl_owner) {.valid = false};
160
+
125
161
  const rb_thread_t *current_owner =
126
162
  #ifndef NO_RB_THREAD_SCHED // Introduced in Ruby 3.2 as a replacement for struct rb_global_vm_lock_struct
127
- ddtrace_get_ractor()->threads.sched.running;
163
+ private_vm_api_global.main_ractor_gvl_owner_context->threads.sched.running;
128
164
  #elif HAVE_RUBY_RACTOR_H
129
- ddtrace_get_ractor()->threads.gvl.owner;
165
+ private_vm_api_global.main_ractor_gvl_owner_context->threads.gvl.owner;
130
166
  #else
131
- GET_VM()->gvl.owner;
167
+ private_vm_api_global.main_ractor_gvl_owner_context->gvl.owner;
132
168
  #endif
133
169
 
134
170
  if (current_owner == NULL) {
@@ -149,8 +185,9 @@ current_gvl_owner gvl_owner(void) {
149
185
  #endif
150
186
  }
151
187
  #else
152
- current_gvl_owner gvl_owner(void) {
153
- rb_vm_t *vm = GET_VM();
188
+ current_gvl_owner main_ractor_gvl_owner(void) {
189
+ rb_vm_t *vm = private_vm_api_global.main_ractor_gvl_owner_context;
190
+ if (vm == NULL) return (current_gvl_owner) {.valid = false};
154
191
 
155
192
  // BIG Issue: Ruby < 2.6 did not have the owner field. The really nice thing about the owner field is that it's
156
193
  // "atomic" -- when a thread sets it, it "declares" two things in a single step
@@ -169,15 +206,15 @@ current_gvl_owner gvl_owner(void) {
169
206
  // * Thread A sets `running_thread` (`gvl.acquired == 1` + `running_thread == Thread A`)
170
207
  // * Thread A releases the GVL (`gvl.acquired == 0` + `running_thread == Thread A`)
171
208
  // * Thread B grabs the GVL (`gvl.acquired == 1` + `running_thread == Thread A`)
172
- // * Thread A calls gvl_owner. Due to the current state (`gvl.acquired == 1` + `running_thread == Thread A`), this
209
+ // * Thread A calls main_ractor_gvl_owner. Due to the current state (`gvl.acquired == 1` + `running_thread == Thread A`), this
173
210
  // function returns an incorrect result.
174
211
  // * Thread B finally sets `running_thread` (`gvl.acquired == 1` + `running_thread == Thread B`)
175
212
  //
176
- // This is especially problematic because we use `gvl_owner` to implement `is_current_thread_holding_the_gvl` which
213
+ // This is especially problematic because we use `main_ractor_gvl_owner` to implement `is_current_thread_in_main_ractor_and_holding_the_gvl` which
177
214
  // is called in a signal handler to decide "is it safe for me to call `rb_postponed_job_register_one` or not".
178
215
  // (See constraints in `collectors_cpu_and_wall_time_worker.c` comments for why).
179
216
  //
180
- // Thus an incorrect `is_current_thread_holding_the_gvl` result may lead to issues inside `rb_postponed_job_register_one`.
217
+ // Thus an incorrect `is_current_thread_in_main_ractor_and_holding_the_gvl` result may lead to issues inside `rb_postponed_job_register_one`.
181
218
  //
182
219
  // For this reason we default to use the "no signals workaround" on Ruby 2.5 by default, and we print a
183
220
  // warning when customers force-enable it.
@@ -278,6 +315,23 @@ VALUE thread_name_for(VALUE thread) {
278
315
  return thread_struct_from_object(thread)->name;
279
316
  }
280
317
 
318
+ // This test doubles as a validation for the layout of the rb_thread_t structure. Because the `name` is almost at
319
+ // the end of the structure, if somehow we can't read the name, this probably means we're getting the structure wrong
320
+ // and we should stop immediately (this includes covering the use of `stat_insn_usage` on Ruby <= 3.2).
321
+ //
322
+ // One example where this happens is in patches such as
323
+ // https://github.com/gitlabhq/omnibus-gitlab/blob/master/config/patches/ruby/thread-memory-allocations-3.2.patch
324
+ // that add items to the structure and thus shift its position from where we expect.
325
+ void self_test_thread_name_for(void) {
326
+ VALUE thread = rb_eval_string("Thread.new { Thread.current.name = 'dd-prof-test' }.join");
327
+ VALUE expected_name = rb_funcall(thread, rb_intern("name"), 0);
328
+ if (thread_name_for(thread) != expected_name) {
329
+ rb_raise(rb_eRuntimeError,
330
+ "thread_name_for() self-test failed; this usually means the `rb_thread_t` on this Ruby doesn't match what the profiler expects"
331
+ );
332
+ }
333
+ }
334
+
281
335
  // -----------------------------------------------------------------------------
282
336
  // The sources below are modified versions of code extracted from the Ruby project.
283
337
  // Each function is annotated with its origin, why we imported it, and the changes made.
@@ -356,7 +410,6 @@ calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id) {
356
410
  ptrdiff_t n = pc - ISEQ_BODY(iseq)->iseq_encoded;
357
411
  VM_ASSERT(n <= ISEQ_BODY(iseq)->iseq_size);
358
412
  VM_ASSERT(n >= 0);
359
- ASSUME(n >= 0);
360
413
  size_t pos = n; /* no overflow */
361
414
  if (LIKELY(pos)) {
362
415
  /* use pos-1 because PC points next instruction at the beginning of instruction */
@@ -539,7 +592,38 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
539
592
  cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
540
593
 
541
594
  for (i=0; i<limit && cfp != top_sentinel; cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) {
542
- if (cfp->iseq && !cfp->pc) {
595
+ const VALUE *pc = cfp->pc;
596
+ #ifdef HAVE_ZJIT_FRAME
597
+ const rb_iseq_t *iseq = cfp->_iseq;
598
+ #else
599
+ const rb_iseq_t *iseq = cfp->iseq;
600
+ #endif
601
+
602
+ #if defined(HAVE_ZJIT_FRAME) && USE_ZJIT
603
+ // Based on CFP_ZJIT_FRAME, which we currently can't use directly.
604
+ if (CFP_ZJIT_FRAME_P(cfp)) {
605
+ if ((VALUE)cfp->jit_return == ZJIT_JIT_RETURN_C_FRAME) {
606
+ // ZJIT uses this sentinel for C method frames, which have no Ruby ISEQ or bytecode PC.
607
+ // Set both to NULL explicitly because ZJIT leaves those fields untouched, so they may contain stale data.
608
+ iseq = NULL;
609
+ pc = NULL;
610
+ } else {
611
+ const zjit_jit_frame_t *jit_frame = (const zjit_jit_frame_t *)((VALUE *)cfp->jit_return)[-1];
612
+ iseq = jit_frame->iseq;
613
+ pc = jit_frame->pc;
614
+ }
615
+ }
616
+ #endif
617
+
618
+ #ifndef NO_T_MOVED
619
+ if (iseq && RB_TYPE_P((VALUE) iseq, T_MOVED)) {
620
+ // The profiler is not supposed to sample during GC compaction, so T_MOVED is not expected here.
621
+ // Yet, crash tracking also uses this walker and may run at any time. For now, we choose to skip these frames.
622
+ continue;
623
+ }
624
+ #endif
625
+
626
+ if (iseq && !pc) {
543
627
  // Fix: Do nothing -- this frame should not be used
544
628
  //
545
629
  // rb_profile_frames does not do this check, but `backtrace_each` (`vm_backtrace.c`) does. This frame is not
@@ -568,8 +652,8 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
568
652
 
569
653
  stack_buffer[i].same_frame =
570
654
  stack_buffer[i].is_ruby_frame &&
571
- stack_buffer[i].as.ruby_frame.iseq == cfp->iseq &&
572
- stack_buffer[i].as.ruby_frame.caching_pc == cfp->pc &&
655
+ stack_buffer[i].as.ruby_frame.iseq == iseq &&
656
+ stack_buffer[i].as.ruby_frame.caching_pc == pc &&
573
657
  stack_buffer[i].cme == cme;
574
658
 
575
659
  if (stack_buffer[i].same_frame) { // Nothing to do, buffer already contains this frame
@@ -577,8 +661,8 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
577
661
  continue;
578
662
  }
579
663
 
580
- stack_buffer[i].as.ruby_frame.iseq = cfp->iseq;
581
- stack_buffer[i].as.ruby_frame.caching_pc = (void *) cfp->pc;
664
+ stack_buffer[i].as.ruby_frame.iseq = iseq;
665
+ stack_buffer[i].as.ruby_frame.caching_pc = (void *) pc;
582
666
  stack_buffer[i].cme = cme;
583
667
 
584
668
  // The topmost frame may not have an updated PC because the JIT
@@ -589,10 +673,10 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
589
673
  if (cfp == top && cfp->jit_return) {
590
674
  stack_buffer[i].as.ruby_frame.line = 0;
591
675
  } else {
592
- stack_buffer[i].as.ruby_frame.line = calc_lineno(cfp->iseq, cfp->pc);
676
+ stack_buffer[i].as.ruby_frame.line = calc_lineno(iseq, pc);
593
677
  }
594
678
  #else // Ruby < 3.1
595
- stack_buffer[i].as.ruby_frame.line = calc_lineno(cfp->iseq, cfp->pc);
679
+ stack_buffer[i].as.ruby_frame.line = calc_lineno(iseq, pc);
596
680
  #endif
597
681
 
598
682
  stack_buffer[i].is_ruby_frame = true;
@@ -797,26 +881,11 @@ void self_test_mn_enabled(void) {
797
881
  #endif
798
882
  }
799
883
 
800
- // Taken from upstream imemo.h at commit 6ebcf25de2859b5b6402b7e8b181066c32d0e0bf (November 2023, master branch)
801
- // (See the Ruby project copyright and license above)
802
- // to enable calling rb_imemo_name
803
- //
804
- // Modifications:
805
- // * Added IMEMO_MASK define
806
- // * Changed return type to int to avoid having to define `enum imemo_type`
807
- static inline int ddtrace_imemo_type(VALUE imemo) {
808
- // This mask is the same between Ruby 2.5 and 3.3-preview3. Furthermore, the intention of this method is to be used
809
- // to call `rb_imemo_name` which correctly handles invalid numbers so even if the mask changes in the future, at most
810
- // we'll get incorrect results (and never a VM crash)
811
- #define IMEMO_MASK 0x0f
812
- return (RBASIC(imemo)->flags >> FL_USHIFT) & IMEMO_MASK;
813
- }
814
-
815
884
  // Safety: This function assumes the object passed in is of the imemo type. But in the worst case, you'll just get
816
885
  // a string that doesn't make any sense.
817
886
  #ifndef NO_IMEMO_NAME
818
887
  const char *imemo_kind(VALUE imemo) {
819
- return rb_imemo_name(ddtrace_imemo_type(imemo));
888
+ return rb_imemo_name(imemo_type(imemo));
820
889
  }
821
890
  #else
822
891
  const char *imemo_kind(__attribute__((unused)) VALUE imemo) {
@@ -39,12 +39,16 @@ typedef struct {
39
39
  } frame_info;
40
40
 
41
41
  rb_nativethread_id_t pthread_id_for(VALUE thread);
42
- bool is_current_thread_holding_the_gvl(void);
43
- current_gvl_owner gvl_owner(void);
42
+ // Must be called from the main ractor while holding the GVL
43
+ void private_vm_api_access_init(void);
44
+ void private_vm_api_access_self_test(void);
45
+ current_gvl_owner main_ractor_gvl_owner(void);
46
+ bool is_current_thread_in_main_ractor_and_holding_the_gvl(void);
44
47
  uint64_t native_thread_id_for(VALUE thread);
45
48
  void ddtrace_thread_list(VALUE result_array);
46
49
  bool is_thread_alive(VALUE thread);
47
50
  VALUE thread_name_for(VALUE thread);
51
+ void self_test_thread_name_for(void);
48
52
 
49
53
  int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *stack_buffer);
50
54
 
@@ -37,9 +37,9 @@ static void *trigger_grab_gvl_and_raise_value_arg(void *trigger_args);
37
37
  static VALUE _native_grab_gvl_and_raise_syserr(DDTRACE_UNUSED VALUE _self, VALUE syserr_errno, VALUE test_message, VALUE test_message_arg, VALUE release_gvl);
38
38
  static void *trigger_grab_gvl_and_raise_syserr(void *trigger_args);
39
39
  static VALUE _native_ddtrace_rb_ractor_main_p(DDTRACE_UNUSED VALUE _self);
40
- static VALUE _native_is_current_thread_holding_the_gvl(DDTRACE_UNUSED VALUE _self);
41
- static VALUE _native_release_gvl_and_call_is_current_thread_holding_the_gvl(DDTRACE_UNUSED VALUE _self);
42
- static void *testing_is_current_thread_holding_the_gvl(DDTRACE_UNUSED void *_unused);
40
+ static VALUE _native_is_current_thread_in_main_ractor_and_holding_the_gvl(DDTRACE_UNUSED VALUE _self);
41
+ static VALUE _native_release_gvl_and_call_is_current_thread_in_main_ractor_and_holding_the_gvl(DDTRACE_UNUSED VALUE _self);
42
+ static void *testing_is_current_thread_in_main_ractor_and_holding_the_gvl(void *unused);
43
43
  static VALUE _native_install_holding_the_gvl_signal_handler(DDTRACE_UNUSED VALUE _self);
44
44
  static void holding_the_gvl_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
45
45
  static VALUE _native_trigger_holding_the_gvl_signal_handler_on(DDTRACE_UNUSED VALUE _self, VALUE background_thread);
@@ -66,6 +66,7 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
66
66
  rb_define_singleton_method(native_extension_module, "native_working?", native_working_p, 0);
67
67
  rb_funcall(native_extension_module, rb_intern("private_class_method"), 1, ID2SYM(rb_intern("native_working?")));
68
68
 
69
+ private_vm_api_access_init();
69
70
  ruby_helpers_init();
70
71
  setup_signal_handler_init(profiling_module);
71
72
  collectors_cpu_and_wall_time_worker_init(profiling_module);
@@ -88,11 +89,11 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
88
89
  rb_define_singleton_method(testing_module, "_native_grab_gvl_and_raise_value_arg", _native_grab_gvl_and_raise_value_arg, 3);
89
90
  rb_define_singleton_method(testing_module, "_native_grab_gvl_and_raise_syserr", _native_grab_gvl_and_raise_syserr, 4);
90
91
  rb_define_singleton_method(testing_module, "_native_ddtrace_rb_ractor_main_p", _native_ddtrace_rb_ractor_main_p, 0);
91
- rb_define_singleton_method(testing_module, "_native_is_current_thread_holding_the_gvl", _native_is_current_thread_holding_the_gvl, 0);
92
+ rb_define_singleton_method(testing_module, "_native_is_current_thread_in_main_ractor_and_holding_the_gvl", _native_is_current_thread_in_main_ractor_and_holding_the_gvl, 0);
92
93
  rb_define_singleton_method(
93
94
  testing_module,
94
- "_native_release_gvl_and_call_is_current_thread_holding_the_gvl",
95
- _native_release_gvl_and_call_is_current_thread_holding_the_gvl,
95
+ "_native_release_gvl_and_call_is_current_thread_in_main_ractor_and_holding_the_gvl",
96
+ _native_release_gvl_and_call_is_current_thread_in_main_ractor_and_holding_the_gvl,
96
97
  0
97
98
  );
98
99
  rb_define_singleton_method(testing_module, "_native_install_holding_the_gvl_signal_handler", _native_install_holding_the_gvl_signal_handler, 0);
@@ -103,6 +104,8 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
103
104
  }
104
105
 
105
106
  static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) {
107
+ private_vm_api_access_self_test();
108
+ self_test_thread_name_for();
106
109
  self_test_clock_id();
107
110
  self_test_current_fiber_for();
108
111
  self_test_mn_enabled();
@@ -218,41 +221,37 @@ static VALUE _native_ddtrace_rb_ractor_main_p(DDTRACE_UNUSED VALUE _self) {
218
221
  return ddtrace_rb_ractor_main_p() ? Qtrue : Qfalse;
219
222
  }
220
223
 
221
- static VALUE _native_is_current_thread_holding_the_gvl(DDTRACE_UNUSED VALUE _self) {
222
- return ((bool) testing_is_current_thread_holding_the_gvl(NULL)) ? Qtrue : Qfalse;
224
+ static VALUE _native_is_current_thread_in_main_ractor_and_holding_the_gvl(DDTRACE_UNUSED VALUE _self) {
225
+ return is_current_thread_in_main_ractor_and_holding_the_gvl() ? Qtrue : Qfalse;
223
226
  }
224
227
 
225
- static VALUE _native_release_gvl_and_call_is_current_thread_holding_the_gvl(DDTRACE_UNUSED VALUE _self) {
226
- return ((bool) rb_thread_call_without_gvl(testing_is_current_thread_holding_the_gvl, NULL, NULL, NULL)) ? Qtrue : Qfalse;
228
+ static VALUE _native_release_gvl_and_call_is_current_thread_in_main_ractor_and_holding_the_gvl(DDTRACE_UNUSED VALUE _self) {
229
+ return ((bool) rb_thread_call_without_gvl(testing_is_current_thread_in_main_ractor_and_holding_the_gvl, NULL, NULL, NULL)) ? Qtrue : Qfalse;
227
230
  }
228
231
 
229
- static void *testing_is_current_thread_holding_the_gvl(DDTRACE_UNUSED void *_unused) {
230
- return (void *) is_current_thread_holding_the_gvl();
231
- }
232
-
233
- static VALUE _native_install_holding_the_gvl_signal_handler(DDTRACE_UNUSED VALUE _self) {
234
- install_sigprof_signal_handler(holding_the_gvl_signal_handler, "holding_the_gvl_signal_handler");
235
- return Qtrue;
232
+ static void *testing_is_current_thread_in_main_ractor_and_holding_the_gvl(DDTRACE_UNUSED void *unused) {
233
+ return (void *) is_current_thread_in_main_ractor_and_holding_the_gvl();
236
234
  }
237
235
 
238
236
  static pthread_mutex_t holding_the_gvl_signal_handler_mutex = PTHREAD_MUTEX_INITIALIZER;
239
237
  static pthread_cond_t holding_the_gvl_signal_handler_executed = PTHREAD_COND_INITIALIZER;
240
238
  static VALUE holding_the_gvl_signal_handler_result[3];
241
239
 
242
- // Ruby VM API that is exported but not present in the header files. Only used by holding_the_gvl_signal_handler below and SHOULD NOT
243
- // be used in any other situation. See the comments on is_current_thread_holding_the_gvl for details.
244
- int ruby_thread_has_gvl_p(void);
240
+ static VALUE _native_install_holding_the_gvl_signal_handler(DDTRACE_UNUSED VALUE _self) {
241
+ install_sigprof_signal_handler(holding_the_gvl_signal_handler, "holding_the_gvl_signal_handler");
242
+ return Qtrue;
243
+ }
245
244
 
246
245
  static void holding_the_gvl_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext) {
247
246
  pthread_mutex_lock(&holding_the_gvl_signal_handler_mutex);
248
247
 
249
248
  VALUE test_executed = Qtrue;
250
249
  VALUE ruby_thread_has_gvl_p_result = ruby_thread_has_gvl_p() ? Qtrue : Qfalse;
251
- VALUE is_current_thread_holding_the_gvl_result = is_current_thread_holding_the_gvl() ? Qtrue : Qfalse;
250
+ VALUE is_current_thread_in_main_ractor_and_holding_the_gvl_result = is_current_thread_in_main_ractor_and_holding_the_gvl() ? Qtrue : Qfalse;
252
251
 
253
252
  holding_the_gvl_signal_handler_result[0] = test_executed;
254
253
  holding_the_gvl_signal_handler_result[1] = ruby_thread_has_gvl_p_result;
255
- holding_the_gvl_signal_handler_result[2] = is_current_thread_holding_the_gvl_result;
254
+ holding_the_gvl_signal_handler_result[2] = is_current_thread_in_main_ractor_and_holding_the_gvl_result;
256
255
 
257
256
  pthread_cond_broadcast(&holding_the_gvl_signal_handler_executed);
258
257
  pthread_mutex_unlock(&holding_the_gvl_signal_handler_mutex);
@@ -298,7 +297,7 @@ static VALUE _native_trigger_holding_the_gvl_signal_handler_on(DDTRACE_UNUSED VA
298
297
 
299
298
  VALUE result = rb_hash_new();
300
299
  rb_hash_aset(result, ID2SYM(rb_intern("ruby_thread_has_gvl_p")), holding_the_gvl_signal_handler_result[1]);
301
- rb_hash_aset(result, ID2SYM(rb_intern("is_current_thread_holding_the_gvl")), holding_the_gvl_signal_handler_result[2]);
300
+ rb_hash_aset(result, ID2SYM(rb_intern("is_current_thread_in_main_ractor_and_holding_the_gvl")), holding_the_gvl_signal_handler_result[2]);
302
301
  return result;
303
302
  }
304
303
 
@@ -66,7 +66,9 @@ void private_grab_gvl_and_raise(VALUE exception_class, int syserr_errno, const c
66
66
  args.format_string = format_string;
67
67
  va_start(args.va_args, format_string);
68
68
 
69
- if (is_current_thread_holding_the_gvl()) {
69
+ // Callers run with the GVL or inside an explicit no-GVL region, so Ruby's blocking-region check is sufficient.
70
+ // We don't run in signal handlers or scheduler hooks, so we don't need is_current_thread_in_main_ractor_and_holding_the_gvl().
71
+ if (ruby_thread_has_gvl_p()) {
70
72
  VALUE detailed_message = rb_vsprintf(format_string, args.va_args);
71
73
  va_end(args.va_args);
72
74
 
@@ -4,6 +4,10 @@
4
4
  #include <stdarg.h>
5
5
  #include "datadog_ruby_common.h"
6
6
 
7
+ // Public since Ruby 4.0 (https://bugs.ruby-lang.org/issues/20877); older Rubies export it without a header declaration.
8
+ // Be careful about using this in the profiler; see notes on is_current_thread_in_main_ractor_and_holding_the_gvl().
9
+ int ruby_thread_has_gvl_p(void);
10
+
7
11
  // Initialize internal data needed by some ruby helpers. Should be called during start, before any actual
8
12
  // usage of ruby helpers.
9
13
  void ruby_helpers_init(void);
@@ -36,8 +36,6 @@ static void install_sigprof_signal_handler_internal(
36
36
  ) {
37
37
  struct sigaction existing_signal_handler_config = {.sa_sigaction = NULL};
38
38
  struct sigaction signal_handler_config = {
39
- // NOTE: Deliberately not SA_ONSTACK. `handle_sampling_signal` uses "am I running on the alternate signal stack?"
40
- // to detect that if it interrupted another signal handler from the Ruby VM, see `is_running_on_alternate_signal_stack` for details.
41
39
  .sa_flags = SA_RESTART | SA_SIGINFO,
42
40
  .sa_sigaction = signal_handler_function
43
41
  };
@@ -9,7 +9,6 @@
9
9
  #include "time_helpers.h"
10
10
  #include "heap_recorder.h"
11
11
  #include "encoded_profile.h"
12
- #include "collectors_thread_context.h"
13
12
 
14
13
  // Used to wrap a ddog_prof_Profile in a Ruby object and expose Ruby-level serialization APIs
15
14
  // This file implements the native bits of the Datadog::Profiling::StackRecorder class
@@ -178,10 +177,6 @@ typedef struct {
178
177
  heap_recorder *heap_recorder;
179
178
  bool heap_clean_after_gc_enabled;
180
179
 
181
- // When set, _native_serialize will call thread_context_collector_on_serialize on this instance
182
- // before serializing, so that threads suspended across the whole profile period still get sampled.
183
- VALUE thread_context_collector_instance;
184
-
185
180
  pthread_mutex_t mutex_slot_one;
186
181
  profile_slot profile_slot_one;
187
182
  pthread_mutex_t mutex_slot_two;
@@ -343,7 +338,6 @@ static VALUE _native_new(VALUE klass) {
343
338
  // being leaked.
344
339
 
345
340
  state->heap_clean_after_gc_enabled = false;
346
- state->thread_context_collector_instance = Qnil;
347
341
 
348
342
  ddog_prof_Slice_SampleType sample_types = {.ptr = all_sample_types, .len = ALL_VALUE_TYPES_COUNT};
349
343
 
@@ -415,7 +409,6 @@ static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_Sam
415
409
  static void stack_recorder_typed_data_mark(void *state_ptr) {
416
410
  stack_recorder_state *state = (stack_recorder_state *) state_ptr;
417
411
 
418
- rb_gc_mark(state->thread_context_collector_instance);
419
412
  heap_recorder_mark(state->heap_recorder);
420
413
  }
421
414
 
@@ -539,21 +532,17 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
539
532
  stack_recorder_state *state;
540
533
  TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
541
534
 
542
- ddog_Timespec finish_timestamp = system_epoch_now_timespec();
543
- // Need to do this while still holding the Global VM Lock; see comments on method for why
544
- serializer_set_start_timestamp_for_next_profile(state, finish_timestamp);
545
-
546
- if (state->thread_context_collector_instance != Qnil) {
547
- thread_context_collector_on_serialize(state->thread_context_collector_instance);
548
- }
549
-
550
- long heap_iteration_prep_start_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
535
+ long heap_iteration_prep_start_time_ns = monotonic_wall_time_now_ns();
551
536
  // Prepare the iteration on heap recorder we'll be doing outside the GVL. The preparation needs to
552
537
  // happen while holding the GVL.
553
538
  // NOTE: While rare, it's possible for the GVL to be released inside this function (see comments on `heap_recorder_update`)
554
539
  // and thus don't assume this is an "atomic" step -- other threads may get some running time in the meanwhile.
555
540
  heap_recorder_prepare_iteration(state->heap_recorder);
556
- long heap_iteration_prep_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - heap_iteration_prep_start_time_ns;
541
+ long heap_iteration_prep_time_ns = monotonic_wall_time_now_ns() - heap_iteration_prep_start_time_ns;
542
+
543
+ ddog_Timespec finish_timestamp = system_epoch_now_timespec();
544
+ // Need to do this while still holding the Global VM Lock; see comments on method for why
545
+ serializer_set_start_timestamp_for_next_profile(state, finish_timestamp);
557
546
 
558
547
  // We'll release the Global VM Lock while we're calling serialize, so that the Ruby VM can continue to work while this
559
548
  // is pending
@@ -811,7 +800,7 @@ static void build_heap_profile_without_gvl(stack_recorder_state *state, profile_
811
800
  static void *call_serialize_without_gvl(void *call_args) {
812
801
  call_serialize_without_gvl_arguments *args = (call_serialize_without_gvl_arguments *) call_args;
813
802
 
814
- long serialize_no_gvl_start_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
803
+ long serialize_no_gvl_start_time_ns = monotonic_wall_time_now_ns();
815
804
 
816
805
  profile_slot *slot_now_inactive = serializer_flip_active_and_inactive_slots(args->state);
817
806
  args->slot = slot_now_inactive;
@@ -819,13 +808,13 @@ static void *call_serialize_without_gvl(void *call_args) {
819
808
  // Now that we have the inactive profile with all but heap samples, lets fill it with heap data
820
809
  // without needing to race with the active sampler
821
810
  build_heap_profile_without_gvl(args->state, args->slot);
822
- args->heap_profile_build_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - serialize_no_gvl_start_time_ns;
811
+ args->heap_profile_build_time_ns = monotonic_wall_time_now_ns() - serialize_no_gvl_start_time_ns;
823
812
 
824
813
  // Note: The profile gets reset by the serialize call
825
814
  args->result = ddog_prof_Profile_serialize(&args->slot->profile, &args->slot->start_timestamp, &args->finish_timestamp);
826
815
  args->advance_gen_result = ddog_prof_ManagedStringStorage_advance_gen(args->state->string_storage);
827
816
  args->serialize_ran = true;
828
- args->serialize_no_gvl_time_ns = long_max_of(0, monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - serialize_no_gvl_start_time_ns);
817
+ args->serialize_no_gvl_time_ns = long_max_of(0, monotonic_wall_time_now_ns() - serialize_no_gvl_start_time_ns);
829
818
 
830
819
  return NULL; // Unused
831
820
  }
@@ -925,7 +914,7 @@ static VALUE test_slot_mutex_state(VALUE recorder_instance, int slot) {
925
914
  }
926
915
 
927
916
  static ddog_Timespec system_epoch_now_timespec(void) {
928
- long now_ns = system_epoch_time_now_ns(RAISE_ON_FAILURE);
917
+ long now_ns = system_epoch_time_now_ns();
929
918
  return (ddog_Timespec) {.seconds = now_ns / SECONDS_AS_NS(1), .nanoseconds = now_ns % SECONDS_AS_NS(1)};
930
919
  }
931
920
 
@@ -1206,10 +1195,3 @@ static VALUE _native_commit_heap_recordings(DDTRACE_UNUSED VALUE _self, VALUE re
1206
1195
 
1207
1196
  return Qtrue;
1208
1197
  }
1209
-
1210
- void recorder_install_on_serialize(VALUE recorder_instance, VALUE thread_context_collector_instance) {
1211
- stack_recorder_state *state;
1212
- TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
1213
-
1214
- state->thread_context_collector_instance = enforce_thread_context_collector_instance(thread_context_collector_instance);
1215
- }
@@ -37,5 +37,4 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
37
37
  void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_CharSlice endpoint);
38
38
  void recorder_commit_heap_recordings_may_lose_gvl(VALUE recorder_instance);
39
39
  void recorder_heap_update_may_lose_gvl(VALUE recorder_instance);
40
- void recorder_install_on_serialize(VALUE recorder_instance, VALUE thread_context_collector_instance);
41
40
  VALUE enforce_recorder_instance(VALUE object);