datadog 2.2.0 → 2.4.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 (196) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -2
  3. data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
  4. data/ext/datadog_profiling_loader/extconf.rb +14 -26
  5. data/ext/datadog_profiling_native_extension/clock_id.h +1 -0
  6. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -2
  7. data/ext/datadog_profiling_native_extension/clock_id_noop.c +1 -2
  8. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +257 -69
  9. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +53 -28
  10. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +34 -4
  11. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +4 -0
  12. data/ext/datadog_profiling_native_extension/collectors_stack.c +136 -81
  13. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  14. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +661 -48
  15. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +10 -1
  16. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +83 -0
  17. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +53 -0
  18. data/ext/datadog_profiling_native_extension/extconf.rb +91 -69
  19. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
  20. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
  21. data/ext/datadog_profiling_native_extension/heap_recorder.c +54 -12
  22. data/ext/datadog_profiling_native_extension/heap_recorder.h +3 -1
  23. data/ext/datadog_profiling_native_extension/helpers.h +6 -17
  24. data/ext/datadog_profiling_native_extension/http_transport.c +41 -9
  25. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +0 -86
  26. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +2 -23
  27. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +61 -172
  28. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +116 -139
  29. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +20 -11
  30. data/ext/datadog_profiling_native_extension/profiling.c +1 -3
  31. data/ext/datadog_profiling_native_extension/ruby_helpers.c +0 -33
  32. data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -26
  33. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -0
  34. data/ext/datadog_profiling_native_extension/stack_recorder.c +14 -2
  35. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
  36. data/ext/datadog_profiling_native_extension/time_helpers.c +0 -15
  37. data/ext/datadog_profiling_native_extension/time_helpers.h +36 -6
  38. data/ext/{datadog_profiling_native_extension → libdatadog_api}/crashtracker.c +37 -22
  39. data/ext/libdatadog_api/datadog_ruby_common.c +83 -0
  40. data/ext/libdatadog_api/datadog_ruby_common.h +53 -0
  41. data/ext/libdatadog_api/extconf.rb +108 -0
  42. data/ext/libdatadog_api/macos_development.md +26 -0
  43. data/ext/libdatadog_extconf_helpers.rb +130 -0
  44. data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
  45. data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
  46. data/lib/datadog/appsec/component.rb +29 -8
  47. data/lib/datadog/appsec/configuration/settings.rb +2 -2
  48. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
  49. data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
  50. data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
  51. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +35 -0
  52. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +109 -0
  53. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +71 -0
  54. data/lib/datadog/appsec/contrib/graphql/integration.rb +54 -0
  55. data/lib/datadog/appsec/contrib/graphql/patcher.rb +37 -0
  56. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +59 -0
  57. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +3 -6
  58. data/lib/datadog/appsec/event.rb +1 -1
  59. data/lib/datadog/appsec/processor/actions.rb +1 -1
  60. data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
  61. data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
  62. data/lib/datadog/appsec/processor.rb +36 -37
  63. data/lib/datadog/appsec/rate_limiter.rb +25 -40
  64. data/lib/datadog/appsec/remote.rb +7 -3
  65. data/lib/datadog/appsec/response.rb +15 -1
  66. data/lib/datadog/appsec.rb +3 -2
  67. data/lib/datadog/core/configuration/components.rb +18 -15
  68. data/lib/datadog/core/configuration/settings.rb +135 -9
  69. data/lib/datadog/core/crashtracking/agent_base_url.rb +21 -0
  70. data/lib/datadog/core/crashtracking/component.rb +111 -0
  71. data/lib/datadog/core/crashtracking/tag_builder.rb +39 -0
  72. data/lib/datadog/core/diagnostics/environment_logger.rb +8 -11
  73. data/lib/datadog/core/environment/execution.rb +5 -5
  74. data/lib/datadog/core/metrics/client.rb +7 -0
  75. data/lib/datadog/core/rate_limiter.rb +183 -0
  76. data/lib/datadog/core/remote/client/capabilities.rb +4 -3
  77. data/lib/datadog/core/remote/component.rb +4 -2
  78. data/lib/datadog/core/remote/negotiation.rb +4 -4
  79. data/lib/datadog/core/remote/tie.rb +2 -0
  80. data/lib/datadog/core/runtime/metrics.rb +1 -1
  81. data/lib/datadog/core/telemetry/component.rb +51 -2
  82. data/lib/datadog/core/telemetry/emitter.rb +9 -11
  83. data/lib/datadog/core/telemetry/event.rb +37 -1
  84. data/lib/datadog/core/telemetry/ext.rb +1 -0
  85. data/lib/datadog/core/telemetry/http/adapters/net.rb +10 -12
  86. data/lib/datadog/core/telemetry/http/ext.rb +3 -0
  87. data/lib/datadog/core/telemetry/http/transport.rb +38 -9
  88. data/lib/datadog/core/telemetry/logger.rb +51 -0
  89. data/lib/datadog/core/telemetry/logging.rb +71 -0
  90. data/lib/datadog/core/telemetry/request.rb +13 -1
  91. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
  92. data/lib/datadog/core/utils/time.rb +12 -0
  93. data/lib/datadog/di/code_tracker.rb +168 -0
  94. data/lib/datadog/di/configuration/settings.rb +163 -0
  95. data/lib/datadog/di/configuration.rb +11 -0
  96. data/lib/datadog/di/error.rb +31 -0
  97. data/lib/datadog/di/extensions.rb +16 -0
  98. data/lib/datadog/di/probe.rb +133 -0
  99. data/lib/datadog/di/probe_builder.rb +41 -0
  100. data/lib/datadog/di/redactor.rb +188 -0
  101. data/lib/datadog/di/serializer.rb +193 -0
  102. data/lib/datadog/di.rb +14 -0
  103. data/lib/datadog/kit/appsec/events.rb +2 -4
  104. data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
  105. data/lib/datadog/opentelemetry/sdk/span_processor.rb +10 -0
  106. data/lib/datadog/opentelemetry/sdk/trace/span.rb +23 -0
  107. data/lib/datadog/profiling/collectors/code_provenance.rb +7 -7
  108. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +28 -26
  109. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
  110. data/lib/datadog/profiling/collectors/info.rb +15 -6
  111. data/lib/datadog/profiling/collectors/thread_context.rb +30 -2
  112. data/lib/datadog/profiling/component.rb +89 -95
  113. data/lib/datadog/profiling/exporter.rb +3 -3
  114. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
  115. data/lib/datadog/profiling/ext.rb +21 -21
  116. data/lib/datadog/profiling/flush.rb +1 -1
  117. data/lib/datadog/profiling/http_transport.rb +14 -7
  118. data/lib/datadog/profiling/load_native_extension.rb +5 -5
  119. data/lib/datadog/profiling/preload.rb +1 -1
  120. data/lib/datadog/profiling/profiler.rb +5 -8
  121. data/lib/datadog/profiling/scheduler.rb +33 -25
  122. data/lib/datadog/profiling/stack_recorder.rb +3 -0
  123. data/lib/datadog/profiling/tag_builder.rb +2 -2
  124. data/lib/datadog/profiling/tasks/exec.rb +5 -5
  125. data/lib/datadog/profiling/tasks/setup.rb +16 -35
  126. data/lib/datadog/profiling.rb +4 -5
  127. data/lib/datadog/single_step_instrument.rb +12 -0
  128. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
  129. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
  130. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
  131. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
  132. data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
  133. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
  134. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
  135. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +4 -1
  136. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
  137. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
  138. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
  139. data/lib/datadog/tracing/contrib/ext.rb +14 -0
  140. data/lib/datadog/tracing/contrib/faraday/middleware.rb +9 -0
  141. data/lib/datadog/tracing/contrib/grape/endpoint.rb +19 -0
  142. data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
  143. data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
  144. data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
  145. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +14 -10
  146. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +10 -4
  147. data/lib/datadog/tracing/contrib/http/instrumentation.rb +18 -15
  148. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +6 -5
  149. data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
  150. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +5 -0
  151. data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
  152. data/lib/datadog/tracing/contrib/lograge/patcher.rb +15 -0
  153. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
  154. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +5 -0
  155. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +17 -13
  156. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
  157. data/lib/datadog/tracing/contrib/patcher.rb +2 -1
  158. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
  159. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
  160. data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
  161. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +28 -0
  162. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
  163. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +22 -10
  164. data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
  165. data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
  166. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
  167. data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
  168. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +5 -0
  169. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -1
  170. data/lib/datadog/tracing/diagnostics/environment_logger.rb +14 -16
  171. data/lib/datadog/tracing/distributed/propagation.rb +7 -0
  172. data/lib/datadog/tracing/metadata/errors.rb +9 -1
  173. data/lib/datadog/tracing/metadata/ext.rb +6 -0
  174. data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
  175. data/lib/datadog/tracing/remote.rb +5 -2
  176. data/lib/datadog/tracing/sampling/matcher.rb +6 -1
  177. data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
  178. data/lib/datadog/tracing/sampling/rule.rb +2 -0
  179. data/lib/datadog/tracing/sampling/rule_sampler.rb +9 -5
  180. data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
  181. data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
  182. data/lib/datadog/tracing/span.rb +9 -2
  183. data/lib/datadog/tracing/span_event.rb +41 -0
  184. data/lib/datadog/tracing/span_operation.rb +6 -2
  185. data/lib/datadog/tracing/trace_operation.rb +26 -2
  186. data/lib/datadog/tracing/tracer.rb +14 -12
  187. data/lib/datadog/tracing/transport/http/client.rb +1 -0
  188. data/lib/datadog/tracing/transport/io/client.rb +1 -0
  189. data/lib/datadog/tracing/transport/serializable_trace.rb +3 -0
  190. data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
  191. data/lib/datadog/tracing/workers.rb +1 -1
  192. data/lib/datadog/version.rb +1 -1
  193. metadata +46 -11
  194. data/lib/datadog/profiling/crashtracker.rb +0 -91
  195. data/lib/datadog/profiling/ext/forking.rb +0 -98
  196. data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -20,7 +20,9 @@
