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