datadog 2.7.1 → 2.9.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -1
- data/ext/datadog_profiling_native_extension/clock_id.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +64 -54
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +16 -16
- data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +259 -132
- data/ext/datadog_profiling_native_extension/extconf.rb +0 -8
- data/ext/datadog_profiling_native_extension/heap_recorder.c +11 -89
- data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
- data/ext/datadog_profiling_native_extension/http_transport.c +4 -4
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +4 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -1
- data/ext/datadog_profiling_native_extension/profiling.c +10 -8
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +8 -8
- data/ext/datadog_profiling_native_extension/stack_recorder.c +54 -88
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
- data/ext/datadog_profiling_native_extension/time_helpers.h +1 -1
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +47 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +31 -0
- data/ext/libdatadog_api/crashtracker.c +3 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +355 -157
- data/lib/datadog/appsec/assets/waf_rules/strict.json +62 -32
- data/lib/datadog/appsec/component.rb +1 -8
- data/lib/datadog/appsec/context.rb +54 -0
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +73 -0
- data/lib/datadog/appsec/contrib/active_record/integration.rb +41 -0
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +53 -0
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +6 -6
- data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +4 -4
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +19 -28
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +5 -5
- data/lib/datadog/appsec/contrib/rack/gateway/response.rb +3 -3
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +64 -96
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +10 -10
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +5 -5
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +6 -6
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +10 -11
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -49
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +21 -32
- data/lib/datadog/appsec/contrib/rails/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +6 -6
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +41 -63
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +2 -2
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +5 -5
- data/lib/datadog/appsec/event.rb +6 -6
- data/lib/datadog/appsec/ext.rb +3 -1
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +22 -32
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +5 -5
- data/lib/datadog/appsec/processor/context.rb +2 -2
- data/lib/datadog/appsec/processor/rule_loader.rb +0 -3
- data/lib/datadog/appsec/remote.rb +1 -3
- data/lib/datadog/appsec/response.rb +7 -11
- data/lib/datadog/appsec.rb +6 -5
- data/lib/datadog/auto_instrument.rb +3 -0
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +39 -11
- data/lib/datadog/core/configuration/components.rb +20 -2
- data/lib/datadog/core/configuration/settings.rb +10 -0
- data/lib/datadog/core/configuration.rb +10 -2
- data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
- data/lib/datadog/core/crashtracking/component.rb +1 -3
- data/lib/datadog/core/remote/client/capabilities.rb +6 -0
- data/lib/datadog/core/remote/client.rb +65 -59
- data/lib/datadog/core/telemetry/component.rb +9 -3
- data/lib/datadog/core/telemetry/event.rb +87 -3
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/core/telemetry/logging.rb +2 -2
- data/lib/datadog/core/telemetry/metric.rb +22 -0
- data/lib/datadog/core/telemetry/worker.rb +33 -0
- data/lib/datadog/di/base.rb +115 -0
- data/lib/datadog/di/code_tracker.rb +11 -7
- data/lib/datadog/di/component.rb +21 -11
- data/lib/datadog/di/configuration/settings.rb +11 -1
- data/lib/datadog/di/contrib/active_record.rb +1 -0
- data/lib/datadog/di/contrib/railtie.rb +15 -0
- data/lib/datadog/di/contrib.rb +26 -0
- data/lib/datadog/di/error.rb +5 -0
- data/lib/datadog/di/instrumenter.rb +111 -20
- data/lib/datadog/di/preload.rb +18 -0
- data/lib/datadog/di/probe.rb +11 -1
- data/lib/datadog/di/probe_builder.rb +1 -0
- data/lib/datadog/di/probe_manager.rb +8 -5
- data/lib/datadog/di/probe_notification_builder.rb +27 -7
- data/lib/datadog/di/probe_notifier_worker.rb +5 -6
- data/lib/datadog/di/remote.rb +124 -0
- data/lib/datadog/di/serializer.rb +14 -7
- data/lib/datadog/di/transport.rb +3 -5
- data/lib/datadog/di/utils.rb +7 -0
- data/lib/datadog/di.rb +23 -62
- data/lib/datadog/kit/appsec/events.rb +3 -3
- data/lib/datadog/kit/identity.rb +4 -4
- data/lib/datadog/profiling/component.rb +59 -69
- data/lib/datadog/profiling/http_transport.rb +1 -26
- data/lib/datadog/tracing/configuration/settings.rb +4 -8
- data/lib/datadog/tracing/contrib/action_cable/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/action_mailer/integration.rb +6 -2
- data/lib/datadog/tracing/contrib/action_pack/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/action_view/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/active_job/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/active_record/integration.rb +6 -2
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +16 -4
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +10 -0
- data/lib/datadog/tracing/contrib/active_support/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/auto_instrument.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +4 -0
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
- data/lib/datadog/tracing/contrib/httprb/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/kafka/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/mongodb/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/opensearch/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/presto/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/rack/integration.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/framework.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/rest_client/integration.rb +3 -0
- data/lib/datadog/tracing/span.rb +12 -4
- data/lib/datadog/tracing/span_event.rb +123 -3
- data/lib/datadog/tracing/span_operation.rb +6 -0
- data/lib/datadog/tracing/transport/serializable_trace.rb +24 -6
- data/lib/datadog/version.rb +2 -2
- data/lib/datadog.rb +3 -0
- metadata +30 -17
- data/lib/datadog/appsec/processor/actions.rb +0 -49
- data/lib/datadog/appsec/reactive/operation.rb +0 -68
- data/lib/datadog/appsec/scope.rb +0 -58
- data/lib/datadog/core/crashtracking/agent_base_url.rb +0 -21
@@ -9,6 +9,7 @@
|
|
9
9
|
#include "private_vm_api_access.h"
|
10
10
|
#include "stack_recorder.h"
|
11
11
|
#include "time_helpers.h"
|
12
|
+
#include "unsafe_api_calls_check.h"
|
12
13
|
|
13
14
|
// Used to trigger sampling of threads, based on external "events", such as:
|
14
15
|
// * periodic timer for cpu-time and wall-time
|
@@ -112,14 +113,14 @@ static uint32_t global_waiting_for_gvl_threshold_ns = MILLIS_AS_NS(10);
|
|
112
113
|
typedef enum { OTEL_CONTEXT_ENABLED_FALSE, OTEL_CONTEXT_ENABLED_ONLY, OTEL_CONTEXT_ENABLED_BOTH } otel_context_enabled;
|
113
114
|
|
114
115
|
// Contains state for a single ThreadContext instance
|
115
|
-
struct
|
116
|
+
typedef struct {
|
116
117
|
// Note: Places in this file that usually need to be changed when this struct is changed are tagged with
|
117
118
|
// "Update this when modifying state struct"
|
118
119
|
|
119
120
|
// Required by Datadog::Profiling::Collectors::Stack as a scratch buffer during sampling
|
120
121
|
ddog_prof_Location *locations;
|
121
122
|
uint16_t max_frames;
|
122
|
-
// Hashmap <Thread Object,
|
123
|
+
// Hashmap <Thread Object, per_thread_context>
|
123
124
|
st_table *hash_map_per_thread_context;
|
124
125
|
// Datadog::Profiling::StackRecorder instance
|
125
126
|
VALUE recorder_instance;
|
@@ -162,10 +163,10 @@ struct thread_context_collector_state {
|
|
162
163
|
long wall_time_at_previous_gc_ns; // Will be INVALID_TIME unless there's accumulated time above
|
163
164
|
long wall_time_at_last_flushed_gc_event_ns; // Starts at 0 and then will always be valid
|
164
165
|
} gc_tracking;
|
165
|
-
};
|
166
|
+
} thread_context_collector_state;
|
166
167
|
|
167
168
|
// Tracks per-thread state
|
168
|
-
struct
|
169
|
+
typedef struct {
|
169
170
|
sampling_buffer *sampling_buffer;
|
170
171
|
char thread_id[THREAD_ID_LIMIT_CHARS];
|
171
172
|
ddog_CharSlice thread_id_char_slice;
|
@@ -181,21 +182,21 @@ struct per_thread_context {
|
|
181
182
|
long cpu_time_at_start_ns;
|
182
183
|
long wall_time_at_start_ns;
|
183
184
|
} gc_tracking;
|
184
|
-
};
|
185
|
+
} per_thread_context;
|
185
186
|
|
186
187
|
// Used to correlate profiles with traces
|
187
|
-
struct
|
188
|
+
typedef struct {
|
188
189
|
bool valid;
|
189
190
|
uint64_t local_root_span_id;
|
190
191
|
uint64_t span_id;
|
191
192
|
VALUE trace_endpoint;
|
192
|
-
};
|
193
|
+
} trace_identifiers;
|
193
194
|
|
194
|
-
struct
|
195
|
+
typedef struct {
|
195
196
|
VALUE span;
|
196
197
|
VALUE span_id;
|
197
198
|
VALUE trace_id;
|
198
|
-
};
|
199
|
+
} otel_span;
|
199
200
|
|
200
201
|
static void thread_context_collector_typed_data_mark(void *state_ptr);
|
201
202
|
static void thread_context_collector_typed_data_free(void *state_ptr);
|
@@ -203,70 +204,77 @@ static int hash_map_per_thread_context_mark(st_data_t key_thread, st_data_t _val
|
|
203
204
|
static int hash_map_per_thread_context_free_values(st_data_t _thread, st_data_t value_per_thread_context, st_data_t _argument);
|
204
205
|
static VALUE _native_new(VALUE klass);
|
205
206
|
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
206
|
-
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE profiler_overhead_stack_thread);
|
207
|
+
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE profiler_overhead_stack_thread, VALUE allow_exception);
|
207
208
|
static VALUE _native_on_gc_start(VALUE self, VALUE collector_instance);
|
208
209
|
static VALUE _native_on_gc_finish(VALUE self, VALUE collector_instance);
|
209
|
-
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state);
|
210
|
+
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state, VALUE allow_exception);
|
210
211
|
static void update_metrics_and_sample(
|
211
|
-
|
212
|
+
thread_context_collector_state *state,
|
212
213
|
VALUE thread_being_sampled,
|
213
214
|
VALUE stack_from_thread,
|
214
|
-
|
215
|
+
per_thread_context *thread_context,
|
215
216
|
sampling_buffer* sampling_buffer,
|
216
217
|
long current_cpu_time_ns,
|
217
218
|
long current_monotonic_wall_time_ns
|
218
219
|
);
|
219
220
|
static void trigger_sample_for_thread(
|
220
|
-
|
221
|
+
thread_context_collector_state *state,
|
221
222
|
VALUE thread,
|
222
223
|
VALUE stack_from_thread,
|
223
|
-
|
224
|
+
per_thread_context *thread_context,
|
224
225
|
sampling_buffer* sampling_buffer,
|
225
226
|
sample_values values,
|
226
227
|
long current_monotonic_wall_time_ns,
|
227
228
|
ddog_CharSlice *ruby_vm_type,
|
228
229
|
ddog_CharSlice *class_name,
|
229
|
-
bool is_gvl_waiting_state
|
230
|
+
bool is_gvl_waiting_state,
|
231
|
+
bool is_safe_to_allocate_objects
|
230
232
|
);
|
231
233
|
static VALUE _native_thread_list(VALUE self);
|
232
|
-
static
|
233
|
-
static
|
234
|
-
static void initialize_context(VALUE thread,
|
235
|
-
static void free_context(
|
234
|
+
static per_thread_context *get_or_create_context_for(VALUE thread, thread_context_collector_state *state);
|
235
|
+
static per_thread_context *get_context_for(VALUE thread, thread_context_collector_state *state);
|
236
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context, thread_context_collector_state *state);
|
237
|
+
static void free_context(per_thread_context* thread_context);
|
236
238
|
static VALUE _native_inspect(VALUE self, VALUE collector_instance);
|
237
|
-
static VALUE per_thread_context_st_table_as_ruby_hash(
|
239
|
+
static VALUE per_thread_context_st_table_as_ruby_hash(thread_context_collector_state *state);
|
238
240
|
static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash);
|
239
|
-
static VALUE stats_as_ruby_hash(
|
240
|
-
static VALUE gc_tracking_as_ruby_hash(
|
241
|
-
static void remove_context_for_dead_threads(
|
241
|
+
static VALUE stats_as_ruby_hash(thread_context_collector_state *state);
|
242
|
+
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state);
|
243
|
+
static void remove_context_for_dead_threads(thread_context_collector_state *state);
|
242
244
|
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, st_data_t _argument);
|
243
245
|
static VALUE _native_per_thread_context(VALUE self, VALUE collector_instance);
|
244
246
|
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, long gc_start_time_ns, bool is_wall_time);
|
245
|
-
static long cpu_time_now_ns(
|
247
|
+
static long cpu_time_now_ns(per_thread_context *thread_context);
|
246
248
|
static long thread_id_for(VALUE thread);
|
247
249
|
static VALUE _native_stats(VALUE self, VALUE collector_instance);
|
248
250
|
static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
|
249
|
-
static void trace_identifiers_for(
|
251
|
+
static void trace_identifiers_for(
|
252
|
+
thread_context_collector_state *state,
|
253
|
+
VALUE thread,
|
254
|
+
trace_identifiers *trace_identifiers_result,
|
255
|
+
bool is_safe_to_allocate_objects
|
256
|
+
);
|
250
257
|
static bool should_collect_resource(VALUE root_span);
|
251
258
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
252
|
-
static VALUE thread_list(
|
259
|
+
static VALUE thread_list(thread_context_collector_state *state);
|
253
260
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object);
|
254
261
|
static VALUE _native_new_empty_thread(VALUE self);
|
255
262
|
static ddog_CharSlice ruby_value_type_to_class_name(enum ruby_value_type type);
|
256
263
|
static void ddtrace_otel_trace_identifiers_for(
|
257
|
-
|
264
|
+
thread_context_collector_state *state,
|
258
265
|
VALUE *active_trace,
|
259
266
|
VALUE *root_span,
|
260
267
|
VALUE *numeric_span_id,
|
261
268
|
VALUE active_span,
|
262
|
-
VALUE otel_values
|
269
|
+
VALUE otel_values,
|
270
|
+
bool is_safe_to_allocate_objects
|
263
271
|
);
|
264
272
|
static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples);
|
265
273
|
static bool handle_gvl_waiting(
|
266
|
-
|
274
|
+
thread_context_collector_state *state,
|
267
275
|
VALUE thread_being_sampled,
|
268
276
|
VALUE stack_from_thread,
|
269
|
-
|
277
|
+
per_thread_context *thread_context,
|
270
278
|
sampling_buffer* sampling_buffer,
|
271
279
|
long current_cpu_time_ns
|
272
280
|
);
|
@@ -276,12 +284,14 @@ static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
276
284
|
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
|
277
285
|
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns);
|
278
286
|
static void otel_without_ddtrace_trace_identifiers_for(
|
279
|
-
|
287
|
+
thread_context_collector_state *state,
|
280
288
|
VALUE thread,
|
281
|
-
|
289
|
+
trace_identifiers *trace_identifiers_result,
|
290
|
+
bool is_safe_to_allocate_objects
|
282
291
|
);
|
283
|
-
static
|
292
|
+
static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
|
284
293
|
static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
|
294
|
+
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
|
285
295
|
|
286
296
|
void collectors_thread_context_init(VALUE profiling_module) {
|
287
297
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
@@ -302,11 +312,11 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
302
312
|
rb_define_singleton_method(collectors_thread_context_class, "_native_initialize", _native_initialize, -1);
|
303
313
|
rb_define_singleton_method(collectors_thread_context_class, "_native_inspect", _native_inspect, 1);
|
304
314
|
rb_define_singleton_method(collectors_thread_context_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
305
|
-
rb_define_singleton_method(testing_module, "_native_sample", _native_sample,
|
315
|
+
rb_define_singleton_method(testing_module, "_native_sample", _native_sample, 3);
|
306
316
|
rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
|
307
317
|
rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
|
308
318
|
rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
|
309
|
-
rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc,
|
319
|
+
rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 3);
|
310
320
|
rb_define_singleton_method(testing_module, "_native_thread_list", _native_thread_list, 0);
|
311
321
|
rb_define_singleton_method(testing_module, "_native_per_thread_context", _native_per_thread_context, 1);
|
312
322
|
rb_define_singleton_method(testing_module, "_native_stats", _native_stats, 1);
|
@@ -347,7 +357,7 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
347
357
|
gc_profiling_init();
|
348
358
|
}
|
349
359
|
|
350
|
-
// This structure is used to define a Ruby object that stores a pointer to a
|
360
|
+
// This structure is used to define a Ruby object that stores a pointer to a thread_context_collector_state
|
351
361
|
// See also https://github.com/ruby/ruby/blob/master/doc/extension.rdoc for how this works
|
352
362
|
static const rb_data_type_t thread_context_collector_typed_data = {
|
353
363
|
.wrap_struct_name = "Datadog::Profiling::Collectors::ThreadContext",
|
@@ -363,7 +373,7 @@ static const rb_data_type_t thread_context_collector_typed_data = {
|
|
363
373
|
// This function is called by the Ruby GC to give us a chance to mark any Ruby objects that we're holding on to,
|
364
374
|
// so that they don't get garbage collected
|
365
375
|
static void thread_context_collector_typed_data_mark(void *state_ptr) {
|
366
|
-
|
376
|
+
thread_context_collector_state *state = (thread_context_collector_state *) state_ptr;
|
367
377
|
|
368
378
|
// Update this when modifying state struct
|
369
379
|
rb_gc_mark(state->recorder_instance);
|
@@ -374,7 +384,7 @@ static void thread_context_collector_typed_data_mark(void *state_ptr) {
|
|
374
384
|
}
|
375
385
|
|
376
386
|
static void thread_context_collector_typed_data_free(void *state_ptr) {
|
377
|
-
|
387
|
+
thread_context_collector_state *state = (thread_context_collector_state *) state_ptr;
|
378
388
|
|
379
389
|
// Update this when modifying state struct
|
380
390
|
|
@@ -399,13 +409,13 @@ static int hash_map_per_thread_context_mark(st_data_t key_thread, DDTRACE_UNUSED
|
|
399
409
|
|
400
410
|
// Used to clear each of the per_thread_contexts inside the hash_map_per_thread_context
|
401
411
|
static int hash_map_per_thread_context_free_values(DDTRACE_UNUSED st_data_t _thread, st_data_t value_per_thread_context, DDTRACE_UNUSED st_data_t _argument) {
|
402
|
-
|
412
|
+
per_thread_context *thread_context = (per_thread_context*) value_per_thread_context;
|
403
413
|
free_context(thread_context);
|
404
414
|
return ST_CONTINUE;
|
405
415
|
}
|
406
416
|
|
407
417
|
static VALUE _native_new(VALUE klass) {
|
408
|
-
|
418
|
+
thread_context_collector_state *state = ruby_xcalloc(1, sizeof(thread_context_collector_state));
|
409
419
|
|
410
420
|
// Note: Any exceptions raised from this note until the TypedData_Wrap_Struct call will lead to the state memory
|
411
421
|
// being leaked.
|
@@ -461,8 +471,8 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
461
471
|
ENFORCE_BOOLEAN(timeline_enabled);
|
462
472
|
ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
|
463
473
|
|
464
|
-
|
465
|
-
TypedData_Get_Struct(self_instance,
|
474
|
+
thread_context_collector_state *state;
|
475
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
466
476
|
|
467
477
|
// Update this when modifying state struct
|
468
478
|
state->max_frames = sampling_buffer_check_max_frames(NUM2INT(max_frames));
|
@@ -496,40 +506,63 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
496
506
|
|
497
507
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
498
508
|
// It SHOULD NOT be used for other purposes.
|
499
|
-
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE profiler_overhead_stack_thread) {
|
509
|
+
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE profiler_overhead_stack_thread, VALUE allow_exception) {
|
510
|
+
ENFORCE_BOOLEAN(allow_exception);
|
511
|
+
|
500
512
|
if (!is_thread_alive(profiler_overhead_stack_thread)) rb_raise(rb_eArgError, "Unexpected: profiler_overhead_stack_thread is not alive");
|
501
513
|
|
514
|
+
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
515
|
+
|
502
516
|
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE), profiler_overhead_stack_thread);
|
517
|
+
|
518
|
+
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
519
|
+
|
503
520
|
return Qtrue;
|
504
521
|
}
|
505
522
|
|
506
523
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
507
524
|
// It SHOULD NOT be used for other purposes.
|
508
525
|
static VALUE _native_on_gc_start(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
526
|
+
debug_enter_unsafe_context();
|
527
|
+
|
509
528
|
thread_context_collector_on_gc_start(collector_instance);
|
529
|
+
|
530
|
+
debug_leave_unsafe_context();
|
531
|
+
|
510
532
|
return Qtrue;
|
511
533
|
}
|
512
534
|
|
513
535
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
514
536
|
// It SHOULD NOT be used for other purposes.
|
515
537
|
static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
538
|
+
debug_enter_unsafe_context();
|
539
|
+
|
516
540
|
(void) !thread_context_collector_on_gc_finish(collector_instance);
|
541
|
+
|
542
|
+
debug_leave_unsafe_context();
|
543
|
+
|
517
544
|
return Qtrue;
|
518
545
|
}
|
519
546
|
|
520
547
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
521
548
|
// It SHOULD NOT be used for other purposes.
|
522
|
-
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state) {
|
549
|
+
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state, VALUE allow_exception) {
|
523
550
|
ENFORCE_BOOLEAN(reset_monotonic_to_system_state);
|
551
|
+
ENFORCE_BOOLEAN(allow_exception);
|
524
552
|
|
525
|
-
|
526
|
-
TypedData_Get_Struct(collector_instance,
|
553
|
+
thread_context_collector_state *state;
|
554
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
527
555
|
|
528
556
|
if (reset_monotonic_to_system_state == Qtrue) {
|
529
557
|
state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
|
530
558
|
}
|
531
559
|
|
560
|
+
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
561
|
+
|
532
562
|
thread_context_collector_sample_after_gc(collector_instance);
|
563
|
+
|
564
|
+
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
565
|
+
|
533
566
|
return Qtrue;
|
534
567
|
}
|
535
568
|
|
@@ -544,11 +577,11 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_
|
|
544
577
|
// The `profiler_overhead_stack_thread` is used to attribute the profiler overhead to a stack borrowed from a different thread
|
545
578
|
// (belonging to ddtrace), so that the overhead is visible in the profile rather than blamed on user code.
|
546
579
|
void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns, VALUE profiler_overhead_stack_thread) {
|
547
|
-
|
548
|
-
TypedData_Get_Struct(self_instance,
|
580
|
+
thread_context_collector_state *state;
|
581
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
549
582
|
|
550
583
|
VALUE current_thread = rb_thread_current();
|
551
|
-
|
584
|
+
per_thread_context *current_thread_context = get_or_create_context_for(current_thread, state);
|
552
585
|
long cpu_time_at_sample_start_for_current_thread = cpu_time_now_ns(current_thread_context);
|
553
586
|
|
554
587
|
VALUE threads = thread_list(state);
|
@@ -556,7 +589,7 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
556
589
|
const long thread_count = RARRAY_LEN(threads);
|
557
590
|
for (long i = 0; i < thread_count; i++) {
|
558
591
|
VALUE thread = RARRAY_AREF(threads, i);
|
559
|
-
|
592
|
+
per_thread_context *thread_context = get_or_create_context_for(thread, state);
|
560
593
|
|
561
594
|
// We account for cpu-time for the current thread in a different way -- we use the cpu-time at sampling start, to avoid
|
562
595
|
// blaming the time the profiler took on whatever's running on the thread right now
|
@@ -592,10 +625,10 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
592
625
|
}
|
593
626
|
|
594
627
|
static void update_metrics_and_sample(
|
595
|
-
|
628
|
+
thread_context_collector_state *state,
|
596
629
|
VALUE thread_being_sampled,
|
597
630
|
VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
|
598
|
-
|
631
|
+
per_thread_context *thread_context,
|
599
632
|
sampling_buffer* sampling_buffer,
|
600
633
|
long current_cpu_time_ns,
|
601
634
|
long current_monotonic_wall_time_ns
|
@@ -647,7 +680,8 @@ static void update_metrics_and_sample(
|
|
647
680
|
current_monotonic_wall_time_ns,
|
648
681
|
NULL,
|
649
682
|
NULL,
|
650
|
-
is_gvl_waiting_state
|
683
|
+
is_gvl_waiting_state,
|
684
|
+
/* is_safe_to_allocate_objects: */ true // We called from a context that's safe to run any regular code, including allocations
|
651
685
|
);
|
652
686
|
}
|
653
687
|
|
@@ -662,12 +696,12 @@ static void update_metrics_and_sample(
|
|
662
696
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
663
697
|
// Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
|
664
698
|
void thread_context_collector_on_gc_start(VALUE self_instance) {
|
665
|
-
|
699
|
+
thread_context_collector_state *state;
|
666
700
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return;
|
667
701
|
// This should never fail the the above check passes
|
668
|
-
TypedData_Get_Struct(self_instance,
|
702
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
669
703
|
|
670
|
-
|
704
|
+
per_thread_context *thread_context = get_context_for(rb_thread_current(), state);
|
671
705
|
|
672
706
|
// If there was no previously-existing context for this thread, we won't allocate one (see safety). For now we just drop
|
673
707
|
// the GC sample, under the assumption that "a thread that is so new that we never sampled it even once before it triggers
|
@@ -695,12 +729,12 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
695
729
|
// Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
|
696
730
|
__attribute__((warn_unused_result))
|
697
731
|
bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
698
|
-
|
732
|
+
thread_context_collector_state *state;
|
699
733
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
700
734
|
// This should never fail the the above check passes
|
701
|
-
TypedData_Get_Struct(self_instance,
|
735
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
702
736
|
|
703
|
-
|
737
|
+
per_thread_context *thread_context = get_context_for(rb_thread_current(), state);
|
704
738
|
|
705
739
|
// If there was no previously-existing context for this thread, we won't allocate one (see safety). We keep a metric for
|
706
740
|
// how often this happens -- see on_gc_start.
|
@@ -773,8 +807,8 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
773
807
|
// Assumption 3: Unlike `on_gc_start` and `on_gc_finish`, this method is allowed to allocate memory as needed.
|
774
808
|
// Assumption 4: This function is called from the main Ractor (if Ruby has support for Ractors).
|
775
809
|
VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
776
|
-
|
777
|
-
TypedData_Get_Struct(self_instance,
|
810
|
+
thread_context_collector_state *state;
|
811
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
778
812
|
|
779
813
|
if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
|
780
814
|
rb_raise(rb_eRuntimeError, "BUG: Unexpected call to sample_after_gc without valid GC information available");
|
@@ -823,17 +857,20 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
823
857
|
}
|
824
858
|
|
825
859
|
static void trigger_sample_for_thread(
|
826
|
-
|
860
|
+
thread_context_collector_state *state,
|
827
861
|
VALUE thread,
|
828
862
|
VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
|
829
|
-
|
863
|
+
per_thread_context *thread_context,
|
830
864
|
sampling_buffer* sampling_buffer,
|
831
865
|
sample_values values,
|
832
866
|
long current_monotonic_wall_time_ns,
|
833
867
|
// These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
|
834
868
|
ddog_CharSlice *ruby_vm_type,
|
835
869
|
ddog_CharSlice *class_name,
|
836
|
-
bool is_gvl_waiting_state
|
870
|
+
bool is_gvl_waiting_state,
|
871
|
+
// If the Ruby VM is at a state that can allocate objects safely, or not. Added for allocation profiling: we're not
|
872
|
+
// allowed to allocate objects (or raise exceptions) when inside the NEWOBJ tracepoint.
|
873
|
+
bool is_safe_to_allocate_objects
|
837
874
|
) {
|
838
875
|
int max_label_count =
|
839
876
|
1 + // thread id
|
@@ -871,12 +908,12 @@ static void trigger_sample_for_thread(
|
|
871
908
|
};
|
872
909
|
}
|
873
910
|
|
874
|
-
|
875
|
-
trace_identifiers_for(state, thread, &trace_identifiers_result);
|
911
|
+
trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
|
912
|
+
trace_identifiers_for(state, thread, &trace_identifiers_result, is_safe_to_allocate_objects);
|
876
913
|
|
877
914
|
if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
|
878
915
|
// If we couldn't get something with ddtrace, let's see if we can get some trace identifiers from opentelemetry directly
|
879
|
-
otel_without_ddtrace_trace_identifiers_for(state, thread, &trace_identifiers_result);
|
916
|
+
otel_without_ddtrace_trace_identifiers_for(state, thread, &trace_identifiers_result, is_safe_to_allocate_objects);
|
880
917
|
}
|
881
918
|
|
882
919
|
if (trace_identifiers_result.valid) {
|
@@ -970,18 +1007,24 @@ static void trigger_sample_for_thread(
|
|
970
1007
|
// It SHOULD NOT be used for other purposes.
|
971
1008
|
static VALUE _native_thread_list(DDTRACE_UNUSED VALUE _self) {
|
972
1009
|
VALUE result = rb_ary_new();
|
1010
|
+
|
1011
|
+
debug_enter_unsafe_context();
|
1012
|
+
|
973
1013
|
ddtrace_thread_list(result);
|
1014
|
+
|
1015
|
+
debug_leave_unsafe_context();
|
1016
|
+
|
974
1017
|
return result;
|
975
1018
|
}
|
976
1019
|
|
977
|
-
static
|
978
|
-
|
1020
|
+
static per_thread_context *get_or_create_context_for(VALUE thread, thread_context_collector_state *state) {
|
1021
|
+
per_thread_context* thread_context = NULL;
|
979
1022
|
st_data_t value_context = 0;
|
980
1023
|
|
981
1024
|
if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
|
982
|
-
thread_context = (
|
1025
|
+
thread_context = (per_thread_context*) value_context;
|
983
1026
|
} else {
|
984
|
-
thread_context = ruby_xcalloc(1, sizeof(
|
1027
|
+
thread_context = ruby_xcalloc(1, sizeof(per_thread_context));
|
985
1028
|
initialize_context(thread, thread_context, state);
|
986
1029
|
st_insert(state->hash_map_per_thread_context, (st_data_t) thread, (st_data_t) thread_context);
|
987
1030
|
}
|
@@ -989,12 +1032,12 @@ static struct per_thread_context *get_or_create_context_for(VALUE thread, struct
|
|
989
1032
|
return thread_context;
|
990
1033
|
}
|
991
1034
|
|
992
|
-
static
|
993
|
-
|
1035
|
+
static per_thread_context *get_context_for(VALUE thread, thread_context_collector_state *state) {
|
1036
|
+
per_thread_context* thread_context = NULL;
|
994
1037
|
st_data_t value_context = 0;
|
995
1038
|
|
996
1039
|
if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
|
997
|
-
thread_context = (
|
1040
|
+
thread_context = (per_thread_context*) value_context;
|
998
1041
|
}
|
999
1042
|
|
1000
1043
|
return thread_context;
|
@@ -1021,7 +1064,7 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
|
|
1021
1064
|
return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
|
1022
1065
|
}
|
1023
1066
|
|
1024
|
-
static void initialize_context(VALUE thread,
|
1067
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context, thread_context_collector_state *state) {
|
1025
1068
|
thread_context->sampling_buffer = sampling_buffer_new(state->max_frames, state->locations);
|
1026
1069
|
|
1027
1070
|
snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
|
@@ -1078,14 +1121,14 @@ static void initialize_context(VALUE thread, struct per_thread_context *thread_c
|
|
1078
1121
|
#endif
|
1079
1122
|
}
|
1080
1123
|
|
1081
|
-
static void free_context(
|
1124
|
+
static void free_context(per_thread_context* thread_context) {
|
1082
1125
|
sampling_buffer_free(thread_context->sampling_buffer);
|
1083
1126
|
ruby_xfree(thread_context);
|
1084
1127
|
}
|
1085
1128
|
|
1086
1129
|
static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
1087
|
-
|
1088
|
-
TypedData_Get_Struct(collector_instance,
|
1130
|
+
thread_context_collector_state *state;
|
1131
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1089
1132
|
|
1090
1133
|
VALUE result = rb_str_new2(" (native state)");
|
1091
1134
|
|
@@ -1113,7 +1156,7 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
1113
1156
|
return result;
|
1114
1157
|
}
|
1115
1158
|
|
1116
|
-
static VALUE per_thread_context_st_table_as_ruby_hash(
|
1159
|
+
static VALUE per_thread_context_st_table_as_ruby_hash(thread_context_collector_state *state) {
|
1117
1160
|
VALUE result = rb_hash_new();
|
1118
1161
|
st_foreach(state->hash_map_per_thread_context, per_thread_context_as_ruby_hash, result);
|
1119
1162
|
return result;
|
@@ -1121,7 +1164,7 @@ static VALUE per_thread_context_st_table_as_ruby_hash(struct thread_context_coll
|
|
1121
1164
|
|
1122
1165
|
static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash) {
|
1123
1166
|
VALUE thread = (VALUE) key_thread;
|
1124
|
-
|
1167
|
+
per_thread_context *thread_context = (per_thread_context*) value_context;
|
1125
1168
|
VALUE result = (VALUE) result_hash;
|
1126
1169
|
VALUE context_as_hash = rb_hash_new();
|
1127
1170
|
rb_hash_aset(result, thread, context_as_hash);
|
@@ -1146,7 +1189,7 @@ static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value
|
|
1146
1189
|
return ST_CONTINUE;
|
1147
1190
|
}
|
1148
1191
|
|
1149
|
-
static VALUE stats_as_ruby_hash(
|
1192
|
+
static VALUE stats_as_ruby_hash(thread_context_collector_state *state) {
|
1150
1193
|
// Update this when modifying state struct (stats inner struct)
|
1151
1194
|
VALUE stats_as_hash = rb_hash_new();
|
1152
1195
|
VALUE arguments[] = {
|
@@ -1157,7 +1200,7 @@ static VALUE stats_as_ruby_hash(struct thread_context_collector_state *state) {
|
|
1157
1200
|
return stats_as_hash;
|
1158
1201
|
}
|
1159
1202
|
|
1160
|
-
static VALUE gc_tracking_as_ruby_hash(
|
1203
|
+
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state) {
|
1161
1204
|
// Update this when modifying state struct (gc_tracking inner struct)
|
1162
1205
|
VALUE result = rb_hash_new();
|
1163
1206
|
VALUE arguments[] = {
|
@@ -1170,13 +1213,13 @@ static VALUE gc_tracking_as_ruby_hash(struct thread_context_collector_state *sta
|
|
1170
1213
|
return result;
|
1171
1214
|
}
|
1172
1215
|
|
1173
|
-
static void remove_context_for_dead_threads(
|
1216
|
+
static void remove_context_for_dead_threads(thread_context_collector_state *state) {
|
1174
1217
|
st_foreach(state->hash_map_per_thread_context, remove_if_dead_thread, 0 /* unused */);
|
1175
1218
|
}
|
1176
1219
|
|
1177
1220
|
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, DDTRACE_UNUSED st_data_t _argument) {
|
1178
1221
|
VALUE thread = (VALUE) key_thread;
|
1179
|
-
|
1222
|
+
per_thread_context* thread_context = (per_thread_context*) value_context;
|
1180
1223
|
|
1181
1224
|
if (is_thread_alive(thread)) return ST_CONTINUE;
|
1182
1225
|
|
@@ -1189,8 +1232,8 @@ static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context,
|
|
1189
1232
|
//
|
1190
1233
|
// Returns the whole contents of the per_thread_context structs being tracked.
|
1191
1234
|
static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
1192
|
-
|
1193
|
-
TypedData_Get_Struct(collector_instance,
|
1235
|
+
thread_context_collector_state *state;
|
1236
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1194
1237
|
|
1195
1238
|
return per_thread_context_st_table_as_ruby_hash(state);
|
1196
1239
|
}
|
@@ -1235,7 +1278,7 @@ static long update_time_since_previous_sample(long *time_at_previous_sample_ns,
|
|
1235
1278
|
}
|
1236
1279
|
|
1237
1280
|
// Safety: This function is assumed never to raise exceptions by callers
|
1238
|
-
static long cpu_time_now_ns(
|
1281
|
+
static long cpu_time_now_ns(per_thread_context *thread_context) {
|
1239
1282
|
thread_cpu_time cpu_time = thread_cpu_time_for(thread_context->thread_cpu_time_id);
|
1240
1283
|
|
1241
1284
|
if (!cpu_time.valid) {
|
@@ -1273,8 +1316,8 @@ VALUE enforce_thread_context_collector_instance(VALUE object) {
|
|
1273
1316
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
1274
1317
|
// It SHOULD NOT be used for other purposes.
|
1275
1318
|
static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
1276
|
-
|
1277
|
-
TypedData_Get_Struct(collector_instance,
|
1319
|
+
thread_context_collector_state *state;
|
1320
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1278
1321
|
|
1279
1322
|
return stats_as_ruby_hash(state);
|
1280
1323
|
}
|
@@ -1282,14 +1325,19 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance)
|
|
1282
1325
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
1283
1326
|
// It SHOULD NOT be used for other purposes.
|
1284
1327
|
static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
1285
|
-
|
1286
|
-
TypedData_Get_Struct(collector_instance,
|
1328
|
+
thread_context_collector_state *state;
|
1329
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1287
1330
|
|
1288
1331
|
return gc_tracking_as_ruby_hash(state);
|
1289
1332
|
}
|
1290
1333
|
|
1291
1334
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
1292
|
-
static void trace_identifiers_for(
|
1335
|
+
static void trace_identifiers_for(
|
1336
|
+
thread_context_collector_state *state,
|
1337
|
+
VALUE thread,
|
1338
|
+
trace_identifiers *trace_identifiers_result,
|
1339
|
+
bool is_safe_to_allocate_objects
|
1340
|
+
) {
|
1293
1341
|
if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
|
1294
1342
|
if (state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY) return;
|
1295
1343
|
|
@@ -1308,7 +1356,9 @@ static void trace_identifiers_for(struct thread_context_collector_state *state,
|
|
1308
1356
|
|
1309
1357
|
VALUE numeric_span_id = Qnil;
|
1310
1358
|
|
1311
|
-
if (otel_values != Qnil)
|
1359
|
+
if (otel_values != Qnil) {
|
1360
|
+
ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values, is_safe_to_allocate_objects);
|
1361
|
+
}
|
1312
1362
|
|
1313
1363
|
if (root_span == Qnil || (active_span == Qnil && numeric_span_id == Qnil)) return;
|
1314
1364
|
|
@@ -1365,8 +1415,8 @@ static bool should_collect_resource(VALUE root_span) {
|
|
1365
1415
|
// Assumption: This method gets called BEFORE restarting profiling -- e.g. there are no components attempting to
|
1366
1416
|
// trigger samples at the same time.
|
1367
1417
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
1368
|
-
|
1369
|
-
TypedData_Get_Struct(collector_instance,
|
1418
|
+
thread_context_collector_state *state;
|
1419
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1370
1420
|
|
1371
1421
|
// Release all context memory before clearing the existing context
|
1372
1422
|
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
@@ -1380,7 +1430,7 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector
|
|
1380
1430
|
return Qtrue;
|
1381
1431
|
}
|
1382
1432
|
|
1383
|
-
static VALUE thread_list(
|
1433
|
+
static VALUE thread_list(thread_context_collector_state *state) {
|
1384
1434
|
VALUE result = state->thread_list_buffer;
|
1385
1435
|
rb_ary_clear(result);
|
1386
1436
|
ddtrace_thread_list(result);
|
@@ -1388,8 +1438,8 @@ static VALUE thread_list(struct thread_context_collector_state *state) {
|
|
1388
1438
|
}
|
1389
1439
|
|
1390
1440
|
void thread_context_collector_sample_allocation(VALUE self_instance, unsigned int sample_weight, VALUE new_object) {
|
1391
|
-
|
1392
|
-
TypedData_Get_Struct(self_instance,
|
1441
|
+
thread_context_collector_state *state;
|
1442
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1393
1443
|
|
1394
1444
|
VALUE current_thread = rb_thread_current();
|
1395
1445
|
|
@@ -1462,7 +1512,7 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
1462
1512
|
|
1463
1513
|
track_object(state->recorder_instance, new_object, sample_weight, optional_class_name);
|
1464
1514
|
|
1465
|
-
|
1515
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
|
1466
1516
|
|
1467
1517
|
trigger_sample_for_thread(
|
1468
1518
|
state,
|
@@ -1474,14 +1524,20 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
1474
1524
|
INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
|
1475
1525
|
&ruby_vm_type,
|
1476
1526
|
optional_class_name,
|
1477
|
-
false
|
1527
|
+
/* is_gvl_waiting_state: */ false,
|
1528
|
+
/* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
|
1478
1529
|
);
|
1479
1530
|
}
|
1480
1531
|
|
1481
1532
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
1482
1533
|
// It SHOULD NOT be used for other purposes.
|
1483
1534
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object) {
|
1535
|
+
debug_enter_unsafe_context();
|
1536
|
+
|
1484
1537
|
thread_context_collector_sample_allocation(collector_instance, NUM2UINT(sample_weight), new_object);
|
1538
|
+
|
1539
|
+
debug_leave_unsafe_context();
|
1540
|
+
|
1485
1541
|
return Qtrue;
|
1486
1542
|
}
|
1487
1543
|
|
@@ -1529,11 +1585,18 @@ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
|
|
1529
1585
|
return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
|
1530
1586
|
}
|
1531
1587
|
|
1532
|
-
static VALUE get_otel_current_span_key(
|
1588
|
+
static VALUE get_otel_current_span_key(thread_context_collector_state *state, bool is_safe_to_allocate_objects) {
|
1533
1589
|
if (state->otel_current_span_key == Qtrue) { // Qtrue means we haven't tried to extract it yet
|
1590
|
+
if (!is_safe_to_allocate_objects) {
|
1591
|
+
// Calling read_otel_current_span_key_const below can trigger exceptions and arbitrary Ruby code running (e.g.
|
1592
|
+
// `const_missing`, etc). Not safe to call in this situation, so we just skip otel info for this sample.
|
1593
|
+
return Qnil;
|
1594
|
+
}
|
1595
|
+
|
1534
1596
|
// If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
|
1535
1597
|
// gets refactored, we should not fall on our face)
|
1536
1598
|
VALUE span_key = rb_protect(read_otel_current_span_key_const, Qnil, NULL);
|
1599
|
+
rb_set_errinfo(Qnil); // **Clear any pending exception after ignoring it**
|
1537
1600
|
|
1538
1601
|
// 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
|
1539
1602
|
state->otel_current_span_key = span_key;
|
@@ -1545,12 +1608,13 @@ static VALUE get_otel_current_span_key(struct thread_context_collector_state *st
|
|
1545
1608
|
// This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
|
1546
1609
|
// differently, and this codepath handles it.
|
1547
1610
|
static void ddtrace_otel_trace_identifiers_for(
|
1548
|
-
|
1611
|
+
thread_context_collector_state *state,
|
1549
1612
|
VALUE *active_trace,
|
1550
1613
|
VALUE *root_span,
|
1551
1614
|
VALUE *numeric_span_id,
|
1552
1615
|
VALUE active_span,
|
1553
|
-
VALUE otel_values
|
1616
|
+
VALUE otel_values,
|
1617
|
+
bool is_safe_to_allocate_objects
|
1554
1618
|
) {
|
1555
1619
|
VALUE resolved_numeric_span_id =
|
1556
1620
|
active_span == Qnil ?
|
@@ -1561,7 +1625,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
1561
1625
|
|
1562
1626
|
if (resolved_numeric_span_id == Qnil) return;
|
1563
1627
|
|
1564
|
-
VALUE otel_current_span_key = get_otel_current_span_key(state);
|
1628
|
+
VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
|
1565
1629
|
if (otel_current_span_key == Qnil) return;
|
1566
1630
|
VALUE current_trace = *active_trace;
|
1567
1631
|
|
@@ -1569,7 +1633,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
1569
1633
|
// trace and span representing it. Each ddtrace trace is then connected to the previous otel span, forming a linked
|
1570
1634
|
// list. The local root span is going to be the trace/span we find at the end of this linked list.
|
1571
1635
|
while (otel_values != Qnil) {
|
1572
|
-
VALUE otel_span =
|
1636
|
+
VALUE otel_span = safely_lookup_hash_without_going_into_ruby_code(otel_values, otel_current_span_key);
|
1573
1637
|
if (otel_span == Qnil) break;
|
1574
1638
|
VALUE next_trace = rb_ivar_get(otel_span, at_datadog_trace_id);
|
1575
1639
|
if (next_trace == Qnil) break;
|
@@ -1588,8 +1652,8 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
1588
1652
|
}
|
1589
1653
|
|
1590
1654
|
void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples) {
|
1591
|
-
|
1592
|
-
TypedData_Get_Struct(self_instance,
|
1655
|
+
thread_context_collector_state *state;
|
1656
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1593
1657
|
|
1594
1658
|
ddog_prof_Label labels[] = {
|
1595
1659
|
// Providing .num = 0 should not be needed but the tracer-2.7 docker image ships a buggy gcc that complains about this
|
@@ -1612,7 +1676,12 @@ void thread_context_collector_sample_skipped_allocation_samples(VALUE self_insta
|
|
1612
1676
|
}
|
1613
1677
|
|
1614
1678
|
static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples) {
|
1679
|
+
debug_enter_unsafe_context();
|
1680
|
+
|
1615
1681
|
thread_context_collector_sample_skipped_allocation_samples(collector_instance, NUM2UINT(skipped_samples));
|
1682
|
+
|
1683
|
+
debug_leave_unsafe_context();
|
1684
|
+
|
1616
1685
|
return Qtrue;
|
1617
1686
|
}
|
1618
1687
|
|
@@ -1638,29 +1707,30 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
|
|
1638
1707
|
// root span id.
|
1639
1708
|
// This matches the semantics of how ddtrace tracing creates a TraceOperation and assigns a local root span to it.
|
1640
1709
|
static void otel_without_ddtrace_trace_identifiers_for(
|
1641
|
-
|
1710
|
+
thread_context_collector_state *state,
|
1642
1711
|
VALUE thread,
|
1643
|
-
|
1712
|
+
trace_identifiers *trace_identifiers_result,
|
1713
|
+
bool is_safe_to_allocate_objects
|
1644
1714
|
) {
|
1645
1715
|
VALUE context_storage = rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
|
1646
1716
|
|
1647
1717
|
// If it exists, context_storage is expected to be an Array[OpenTelemetry::Context]
|
1648
1718
|
if (context_storage == Qnil || !RB_TYPE_P(context_storage, T_ARRAY)) return;
|
1649
1719
|
|
1650
|
-
VALUE otel_current_span_key = get_otel_current_span_key(state);
|
1720
|
+
VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
|
1651
1721
|
if (otel_current_span_key == Qnil) return;
|
1652
1722
|
|
1653
1723
|
int active_context_index = RARRAY_LEN(context_storage) - 1;
|
1654
1724
|
if (active_context_index < 0) return;
|
1655
1725
|
|
1656
|
-
|
1726
|
+
otel_span active_span = otel_span_from(rb_ary_entry(context_storage, active_context_index), otel_current_span_key);
|
1657
1727
|
if (active_span.span == Qnil) return;
|
1658
1728
|
|
1659
|
-
|
1729
|
+
otel_span local_root_span = active_span;
|
1660
1730
|
|
1661
1731
|
// Now find the oldest span starting from the active span that still has the same trace id as the active span
|
1662
1732
|
for (int i = active_context_index - 1; i >= 0; i--) {
|
1663
|
-
|
1733
|
+
otel_span checking_span = otel_span_from(rb_ary_entry(context_storage, i), otel_current_span_key);
|
1664
1734
|
if (checking_span.span == Qnil) return;
|
1665
1735
|
|
1666
1736
|
if (rb_str_equal(active_span.trace_id, checking_span.trace_id) == Qfalse) break;
|
@@ -1680,7 +1750,7 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
1680
1750
|
|
1681
1751
|
VALUE root_span_type = rb_ivar_get(local_root_span.span, at_kind_id /* @kind */);
|
1682
1752
|
// We filter out spans that don't have `kind: :server`
|
1683
|
-
if (root_span_type == Qnil || !RB_TYPE_P(root_span_type, T_SYMBOL) || SYM2ID(root_span_type) != server_id) return;
|
1753
|
+
if (root_span_type == Qnil || !RB_TYPE_P(root_span_type, T_SYMBOL) || !RB_STATIC_SYM_P(root_span_type) || SYM2ID(root_span_type) != server_id) return;
|
1684
1754
|
|
1685
1755
|
VALUE trace_resource = rb_ivar_get(local_root_span.span, at_name_id /* @name */);
|
1686
1756
|
if (!RB_TYPE_P(trace_resource, T_STRING)) return;
|
@@ -1688,8 +1758,8 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
1688
1758
|
trace_identifiers_result->trace_endpoint = trace_resource;
|
1689
1759
|
}
|
1690
1760
|
|
1691
|
-
static
|
1692
|
-
|
1761
|
+
static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key) {
|
1762
|
+
otel_span failed = {.span = Qnil, .span_id = Qnil, .trace_id = Qnil};
|
1693
1763
|
|
1694
1764
|
if (otel_context == Qnil) return failed;
|
1695
1765
|
|
@@ -1697,7 +1767,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp
|
|
1697
1767
|
if (context_entries == Qnil || !RB_TYPE_P(context_entries, T_HASH)) return failed;
|
1698
1768
|
|
1699
1769
|
// If it exists, context_entries is expected to be a Hash[OpenTelemetry::Context::Key, OpenTelemetry::Trace::Span]
|
1700
|
-
VALUE span =
|
1770
|
+
VALUE span = safely_lookup_hash_without_going_into_ruby_code(context_entries, otel_current_span_key);
|
1701
1771
|
if (span == Qnil) return failed;
|
1702
1772
|
|
1703
1773
|
// If it exists, span_context is expected to be a OpenTelemetry::Trace::SpanContext (don't confuse it with OpenTelemetry::Context)
|
@@ -1708,7 +1778,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp
|
|
1708
1778
|
VALUE trace_id = rb_ivar_get(span_context, at_trace_id_id /* @trace_id */);
|
1709
1779
|
if (span_id == Qnil || trace_id == Qnil || !RB_TYPE_P(span_id, T_STRING) || !RB_TYPE_P(trace_id, T_STRING)) return failed;
|
1710
1780
|
|
1711
|
-
return (
|
1781
|
+
return (otel_span) {.span = span, .span_id = span_id, .trace_id = trace_id};
|
1712
1782
|
}
|
1713
1783
|
|
1714
1784
|
// Otel span ids are represented as a big-endian 8-byte string
|
@@ -1810,8 +1880,8 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
1810
1880
|
// NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
|
1811
1881
|
// exists to enable testing.
|
1812
1882
|
VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns) {
|
1813
|
-
|
1814
|
-
TypedData_Get_Struct(self_instance,
|
1883
|
+
thread_context_collector_state *state;
|
1884
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1815
1885
|
|
1816
1886
|
if (!state->timeline_enabled) rb_raise(rb_eRuntimeError, "GVL profiling requires timeline to be enabled");
|
1817
1887
|
|
@@ -1825,7 +1895,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
1825
1895
|
return Qfalse;
|
1826
1896
|
}
|
1827
1897
|
|
1828
|
-
|
1898
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
|
1829
1899
|
|
1830
1900
|
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may chose to push an
|
1831
1901
|
// extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
|
@@ -1851,10 +1921,10 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
1851
1921
|
// need to take when sampling cpu/wall-time for a thread that's in the "Waiting for GVL" state.
|
1852
1922
|
__attribute__((warn_unused_result))
|
1853
1923
|
static bool handle_gvl_waiting(
|
1854
|
-
|
1924
|
+
thread_context_collector_state *state,
|
1855
1925
|
VALUE thread_being_sampled,
|
1856
1926
|
VALUE stack_from_thread,
|
1857
|
-
|
1927
|
+
per_thread_context *thread_context,
|
1858
1928
|
sampling_buffer* sampling_buffer,
|
1859
1929
|
long current_cpu_time_ns
|
1860
1930
|
) {
|
@@ -1939,7 +2009,8 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
1939
2009
|
gvl_waiting_started_wall_time_ns,
|
1940
2010
|
NULL,
|
1941
2011
|
NULL,
|
1942
|
-
false // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
|
2012
|
+
/* 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
|
2013
|
+
/* is_safe_to_allocate_objects: */ true // This is similar to a regular cpu/wall sample, so it's also safe
|
1943
2014
|
);
|
1944
2015
|
}
|
1945
2016
|
|
@@ -1949,40 +2020,62 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
1949
2020
|
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
1950
2021
|
ENFORCE_THREAD(thread);
|
1951
2022
|
|
2023
|
+
debug_enter_unsafe_context();
|
2024
|
+
|
1952
2025
|
thread_context_collector_on_gvl_waiting(thread_from_thread_object(thread));
|
2026
|
+
|
2027
|
+
debug_leave_unsafe_context();
|
2028
|
+
|
1953
2029
|
return Qnil;
|
1954
2030
|
}
|
1955
2031
|
|
1956
2032
|
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
1957
2033
|
ENFORCE_THREAD(thread);
|
1958
2034
|
|
2035
|
+
debug_enter_unsafe_context();
|
2036
|
+
|
1959
2037
|
intptr_t gvl_waiting_at = gvl_profiling_state_thread_object_get(thread);
|
2038
|
+
|
2039
|
+
debug_leave_unsafe_context();
|
2040
|
+
|
1960
2041
|
return LONG2NUM(gvl_waiting_at);
|
1961
2042
|
}
|
1962
2043
|
|
1963
2044
|
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
1964
2045
|
ENFORCE_THREAD(thread);
|
1965
2046
|
|
1966
|
-
|
2047
|
+
debug_enter_unsafe_context();
|
2048
|
+
|
2049
|
+
VALUE result = thread_context_collector_on_gvl_running(thread_from_thread_object(thread)) == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
|
2050
|
+
|
2051
|
+
debug_leave_unsafe_context();
|
2052
|
+
|
2053
|
+
return result;
|
1967
2054
|
}
|
1968
2055
|
|
1969
2056
|
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
|
1970
2057
|
ENFORCE_THREAD(thread);
|
1971
2058
|
|
1972
|
-
|
2059
|
+
debug_enter_unsafe_context();
|
2060
|
+
|
2061
|
+
VALUE result = thread_context_collector_sample_after_gvl_running(
|
1973
2062
|
collector_instance,
|
1974
2063
|
thread,
|
1975
2064
|
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
1976
2065
|
);
|
2066
|
+
|
2067
|
+
debug_leave_unsafe_context();
|
2068
|
+
|
2069
|
+
return result;
|
1977
2070
|
}
|
1978
2071
|
|
1979
2072
|
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns) {
|
1980
2073
|
ENFORCE_THREAD(thread);
|
1981
2074
|
|
1982
|
-
|
1983
|
-
TypedData_Get_Struct(collector_instance,
|
2075
|
+
thread_context_collector_state *state;
|
2076
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
1984
2077
|
|
1985
|
-
|
2078
|
+
per_thread_context *thread_context = get_context_for(thread, state);
|
1986
2079
|
if (thread_context == NULL) rb_raise(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
1987
2080
|
|
1988
2081
|
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
@@ -1992,11 +2085,45 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
1992
2085
|
|
1993
2086
|
#else
|
1994
2087
|
static bool handle_gvl_waiting(
|
1995
|
-
DDTRACE_UNUSED
|
2088
|
+
DDTRACE_UNUSED thread_context_collector_state *state,
|
1996
2089
|
DDTRACE_UNUSED VALUE thread_being_sampled,
|
1997
2090
|
DDTRACE_UNUSED VALUE stack_from_thread,
|
1998
|
-
DDTRACE_UNUSED
|
2091
|
+
DDTRACE_UNUSED per_thread_context *thread_context,
|
1999
2092
|
DDTRACE_UNUSED sampling_buffer* sampling_buffer,
|
2000
2093
|
DDTRACE_UNUSED long current_cpu_time_ns
|
2001
2094
|
) { return false; }
|
2002
2095
|
#endif // NO_GVL_INSTRUMENTATION
|
2096
|
+
|
2097
|
+
#define MAX_SAFE_LOOKUP_SIZE 16
|
2098
|
+
|
2099
|
+
typedef struct { VALUE lookup_key; VALUE result; } safe_lookup_hash_state;
|
2100
|
+
|
2101
|
+
static int safe_lookup_hash_iterate(VALUE key, VALUE value, VALUE state_ptr) {
|
2102
|
+
safe_lookup_hash_state *state = (safe_lookup_hash_state *) state_ptr;
|
2103
|
+
|
2104
|
+
if (key == state->lookup_key) {
|
2105
|
+
state->result = value;
|
2106
|
+
return ST_STOP;
|
2107
|
+
}
|
2108
|
+
|
2109
|
+
return ST_CONTINUE;
|
2110
|
+
}
|
2111
|
+
|
2112
|
+
// This method exists because we need to look up a hash during sampling, but we don't want to invoke any
|
2113
|
+
// Ruby code as a side effect. To be able to look up by hash, `rb_hash_lookup` calls `#hash` on the key,
|
2114
|
+
// which we want to avoid.
|
2115
|
+
// Thus, instead, we opt to just iterate through the hash and check if we can find what we're looking for.
|
2116
|
+
//
|
2117
|
+
// To avoid having too much overhead here we only iterate in hashes up to MAX_SAFE_LOOKUP_SIZE.
|
2118
|
+
// Note that we don't even try to iterate if the hash is bigger -- this is to avoid flaky behavior where
|
2119
|
+
// depending on the internal storage order of the hash we may or not find the key, and instead we always
|
2120
|
+
// enforce the size.
|
2121
|
+
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key) {
|
2122
|
+
if (!RB_TYPE_P(hash, T_HASH) || RHASH_SIZE(hash) > MAX_SAFE_LOOKUP_SIZE) return Qnil;
|
2123
|
+
|
2124
|
+
safe_lookup_hash_state state = {.lookup_key = key, .result = Qnil};
|
2125
|
+
|
2126
|
+
rb_hash_foreach(hash, safe_lookup_hash_iterate, (VALUE) &state);
|
2127
|
+
|
2128
|
+
return state.result;
|
2129
|
+
}
|