20
20
  #define ERR_CLOCK_FAIL "failed to get clock time"
21
21
 
22
22
  // Maximum allowed value for an allocation weight. Attempts to use higher values will result in clamping.
23
- unsigned int MAX_ALLOC_WEIGHT = 65535;
23
+ // See https://docs.google.com/document/d/1lWLB714wlLBBq6T4xZyAc4a5wtWhSmr4-hgiPKeErlA/edit#heading=h.ugp0zxcj5iqh
24
+ // (Datadog-only link) for research backing the choice of this value.
25
+ unsigned int MAX_ALLOC_WEIGHT = 10000;
24
26
 
25
27
  // Used to trigger the execution of Collectors::ThreadState, which implements all of the sampling logic
26
28
  // itself; this class only implements the "when to do it" part.
@@ -86,6 +88,7 @@ unsigned int MAX_ALLOC_WEIGHT = 65535;
86
88
  // `collectors_cpu_and_wall_time_worker_init` below and always get reused after that.
87
89
  static rb_postponed_job_handle_t sample_from_postponed_job_handle;
88
90
  static rb_postponed_job_handle_t after_gc_from_postponed_job_handle;
91
+ static rb_postponed_job_handle_t after_gvl_running_from_postponed_job_handle;
89
92
  #endif
90
93
 
91
94
  // Contains state for a single CpuAndWallTimeWorker instance
@@ -96,6 +99,8 @@ struct cpu_and_wall_time_worker_state {
96
99
  bool no_signals_workaround_enabled;
97
100
  bool dynamic_sampling_rate_enabled;
98
101
  bool allocation_profiling_enabled;
102
+ bool allocation_counting_enabled;
103
+ bool gvl_profiling_enabled;
99
104
  bool skip_idle_samples_for_testing;
100
105
  VALUE self_instance;
101
106
  VALUE thread_context_collector_instance;
@@ -104,7 +109,6 @@ struct cpu_and_wall_time_worker_state {
104
109
  dynamic_sampling_rate_state cpu_dynamic_sampling_rate;
105
110
  discrete_dynamic_sampler allocation_sampler;
106
111
  VALUE gc_tracepoint; // Used to get gc start/finish information
107
- VALUE object_allocation_tracepoint; // Used to get allocation counts and allocation profiling
108
112
 
109
113
  // These are mutable and used to signal things between the worker thread and other threads
110
114
 
@@ -117,10 +121,15 @@ struct cpu_and_wall_time_worker_state {
117
121
 
118
122
  // Others
119
123
 
120
- // Used to detect/avoid nested sampling, e.g. when the object_allocation_tracepoint gets triggered by a memory allocation
124
+ // Used to detect/avoid nested sampling, e.g. when on_newobj_event gets triggered by a memory allocation
121
125
  // that happens during another sample.
122
126
  bool during_sample;
123
127
 
128
+ #ifndef NO_GVL_INSTRUMENTATION
129
+ // Only set when sampling is active (gets created at start and cleaned on stop)
130
+ rb_internal_thread_event_hook_t *gvl_profiling_hook;
131
+ #endif
132
+
124
133
  struct stats {
125
134
  // # Generic stats
126
135
  // How many times we tried to trigger a sample
@@ -167,22 +176,15 @@ struct cpu_and_wall_time_worker_state {
167
176
  uint64_t allocation_sampling_time_ns_total;
168
177
  // How many times we saw allocations being done inside a sample
169
178
  unsigned int allocations_during_sample;
179
+
180
+ // # GVL profiling stats
181
+ // How many times we triggered the after_gvl_running sampling
182
+ unsigned int after_gvl_running;
170
183
  } stats;
171
184
  };
172
185
 
173
186
  static VALUE _native_new(VALUE klass);
174
- static VALUE _native_initialize(
175
- DDTRACE_UNUSED VALUE _self,
176
- VALUE self_instance,
177
- VALUE thread_context_collector_instance,
178
- VALUE gc_profiling_enabled,
179
- VALUE idle_sampling_helper_instance,
180
- VALUE no_signals_workaround_enabled,
181
- VALUE dynamic_sampling_rate_enabled,
182
- VALUE dynamic_sampling_rate_overhead_target_percentage,
183
- VALUE allocation_profiling_enabled,
184
- VALUE skip_idle_samples_for_testing
185
- );
187
+ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
186
188
  static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr);
187
189
  static VALUE _native_sampling_loop(VALUE self, VALUE instance);
188
190
  static VALUE _native_stop(DDTRACE_UNUSED VALUE _self, VALUE self_instance, VALUE worker_thread);
@@ -216,7 +218,7 @@ static void grab_gvl_and_sample(void);
216
218
  static void reset_stats_not_thread_safe(struct cpu_and_wall_time_worker_state *state);
217
219
  static void sleep_for(uint64_t time_ns);
218
220
  static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self);
219
- static void on_newobj_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused);
221
+ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *unused2);
220
222
  static void disable_tracepoints(struct cpu_and_wall_time_worker_state *state);
221
223
  static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self);
222
224
  static VALUE rescued_sample_allocation(VALUE tracepoint_data);
@@ -224,6 +226,25 @@ static void delayed_error(struct cpu_and_wall_time_worker_state *state, const ch
224
226
  static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg);
225
227
  static VALUE _native_hold_signals(DDTRACE_UNUSED VALUE self);
226
228
  static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self);
229
+ #ifndef NO_GVL_INSTRUMENTATION
230
+ static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused);
231
+ static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused);
232
+ #endif
233
+ static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE instance);
234
+
235
+ // We're using `on_newobj_event` function with `rb_add_event_hook2`, which requires in its public signature a function
236
+ // with signature `rb_event_hook_func_t` which doesn't match `on_newobj_event`.
237
+ //
238
+ // But in practice, because we pass the `RUBY_EVENT_HOOK_FLAG_RAW_ARG` flag to `rb_add_event_hook2`, it casts the
239
+ // expected signature into a `rb_event_hook_raw_arg_func_t`:
240
+ // > typedef void (*rb_event_hook_raw_arg_func_t)(VALUE data, const rb_trace_arg_t *arg); (from vm_trace.c)
241
+ // which does match `on_newobj_event`.
242
+ //
243
+ // So TL;DR we're just doing this here to avoid the warning and explain why the apparent mismatch in function signatures.
244
+ #pragma GCC diagnostic push
245
+ #pragma GCC diagnostic ignored "-Wcast-function-type"
246
+ static const rb_event_hook_func_t on_newobj_event_as_hook = (rb_event_hook_func_t) on_newobj_event;
247
+ #pragma GCC diagnostic pop
227
248
 
228
249
  // Note on sampler global state safety:
229
250
  //
@@ -255,8 +276,13 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
255
276
  int unused_flags = 0;
256
277
  sample_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, sample_from_postponed_job, NULL);
257
278
  after_gc_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_gc_from_postponed_job, NULL);
279
+ after_gvl_running_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_gvl_running_from_postponed_job, NULL);
258
280
 
259
- if (sample_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID || after_gc_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID) {
281
+ if (
282
+ sample_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
283
+ after_gc_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
284
+ after_gvl_running_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID
285
+ ) {
260
286
  rb_raise(rb_eRuntimeError, "Failed to register profiler postponed jobs (got POSTPONED_JOB_HANDLE_INVALID)");
261
287
  }
262
288
  #else
@@ -278,7 +304,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
278
304
  // https://bugs.ruby-lang.org/issues/18007 for a discussion around this.
279
305
  rb_define_alloc_func(collectors_cpu_and_wall_time_worker_class, _native_new);
280
306
 
281
- rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_initialize", _native_initialize, 9);
307
+ rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_initialize", _native_initialize, -1);
282
308
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_sampling_loop", _native_sampling_loop, 1);
283
309
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_stop", _native_stop, 2);
284
310
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
@@ -300,6 +326,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
300
326
  rb_define_singleton_method(testing_module, "_native_is_sigprof_blocked_in_current_thread", _native_is_sigprof_blocked_in_current_thread, 0);
301
327
  rb_define_singleton_method(testing_module, "_native_with_blocked_sigprof", _native_with_blocked_sigprof, 0);
302
328
  rb_define_singleton_method(testing_module, "_native_delayed_error", _native_delayed_error, 2);
329
+ rb_define_singleton_method(testing_module, "_native_gvl_profiling_hook_active", _native_gvl_profiling_hook_active, 1);
303
330
  }
304
331
 
305
332
  // This structure is used to define a Ruby object that stores a pointer to a struct cpu_and_wall_time_worker_state
@@ -316,6 +343,8 @@ static const rb_data_type_t cpu_and_wall_time_worker_typed_data = {
316
343
  };
317
344
 
318
345
  static VALUE _native_new(VALUE klass) {
346
+ long now = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
347
+
319
348
  struct cpu_and_wall_time_worker_state *state = ruby_xcalloc(1, sizeof(struct cpu_and_wall_time_worker_state));
320
349
 
321
350
  // Note: Any exceptions raised from this note until the TypedData_Wrap_Struct call will lead to the state memory
@@ -325,13 +354,14 @@ static VALUE _native_new(VALUE klass) {
325
354
  state->no_signals_workaround_enabled = false;
326
355
  state->dynamic_sampling_rate_enabled = true;
327
356
  state->allocation_profiling_enabled = false;
357
+ state->allocation_counting_enabled = false;
358
+ state->gvl_profiling_enabled = false;
328
359
  state->skip_idle_samples_for_testing = false;
329
360
  state->thread_context_collector_instance = Qnil;
330
361
  state->idle_sampling_helper_instance = Qnil;
331
362
  state->owner_thread = Qnil;
332
363
  dynamic_sampling_rate_init(&state->cpu_dynamic_sampling_rate);
333
364
  state->gc_tracepoint = Qnil;
334
- state->object_allocation_tracepoint = Qnil;
335
365
 
336
366
  atomic_init(&state->should_run, false);
337
367
  state->failure_exception = Qnil;
@@ -339,36 +369,44 @@ static VALUE _native_new(VALUE klass) {
339
369
 
340
370
  state->during_sample = false;
341
371
 
342
- reset_stats_not_thread_safe(state);
343
-
344
- long now = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
345
- if (now == 0) {
346
- ruby_xfree(state);
347
- rb_raise(rb_eRuntimeError, ERR_CLOCK_FAIL);
348
- }
372
+ #ifndef NO_GVL_INSTRUMENTATION
373
+ state->gvl_profiling_hook = NULL;
374
+ #endif
349
375
 
376
+ reset_stats_not_thread_safe(state);
350
377
  discrete_dynamic_sampler_init(&state->allocation_sampler, "allocation", now);
351
378
 
379
+ // Note: As of this writing, no new Ruby objects get created and stored in the state. If that ever changes, remember
380
+ // to keep them on the stack and mark them with RB_GC_GUARD -- otherwise it's possible for a GC to run and
381
+ // since the instance representing the state does not yet exist, such objects will not get marked.
382
+
352
383
  return state->self_instance = TypedData_Wrap_Struct(klass, &cpu_and_wall_time_worker_typed_data, state);
353
384
  }
354
385
 
355
- static VALUE _native_initialize(
356
- DDTRACE_UNUSED VALUE _self,
357
- VALUE self_instance,
358
- VALUE thread_context_collector_instance,
359
- VALUE gc_profiling_enabled,
360
- VALUE idle_sampling_helper_instance,
361
- VALUE no_signals_workaround_enabled,
362
- VALUE dynamic_sampling_rate_enabled,
363
- VALUE dynamic_sampling_rate_overhead_target_percentage,
364
- VALUE allocation_profiling_enabled,
365
- VALUE skip_idle_samples_for_testing
366
- ) {
386
+ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
387
+ VALUE options;
388
+ rb_scan_args(argc, argv, "0:", &options);
389
+ if (options == Qnil) options = rb_hash_new();
390
+
391
+ VALUE self_instance = rb_hash_fetch(options, ID2SYM(rb_intern("self_instance")));
392
+ VALUE thread_context_collector_instance = rb_hash_fetch(options, ID2SYM(rb_intern("thread_context_collector")));
393
+ VALUE gc_profiling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("gc_profiling_enabled")));
394
+ VALUE idle_sampling_helper_instance = rb_hash_fetch(options, ID2SYM(rb_intern("idle_sampling_helper")));
395
+ VALUE no_signals_workaround_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("no_signals_workaround_enabled")));
396
+ VALUE dynamic_sampling_rate_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("dynamic_sampling_rate_enabled")));
397
+ VALUE dynamic_sampling_rate_overhead_target_percentage = rb_hash_fetch(options, ID2SYM(rb_intern("dynamic_sampling_rate_overhead_target_percentage")));
398
+ VALUE allocation_profiling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("allocation_profiling_enabled")));
399
+ VALUE allocation_counting_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("allocation_counting_enabled")));
400
+ VALUE gvl_profiling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("gvl_profiling_enabled")));
401
+ VALUE skip_idle_samples_for_testing = rb_hash_fetch(options, ID2SYM(rb_intern("skip_idle_samples_for_testing")));
402
+
367
403
  ENFORCE_BOOLEAN(gc_profiling_enabled);
368
404
  ENFORCE_BOOLEAN(no_signals_workaround_enabled);
369
405
  ENFORCE_BOOLEAN(dynamic_sampling_rate_enabled);
370
406
  ENFORCE_TYPE(dynamic_sampling_rate_overhead_target_percentage, T_FLOAT);
371
407
  ENFORCE_BOOLEAN(allocation_profiling_enabled);
408
+ ENFORCE_BOOLEAN(allocation_counting_enabled);
409
+ ENFORCE_BOOLEAN(gvl_profiling_enabled);
372
410
  ENFORCE_BOOLEAN(skip_idle_samples_for_testing)
373
411
 
374
412
  struct cpu_and_wall_time_worker_state *state;
@@ -378,6 +416,8 @@ static VALUE _native_initialize(
378
416
  state->no_signals_workaround_enabled = (no_signals_workaround_enabled == Qtrue);
379
417
  state->dynamic_sampling_rate_enabled = (dynamic_sampling_rate_enabled == Qtrue);
380
418
  state->allocation_profiling_enabled = (allocation_profiling_enabled == Qtrue);
419
+ state->allocation_counting_enabled = (allocation_counting_enabled == Qtrue);
420
+ state->gvl_profiling_enabled = (gvl_profiling_enabled == Qtrue);
381
421
  state->skip_idle_samples_for_testing = (skip_idle_samples_for_testing == Qtrue);
382
422
 
383
423
  double total_overhead_target_percentage = NUM2DBL(dynamic_sampling_rate_overhead_target_percentage);
@@ -394,7 +434,6 @@ static VALUE _native_initialize(
394
434
  state->thread_context_collector_instance = enforce_thread_context_collector_instance(thread_context_collector_instance);
395
435
  state->idle_sampling_helper_instance = idle_sampling_helper_instance;
396
436
  state->gc_tracepoint = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_GC_ENTER | RUBY_INTERNAL_EVENT_GC_EXIT, on_gc_event, NULL /* unused */);
397
- state->object_allocation_tracepoint = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_NEWOBJ, on_newobj_event, NULL /* unused */);
398
437
 
399
438
  return Qtrue;
400
439
  }
@@ -409,7 +448,6 @@ static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr) {
409
448
  rb_gc_mark(state->failure_exception);
410
449
  rb_gc_mark(state->stop_thread);
411
450
  rb_gc_mark(state->gc_tracepoint);
412
- rb_gc_mark(state->object_allocation_tracepoint);
413
451
  }
414
452
 
415
453
  // Called in a background thread created in CpuAndWallTimeWorker#start
@@ -755,7 +793,35 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
755
793
  // because they may raise exceptions.
756
794
  install_sigprof_signal_handler(handle_sampling_signal, "handle_sampling_signal");
757
795
  if (state->gc_profiling_enabled) rb_tracepoint_enable(state->gc_tracepoint);
758
- if (state->allocation_profiling_enabled) rb_tracepoint_enable(state->object_allocation_tracepoint);
796
+ if (state->allocation_profiling_enabled) {
797
+ rb_add_event_hook2(
798
+ on_newobj_event_as_hook,
799
+ RUBY_INTERNAL_EVENT_NEWOBJ,
800
+ state->self_instance,
801
+ RUBY_EVENT_HOOK_FLAG_SAFE | RUBY_EVENT_HOOK_FLAG_RAW_ARG)
802
+ ;
803
+ }
804
+
805
+ if (state->gvl_profiling_enabled) {
806
+ #ifndef NO_GVL_INSTRUMENTATION
807
+ #ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS
808
+ gvl_profiling_state_thread_tracking_workaround();
809
+ #endif
810
+
811
+ state->gvl_profiling_hook = rb_internal_thread_add_event_hook(
812
+ on_gvl_event,
813
+ (
814
+ // For now we're only asking for these events, even though there's more
815
+ // (e.g. check docs or gvl-tracing gem)
816
+ RUBY_INTERNAL_THREAD_EVENT_READY /* waiting for gvl */ |
817
+ RUBY_INTERNAL_THREAD_EVENT_RESUMED /* running/runnable */
818
+ ),
819
+ NULL
820
+ );
821
+ #else
822
+ rb_raise(rb_eArgError, "GVL profiling is not supported in this Ruby version");
823
+ #endif
824
+ }
759
825
 
760
826
  // Flag the profiler as running before we release the GVL, in case anyone's waiting to know about it
761
827
  rb_funcall(instance, rb_intern("signal_running"), 0);
@@ -868,7 +934,6 @@ static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused) {
868
934
 
869
935
  state->during_sample = true;
870
936
 
871
- // Trigger sampling using the Collectors::ThreadState; rescue against any exceptions that happen during sampling
872
937
  safely_call(thread_context_collector_sample_after_gc, state->thread_context_collector_instance, state->self_instance);
873
938
 
874
939
  state->during_sample = false;
@@ -975,6 +1040,9 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
975
1040
  ID2SYM(rb_intern("allocation_sampling_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats.allocation_sampling_time_ns_total, state->stats.allocation_sampled),
976
1041
  ID2SYM(rb_intern("allocation_sampler_snapshot")), /* => */ allocation_sampler_snapshot,
977
1042
  ID2SYM(rb_intern("allocations_during_sample")), /* => */ state->allocation_profiling_enabled ? UINT2NUM(state->stats.allocations_during_sample) : Qnil,
1043
+
1044
+ // GVL profiling stats
1045
+ ID2SYM(rb_intern("after_gvl_running")), /* => */ UINT2NUM(state->stats.after_gvl_running),
978
1046
  };
979
1047
  for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
980
1048
  return stats_as_hash;
@@ -1036,46 +1104,87 @@ static void sleep_for(uint64_t time_ns) {
1036
1104
  }
1037
1105
 
1038
1106
  static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self) {
1039
- bool are_allocations_being_tracked = active_sampler_instance_state != NULL && active_sampler_instance_state->allocation_profiling_enabled;
1107
+ struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state;
1108
+
1109
+ bool are_allocations_being_tracked = state != NULL && state->allocation_profiling_enabled && state->allocation_counting_enabled;
1040
1110
 
1041
1111
  return are_allocations_being_tracked ? ULL2NUM(allocation_count) : Qnil;
1042
1112
  }
1043
1113
 
1044
- // Implements memory-related profiling events. This function is called by Ruby via the `object_allocation_tracepoint`
1045
- // when the RUBY_INTERNAL_EVENT_NEWOBJ event is triggered.
1046
- static void on_newobj_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
1047
- // Update thread-local allocation count
1048
- if (RB_UNLIKELY(allocation_count == UINT64_MAX)) {
1049
- allocation_count = 0;
1050
- } else {
1051
- allocation_count++;
1052
- }
1114
+ #define HANDLE_CLOCK_FAILURE(call) ({ \
1115
+ long _result = (call); \
1116
+ if (_result == 0) { \
1117
+ delayed_error(state, ERR_CLOCK_FAIL); \
1118
+ return; \
1119
+ } \
1120
+ _result; \
1121
+ })
1053
1122
 
1123
+ // Implements memory-related profiling events. This function is called by Ruby via the `rb_add_event_hook2`
1124
+ // when the RUBY_INTERNAL_EVENT_NEWOBJ event is triggered.
1125
+ //
1126
+ // When allocation sampling is enabled, this function gets called for almost all* objects allocated by the Ruby VM.
1127
+ // (*In some weird cases the VM may skip this tracepoint.)
1128
+ //
1129
+ // At a high level, there's two paths through this function:
1130
+ // 1. should_sample == false -> return
1131
+ // 2. should_sample == true -> sample
1132
+ //
1133
+ // On big applications, path 1. is the hottest, since we don't sample every object. So it's quite important for it to
1134
+ // be as fast as possible.
1135
+ //
1136
+ // NOTE: You may be wondering why we don't use any of the arguments to this function. It turns out it's possible to just
1137
+ // call `rb_tracearg_from_tracepoint(anything)` anywhere during this function or its callees to get the data, so that's
1138
+ // why it's not being passed as an argument.
1139
+ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *unused2) {
1054
1140
  struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1055
1141
 
1056
1142
  // This should not happen in a normal situation because the tracepoint is always enabled after the instance is set
1057
1143
  // and disabled before it is cleared, but just in case...
1058
1144
  if (state == NULL) return;
1059
1145
 
1060
- // In a few cases, we may actually be allocating an object as part of profiler sampling. We don't want to recursively
1146
+ if (RB_UNLIKELY(state->allocation_counting_enabled)) {
1147
+ // Update thread-local allocation count
1148
+ if (RB_UNLIKELY(allocation_count == UINT64_MAX)) {
1149
+ allocation_count = 0;
1150
+ } else {
1151
+ allocation_count++;
1152
+ }
1153
+ }
1154
+
1155
+ // In rare cases, we may actually be allocating an object as part of profiler sampling. We don't want to recursively
1061
1156
  // sample, so we just return early
1062
1157
  if (state->during_sample) {
1063
1158
  state->stats.allocations_during_sample++;
1064
1159
  return;
1065
1160
  }
1066
1161
 
1067
- if (state->dynamic_sampling_rate_enabled) {
1068
- long now = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
1069
- if (now == 0) {
1070
- delayed_error(state, ERR_CLOCK_FAIL);
1071
- return;
1072
- }
1073
- if (!discrete_dynamic_sampler_should_sample(&state->allocation_sampler, now)) {
1074
- state->stats.allocation_skipped++;
1075
- return;
1162
+ // Hot path: Dynamic sampling rate is usually enabled and the sampling decision is usually false
1163
+ if (RB_LIKELY(state->dynamic_sampling_rate_enabled && !discrete_dynamic_sampler_should_sample(&state->allocation_sampler))) {
1164
+ state->stats.allocation_skipped++;
1165
+
1166
+ coarse_instant now = monotonic_coarse_wall_time_now_ns();
1167
+ HANDLE_CLOCK_FAILURE(now.timestamp_ns);
1168
+
1169
+ bool needs_readjust = discrete_dynamic_sampler_skipped_sample(&state->allocation_sampler, now);
1170
+ if (RB_UNLIKELY(needs_readjust)) {
1171
+ // We rarely readjust, so this is a cold path
1172
+ // Also, while above we used the cheaper monotonic_coarse, for this call we want the regular monotonic call,
1173
+ // which is why we end up getting time "again".
1174
+ discrete_dynamic_sampler_readjust(
1175
+ &state->allocation_sampler, HANDLE_CLOCK_FAILURE(monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE))
1176
+ );
1076
1177
  }
1178
+
1179
+ return;
1077
1180
  }
1078
1181
 
1182
+ // From here on, we've decided to go ahead with the sample, which is way less common than skipping it
1183
+
1184
+ discrete_dynamic_sampler_before_sample(
1185
+ &state->allocation_sampler, HANDLE_CLOCK_FAILURE(monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE))
1186
+ );
1187
+
1079
1188
  // @ivoanjo: Strictly speaking, this is not needed because Ruby should not call the same tracepoint while a previous
1080
1189
  // invocation is still pending, (e.g. it wouldn't call `on_newobj_event` while it's already running), but I decided
1081
1190
  // to keep this here for consistency -- every call to the thread context (other than the special gc calls which are
@@ -1083,7 +1192,7 @@ static void on_newobj_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused)
1083
1192
  state->during_sample = true;
1084
1193
 
1085
1194
  // Rescue against any exceptions that happen during sampling
1086
- safely_call(rescued_sample_allocation, tracepoint_data, state->self_instance);
1195
+ safely_call(rescued_sample_allocation, Qnil, state->self_instance);
1087
1196
 
1088
1197
  if (state->dynamic_sampling_rate_enabled) {
1089
1198
  long now = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
@@ -1108,9 +1217,15 @@ static void disable_tracepoints(struct cpu_and_wall_time_worker_state *state) {
1108
1217
  if (state->gc_tracepoint != Qnil) {
1109
1218
  rb_tracepoint_disable(state->gc_tracepoint);
1110
1219
  }
1111
- if (state->object_allocation_tracepoint != Qnil) {
1112
- rb_tracepoint_disable(state->object_allocation_tracepoint);
1113
- }
1220
+
1221
+ rb_remove_event_hook_with_data(on_newobj_event_as_hook, state->self_instance);
1222
+
1223
+ #ifndef NO_GVL_INSTRUMENTATION
1224
+ if (state->gvl_profiling_hook) {
1225
+ rb_internal_thread_remove_event_hook(state->gvl_profiling_hook);
1226
+ state->gvl_profiling_hook = NULL;
1227
+ }
1228
+ #endif
1114
1229
  }
1115
1230
 
1116
1231
  static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self) {
@@ -1126,13 +1241,14 @@ static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self) {
1126
1241
  }
1127
1242
  }
1128
1243
 
1129
- static VALUE rescued_sample_allocation(VALUE tracepoint_data) {
1244
+ static VALUE rescued_sample_allocation(DDTRACE_UNUSED VALUE unused) {
1130
1245
  struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1131
1246
 
1132
1247
  // This should not happen in a normal situation because on_newobj_event already checked for this, but just in case...
1133
1248
  if (state == NULL) return Qnil;
1134
1249
 
1135
- rb_trace_arg_t *data = rb_tracearg_from_tracepoint(tracepoint_data);
1250
+ // If we're getting called from inside a tracepoint/event hook, Ruby exposes the data using this function.
1251
+ rb_trace_arg_t *data = rb_tracearg_from_tracepoint(Qnil);
1136
1252
  VALUE new_object = rb_tracearg_object(data);
1137
1253
 
1138
1254
  unsigned long allocations_since_last_sample = state->dynamic_sampling_rate_enabled ?
@@ -1140,9 +1256,16 @@ static VALUE rescued_sample_allocation(VALUE tracepoint_data) {
1140
1256
  discrete_dynamic_sampler_events_since_last_sample(&state->allocation_sampler) :
1141
1257
  // if we aren't, then we're sampling every event
1142
1258
  1;
1143
- // TODO: Signal in the profile that clamping happened?
1259
+
1260
+ // To control bias from sampling, we clamp the maximum weight attributed to a single allocation sample. This avoids
1261
+ // assigning a very large number to a sample, if for instance the dynamic sampling mechanism chose a really big interval.
1144
1262
  unsigned int weight = allocations_since_last_sample > MAX_ALLOC_WEIGHT ? MAX_ALLOC_WEIGHT : (unsigned int) allocations_since_last_sample;
1145
1263
  thread_context_collector_sample_allocation(state->thread_context_collector_instance, weight, new_object);
1264
+ // ...but we still represent the skipped samples in the profile, thus the data will account for all allocations.
1265
+ if (weight < allocations_since_last_sample) {
1266
+ uint32_t skipped_samples = (uint32_t) uint64_min_of(allocations_since_last_sample - weight, UINT32_MAX);
1267
+ thread_context_collector_sample_skipped_allocation_samples(state->thread_context_collector_instance, skipped_samples);
1268
+ }
1146
1269
 
1147
1270
  // Return a dummy VALUE because we're called from rb_rescue2 which requires it
1148
1271
  return Qnil;
@@ -1177,3 +1300,68 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
1177
1300
  unblock_sigprof_signal_handler_from_running_in_current_thread();
1178
1301
  return Qtrue;
1179
1302
  }
1303
+
1304
+ #ifndef NO_GVL_INSTRUMENTATION
1305
+ static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused) {
1306
+ // Be very careful about touching the `state` here or doing anything at all:
1307
+ // This function gets called without the GVL, and potentially from background Ractors!
1308
+ //
1309
+ // In fact, the `target_thread` that this event is about may not even be the current thread. (So be careful with thread locals that
1310
+ // are not directly tied to the `target_thread` object and the like)
1311
+ gvl_profiling_thread target_thread = thread_from_event(event_data);
1312
+
1313
+ if (event_id == RUBY_INTERNAL_THREAD_EVENT_READY) { /* waiting for gvl */
1314
+ thread_context_collector_on_gvl_waiting(target_thread);
1315
+ } else if (event_id == RUBY_INTERNAL_THREAD_EVENT_RESUMED) { /* running/runnable */
1316
+ // Interesting note: A RUBY_INTERNAL_THREAD_EVENT_RESUMED is guaranteed to be called with the GVL being acquired.
1317
+ // (And... I think target_thread will be == rb_thread_current()?)
1318
+ // But we're not sure if we're on the main Ractor yet. The thread context collector actually can actually help here:
1319
+ // it tags threads it's tracking, so if a thread is tagged then by definition we know that thread belongs to the main
1320
+ // Ractor. Thus, if we really really wanted to access the state, we could do it after making sure we're on the correct Ractor.
1321
+
1322
+ #ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS
1323
+ target_thread = gvl_profiling_state_maybe_initialize();
1324
+ #endif
1325
+
1326
+ bool should_sample = thread_context_collector_on_gvl_running(target_thread);
1327
+
1328
+ if (should_sample) {
1329
+ // should_sample is only true if a thread belongs to the main Ractor, so we're good to go
1330
+ #ifndef NO_POSTPONED_TRIGGER
1331
+ rb_postponed_job_trigger(after_gvl_running_from_postponed_job_handle);
1332
+ #else
1333
+ rb_postponed_job_register_one(0, after_gvl_running_from_postponed_job, NULL);
1334
+ #endif
1335
+ }
1336
+ } else {
1337
+ // This is a very delicate time and it's hard for us to raise an exception so let's at least complain to stderr
1338
+ fprintf(stderr, "[ddtrace] Unexpected value in on_gvl_event (%d)\n", event_id);
1339
+ }
1340
+ }
1341
+
1342
+ static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused) {
1343
+ struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1344
+
1345
+ // This can potentially happen if the CpuAndWallTimeWorker was stopped while the postponed job was waiting to be executed; nothing to do
1346
+ if (state == NULL) return;
1347
+
1348
+ state->during_sample = true;
1349
+
1350
+ safely_call(thread_context_collector_sample_after_gvl_running, state->thread_context_collector_instance, state->self_instance);
1351
+
1352
+ state->stats.after_gvl_running++;
1353
+
1354
+ state->during_sample = false;
1355
+ }
1356
+
1357
+ static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE instance) {
1358
+ struct cpu_and_wall_time_worker_state *state;
1359
+ TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1360
+
1361
+ return state->gvl_profiling_hook != NULL ? Qtrue : Qfalse;
1362
+ }
1363
+ #else
1364
+ static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, DDTRACE_UNUSED VALUE instance) {
1365
+ return Qfalse;
1366
+ }
1367
+ #endif