datadog 2.41.0 → 2.43.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 +57 -1
- data/README.md +3 -0
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +120 -61
- data/ext/datadog_profiling_native_extension/collectors_stack.c +11 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +156 -108
- 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 +351 -255
- data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -24
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +518 -442
- 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 +106 -61
- data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +2 -1
- 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 +247 -0
- data/ext/libdatadog_api/otel_thread_context.h +5 -0
- data/ext/libdatadog_api/trace_exporter.c +642 -22
- 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/open_feature/transport.rb +5 -0
- 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/info.rb +2 -6
- 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/contrib/sequel/jdbc_connection_string.rb +140 -0
- data/lib/datadog/tracing/contrib/sequel/utils.rb +49 -62
- 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 +34 -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/tracing/transport/native.rb +17 -25
- data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
- data/lib/datadog/tracing/transport/traces.rb +2 -22
- data/lib/datadog/version.rb +1 -1
- metadata +16 -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
|
|
|
@@ -886,7 +888,12 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
|
886
888
|
}
|
|
887
889
|
|
|
888
890
|
// Here we record the wall-time first and in on_gc_finish we record it second to try to avoid having wall-time be slightly < cpu-time
|
|
889
|
-
|
|
891
|
+
long wall_time_at_start_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
|
892
|
+
|
|
893
|
+
// If our start timestamp is not OK, we skip tracking this GC as well
|
|
894
|
+
if (wall_time_at_start_ns == 0) return;
|
|
895
|
+
|
|
896
|
+
thread_context->gc_tracking.wall_time_at_start_ns = wall_time_at_start_ns;
|
|
890
897
|
thread_context->gc_tracking.cpu_time_at_start_ns = cpu_time_now_ns(thread_context);
|
|
891
898
|
}
|
|
892
899
|
|
|
@@ -915,7 +922,8 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
915
922
|
long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
|
|
916
923
|
long wall_time_at_start_ns = thread_context->gc_tracking.wall_time_at_start_ns;
|
|
917
924
|
|
|
918
|
-
|
|
925
|
+
// Both fields are always set and cleared together, so checking one is enough
|
|
926
|
+
if (cpu_time_at_start_ns == INVALID_TIME) {
|
|
919
927
|
// If this happened, it means that on_gc_start was either never called for the thread OR it was called but no thread
|
|
920
928
|
// context existed at the time. The former can be the result of a bug, but since we can't distinguish them, we just
|
|
921
929
|
// do nothing.
|
|
@@ -983,7 +991,14 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
983
991
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
984
992
|
|
|
985
993
|
if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
|
|
986
|
-
|
|
994
|
+
// Rarely, we might be called with nothing to do, as an earlier call already flushed the needed info.
|
|
995
|
+
// This is because Ruby clears the "pending" flag for an entire batch of postponed jobs BEFORE running any of them
|
|
996
|
+
// (see `rb_postponed_job_flush`). Thus, if another GC happens while an earlier job in the postponed batch is
|
|
997
|
+
// running (and other parts of the profiler such as the heap profiler can trigger this)
|
|
998
|
+
// then `on_gc_finish` records that data and asks for a second flush, even though Ruby has not yet run the
|
|
999
|
+
// first one. Because of this, Ruby can call us once more with nothing left to do.
|
|
1000
|
+
state->stats.gc_samples_skipped_nothing_to_flush++;
|
|
1001
|
+
return Qnil;
|
|
987
1002
|
}
|
|
988
1003
|
|
|
989
1004
|
int max_labels_needed_for_gc = 7; // Magic number gets validated inside gc_profiling_set_metadata
|
|
@@ -1017,13 +1032,23 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
1017
1032
|
|
|
1018
1033
|
state->stats.gc_samples++;
|
|
1019
1034
|
|
|
1020
|
-
// Let recorder do any cleanup/updates it requires after a GC step.
|
|
1021
|
-
recorder_after_gc_step(state->recorder_instance);
|
|
1022
|
-
|
|
1023
1035
|
// Return a VALUE to make it easier to call this function from Ruby APIs that expect a return value (such as rb_rescue2)
|
|
1024
1036
|
return Qnil;
|
|
1025
1037
|
}
|
|
1026
1038
|
|
|
1039
|
+
// Let the recorder do any cleanup/updates it requires after a GC step.
|
|
1040
|
+
//
|
|
1041
|
+
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
1042
|
+
// Assumption 2: This function is allowed to raise exceptions. Caller is responsible for handling them, if needed.
|
|
1043
|
+
VALUE thread_context_collector_heap_update_may_lose_gvl(VALUE self_instance) {
|
|
1044
|
+
thread_context_collector_state *state;
|
|
1045
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1046
|
+
|
|
1047
|
+
recorder_heap_update_may_lose_gvl(state->recorder_instance);
|
|
1048
|
+
|
|
1049
|
+
return Qnil; // for rb_rescue2
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1027
1052
|
static void trigger_sample_for_thread(
|
|
1028
1053
|
thread_context_collector_state *state,
|
|
1029
1054
|
VALUE thread_being_sampled,
|
|
@@ -1033,10 +1058,7 @@ static void trigger_sample_for_thread(
|
|
|
1033
1058
|
// These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
|
|
1034
1059
|
ddog_CharSlice *ruby_vm_type,
|
|
1035
1060
|
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
|
|
1061
|
+
bool is_gvl_waiting_state
|
|
1040
1062
|
) {
|
|
1041
1063
|
int max_label_count =
|
|
1042
1064
|
1 + // thread id
|
|
@@ -1074,11 +1096,11 @@ static void trigger_sample_for_thread(
|
|
|
1074
1096
|
}
|
|
1075
1097
|
|
|
1076
1098
|
trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
|
|
1077
|
-
trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result
|
|
1099
|
+
trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result);
|
|
1078
1100
|
|
|
1079
1101
|
if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
|
|
1080
1102
|
// 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
|
|
1103
|
+
otel_without_ddtrace_trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result);
|
|
1082
1104
|
}
|
|
1083
1105
|
|
|
1084
1106
|
if (trace_identifiers_result.valid) {
|
|
@@ -1345,7 +1367,6 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
|
1345
1367
|
rb_str_concat(result, rb_sprintf(" main_thread=%"PRIsVALUE, state->main_thread));
|
|
1346
1368
|
rb_str_concat(result, rb_sprintf(" gc_tracking=%"PRIsVALUE, gc_tracking_as_ruby_hash(state)));
|
|
1347
1369
|
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
1370
|
|
|
1350
1371
|
return result;
|
|
1351
1372
|
}
|
|
@@ -1385,6 +1406,7 @@ static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE has
|
|
|
1385
1406
|
ID2SYM(rb_intern("sample_count")), /* => */ UINT2NUM(state->stats.sample_count),
|
|
1386
1407
|
ID2SYM(rb_intern("gc_samples")), /* => */ UINT2NUM(state->stats.gc_samples),
|
|
1387
1408
|
ID2SYM(rb_intern("gc_samples_missed_due_to_missing_context")), /* => */ UINT2NUM(state->stats.gc_samples_missed_due_to_missing_context),
|
|
1409
|
+
ID2SYM(rb_intern("gc_samples_skipped_nothing_to_flush")), /* => */ UINT2NUM(state->stats.gc_samples_skipped_nothing_to_flush),
|
|
1388
1410
|
ID2SYM(rb_intern("inactive_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.inactive_thread_samples_skipped),
|
|
1389
1411
|
ID2SYM(rb_intern("profiler_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.profiler_thread_samples_skipped),
|
|
1390
1412
|
};
|
|
@@ -1427,9 +1449,6 @@ static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collec
|
|
|
1427
1449
|
}
|
|
1428
1450
|
|
|
1429
1451
|
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, per_thread_context *thread_context) {
|
|
1430
|
-
// If we didn't have a time for the previous sample, we use the current one
|
|
1431
|
-
if (*time_at_previous_sample_ns == INVALID_TIME) *time_at_previous_sample_ns = current_time_ns;
|
|
1432
|
-
|
|
1433
1452
|
// We don't expect to be sampling a thread (and thus updating these counters) while Ruby is doing GC (between
|
|
1434
1453
|
// `thread_context_collector_on_gc_start` and `thread_context_collector_on_gc_finish`)
|
|
1435
1454
|
if (thread_context->gc_tracking.cpu_time_at_start_ns != INVALID_TIME) {
|
|
@@ -1451,6 +1470,13 @@ static long update_time_since_previous_sample(long *time_at_previous_sample_ns,
|
|
|
1451
1470
|
}
|
|
1452
1471
|
|
|
1453
1472
|
static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns) {
|
|
1473
|
+
// A previous `cpu_time_now_ns` may have failed to read the clock and thus invalidated our baseline; in that case we
|
|
1474
|
+
// use the current time, so this sample gets no cpu-time and the next one gets an accurate delta.
|
|
1475
|
+
// Note wall-time never needs this: it is always seeded with a real value in `initialize_context`.
|
|
1476
|
+
if (thread_context->cpu_time_at_previous_sample_ns == INVALID_TIME) {
|
|
1477
|
+
thread_context->cpu_time_at_previous_sample_ns = current_cpu_time_ns;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1454
1480
|
long elapsed_time_ns = update_time_since_previous_sample(
|
|
1455
1481
|
&thread_context->cpu_time_at_previous_sample_ns,
|
|
1456
1482
|
current_cpu_time_ns,
|
|
@@ -1515,14 +1541,14 @@ VALUE enforce_thread_context_collector_instance(VALUE object) {
|
|
|
1515
1541
|
return object;
|
|
1516
1542
|
}
|
|
1517
1543
|
|
|
1518
|
-
//
|
|
1519
|
-
//
|
|
1520
|
-
//
|
|
1521
|
-
void
|
|
1544
|
+
// Commit any pending heap allocation recordings.
|
|
1545
|
+
// Recording an allocation only puts it on a pending list, because taking the weak reference the heap profiler needs
|
|
1546
|
+
// can't be done from inside the NEWOBJ tracepoint -- see `heap_recorder_commit_recordings_may_lose_gvl`.
|
|
1547
|
+
void thread_context_collector_commit_heap_recordings_may_lose_gvl(VALUE self_instance) {
|
|
1522
1548
|
thread_context_collector_state *state;
|
|
1523
1549
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1524
1550
|
|
|
1525
|
-
|
|
1551
|
+
recorder_commit_heap_recordings_may_lose_gvl(state->recorder_instance);
|
|
1526
1552
|
}
|
|
1527
1553
|
|
|
1528
1554
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
@@ -1547,8 +1573,7 @@ static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_ins
|
|
|
1547
1573
|
static void trace_identifiers_for(
|
|
1548
1574
|
thread_context_collector_state *state,
|
|
1549
1575
|
VALUE thread,
|
|
1550
|
-
trace_identifiers *trace_identifiers_result
|
|
1551
|
-
bool is_safe_to_allocate_objects
|
|
1576
|
+
trace_identifiers *trace_identifiers_result
|
|
1552
1577
|
) {
|
|
1553
1578
|
if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
|
|
1554
1579
|
if (state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY) return;
|
|
@@ -1569,7 +1594,7 @@ static void trace_identifiers_for(
|
|
|
1569
1594
|
VALUE numeric_span_id = Qnil;
|
|
1570
1595
|
|
|
1571
1596
|
if (otel_values != Qnil) {
|
|
1572
|
-
ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values
|
|
1597
|
+
ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values);
|
|
1573
1598
|
}
|
|
1574
1599
|
|
|
1575
1600
|
if (root_span == Qnil || (active_span == Qnil && numeric_span_id == Qnil)) return;
|
|
@@ -1665,11 +1690,11 @@ bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
|
1665
1690
|
return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
|
|
1666
1691
|
}
|
|
1667
1692
|
|
|
1668
|
-
// This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should
|
|
1669
|
-
// Ruby heap.
|
|
1693
|
+
// This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
|
|
1694
|
+
// Ruby heap nor release the GVL (https://github.com/DataDog/dd-trace-rb/pull/4240).
|
|
1670
1695
|
//
|
|
1671
|
-
// Returns true if
|
|
1672
|
-
// tracepoint, such as allocate new objects), and false if it doesn't
|
|
1696
|
+
// Returns true if `thread_context_collector_commit_heap_recordings_may_lose_gvl` needs to be called (to do work
|
|
1697
|
+
// that can't be done from inside the tracepoint, such as allocate new objects), and false if it doesn't
|
|
1673
1698
|
//
|
|
1674
1699
|
// The callers must ensure thread_context is non-NULL.
|
|
1675
1700
|
bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object) {
|
|
@@ -1714,13 +1739,10 @@ bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_
|
|
|
1714
1739
|
// Thus, we need to make sure there's actually a class before getting its name.
|
|
1715
1740
|
|
|
1716
1741
|
if (klass != 0) {
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
if (name_length > 0) {
|
|
1721
|
-
class_name = (ddog_CharSlice) {.ptr = name, .len = name_length};
|
|
1742
|
+
VALUE name = ddtrace_alloc_free_rb_mod_name(klass);
|
|
1743
|
+
if (name != Qnil) {
|
|
1744
|
+
class_name = char_slice_from_ruby_string(name);
|
|
1722
1745
|
} else {
|
|
1723
|
-
// @ivoanjo: I'm not sure this can ever happen, but just-in-case
|
|
1724
1746
|
class_name = ruby_value_type_to_class_name(type);
|
|
1725
1747
|
}
|
|
1726
1748
|
} else {
|
|
@@ -1740,23 +1762,22 @@ bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_
|
|
|
1740
1762
|
class_name = ruby_vm_type; // For other weird internal things we just use the VM type
|
|
1741
1763
|
}
|
|
1742
1764
|
|
|
1743
|
-
bool needs_after_allocation = track_object(state->recorder_instance, new_object, sample_weight, class_name);
|
|
1744
|
-
|
|
1745
1765
|
VALUE current_thread = rb_thread_current();
|
|
1746
1766
|
|
|
1767
|
+
heap_sample_values heap_sample = {.new_object = new_object, .alloc_class = class_name};
|
|
1768
|
+
|
|
1747
1769
|
trigger_sample_for_thread(
|
|
1748
1770
|
state,
|
|
1749
1771
|
current_thread,
|
|
1750
1772
|
thread_context,
|
|
1751
|
-
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample =
|
|
1773
|
+
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = &heap_sample},
|
|
1752
1774
|
INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
|
|
1753
1775
|
&ruby_vm_type,
|
|
1754
1776
|
&class_name,
|
|
1755
|
-
/* is_gvl_waiting_state: */ false
|
|
1756
|
-
/* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
|
|
1777
|
+
/* is_gvl_waiting_state: */ false
|
|
1757
1778
|
);
|
|
1758
1779
|
|
|
1759
|
-
return
|
|
1780
|
+
return heap_sample.out_needs_commit;
|
|
1760
1781
|
}
|
|
1761
1782
|
|
|
1762
1783
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
@@ -1769,13 +1790,13 @@ static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collecto
|
|
|
1769
1790
|
|
|
1770
1791
|
debug_enter_unsafe_context();
|
|
1771
1792
|
|
|
1772
|
-
bool
|
|
1793
|
+
bool needs_commit = thread_context_collector_sample_allocation(collector_instance, thread_context, NUM2UINT(sample_weight), new_object);
|
|
1773
1794
|
|
|
1774
1795
|
debug_leave_unsafe_context();
|
|
1775
1796
|
|
|
1776
1797
|
// We could instead choose to automatically trigger the after allocation here; yet, it seems kinda nice to keep it manual for
|
|
1777
1798
|
// the tests so we can pull on each lever separately and observe "the sausage being made" in steps
|
|
1778
|
-
return
|
|
1799
|
+
return needs_commit ? Qtrue : Qfalse;
|
|
1779
1800
|
}
|
|
1780
1801
|
|
|
1781
1802
|
static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
|
|
@@ -1825,26 +1846,57 @@ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
|
|
|
1825
1846
|
return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
|
|
1826
1847
|
}
|
|
1827
1848
|
|
|
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**
|
|
1849
|
+
static VALUE otel_current_span_key_not_found(DDTRACE_UNUSED VALUE _unused, DDTRACE_UNUSED VALUE _exception) {
|
|
1850
|
+
return Qnil;
|
|
1851
|
+
}
|
|
1840
1852
|
|
|
1841
|
-
|
|
1842
|
-
|
|
1853
|
+
static VALUE get_otel_current_span_key(thread_context_collector_state *state) {
|
|
1854
|
+
if (state->otel_current_span_key == OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED) {
|
|
1855
|
+
// Extracting this needs calling into Ruby code, which we don't want to do in the middle of a sample.
|
|
1856
|
+
// So instead we signal that we want this, and let it be resolved separately.
|
|
1857
|
+
state->otel_current_span_key = OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED;
|
|
1858
|
+
return Qnil;
|
|
1843
1859
|
}
|
|
1844
1860
|
|
|
1861
|
+
// We asked for it, but it hasn't been extracted yet
|
|
1862
|
+
if (is_otel_current_span_key_needed(state)) return Qnil;
|
|
1863
|
+
|
|
1845
1864
|
return state->otel_current_span_key;
|
|
1846
1865
|
}
|
|
1847
1866
|
|
|
1867
|
+
static bool is_otel_current_span_key_needed(thread_context_collector_state *state) {
|
|
1868
|
+
return state->otel_current_span_key == OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
// Extracts `otel_current_span_key`, if a sample asked for it (see `get_otel_current_span_key`).
|
|
1872
|
+
// This is deliberately not done during sampling because we can lose the GVL.
|
|
1873
|
+
//
|
|
1874
|
+
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
1875
|
+
void thread_context_collector_resolve_otel_span_key_may_lose_gvl(VALUE self_instance) {
|
|
1876
|
+
thread_context_collector_state *state;
|
|
1877
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1878
|
+
|
|
1879
|
+
if (!is_otel_current_span_key_needed(state)) return;
|
|
1880
|
+
|
|
1881
|
+
// If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
|
|
1882
|
+
// gets refactored, we should not fall on our face).
|
|
1883
|
+
//
|
|
1884
|
+
// Note that we use `rb_rescue2` and not `rb_protect` so that we only swallow exceptions: anything else that can
|
|
1885
|
+
// unwind through here (e.g. a `Thread#kill`) is not ours to swallow. `rb_rescue2` also takes care of restoring
|
|
1886
|
+
// any previously-pending exception for us.
|
|
1887
|
+
VALUE span_key = rb_rescue2(
|
|
1888
|
+
read_otel_current_span_key_const,
|
|
1889
|
+
Qnil,
|
|
1890
|
+
otel_current_span_key_not_found,
|
|
1891
|
+
Qnil,
|
|
1892
|
+
rb_eException, // rb_eException is the base class of all Ruby exceptions
|
|
1893
|
+
0 // Required by API to be the last argument
|
|
1894
|
+
);
|
|
1895
|
+
|
|
1896
|
+
// 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
|
|
1897
|
+
state->otel_current_span_key = span_key;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1848
1900
|
// This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
|
|
1849
1901
|
// differently, and this codepath handles it.
|
|
1850
1902
|
static void ddtrace_otel_trace_identifiers_for(
|
|
@@ -1853,8 +1905,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
1853
1905
|
VALUE *root_span,
|
|
1854
1906
|
VALUE *numeric_span_id,
|
|
1855
1907
|
VALUE active_span,
|
|
1856
|
-
VALUE otel_values
|
|
1857
|
-
bool is_safe_to_allocate_objects
|
|
1908
|
+
VALUE otel_values
|
|
1858
1909
|
) {
|
|
1859
1910
|
VALUE resolved_numeric_span_id =
|
|
1860
1911
|
active_span == Qnil ?
|
|
@@ -1865,7 +1916,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
1865
1916
|
|
|
1866
1917
|
if (resolved_numeric_span_id == Qnil) return;
|
|
1867
1918
|
|
|
1868
|
-
VALUE otel_current_span_key = get_otel_current_span_key(state
|
|
1919
|
+
VALUE otel_current_span_key = get_otel_current_span_key(state);
|
|
1869
1920
|
if (otel_current_span_key == Qnil) return;
|
|
1870
1921
|
VALUE current_trace = *active_trace;
|
|
1871
1922
|
|
|
@@ -1985,15 +2036,14 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
|
|
|
1985
2036
|
static void otel_without_ddtrace_trace_identifiers_for(
|
|
1986
2037
|
thread_context_collector_state *state,
|
|
1987
2038
|
VALUE thread,
|
|
1988
|
-
trace_identifiers *trace_identifiers_result
|
|
1989
|
-
bool is_safe_to_allocate_objects
|
|
2039
|
+
trace_identifiers *trace_identifiers_result
|
|
1990
2040
|
) {
|
|
1991
2041
|
VALUE context_storage = otel_context_storage_for(state, thread);
|
|
1992
2042
|
|
|
1993
2043
|
// If it exists, context_storage is expected to be an Array[OpenTelemetry::Context]
|
|
1994
2044
|
if (context_storage == Qnil || !RB_TYPE_P(context_storage, T_ARRAY)) return;
|
|
1995
2045
|
|
|
1996
|
-
VALUE otel_current_span_key = get_otel_current_span_key(state
|
|
2046
|
+
VALUE otel_current_span_key = get_otel_current_span_key(state);
|
|
1997
2047
|
if (otel_current_span_key == Qnil) return;
|
|
1998
2048
|
|
|
1999
2049
|
long active_context_index = RARRAY_LEN(context_storage) - 1;
|
|
@@ -2195,12 +2245,9 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2195
2245
|
}
|
|
2196
2246
|
|
|
2197
2247
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
2198
|
-
//
|
|
2248
|
+
// We must only use async-signal-safe functions here, see notes in the caller
|
|
2199
2249
|
__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
|
-
|
|
2250
|
+
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
2251
|
// Bump the event counter and clears the state bit to "running"
|
|
2205
2252
|
uint64_t counter_portion = thread_context->gvl_state_change_count >> 1;
|
|
2206
2253
|
thread_context->gvl_state_change_count = ((counter_portion + 1) << 1) | GVL_RUNNING;
|
|
@@ -2217,7 +2264,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2217
2264
|
|
|
2218
2265
|
long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - gvl_waiting_at;
|
|
2219
2266
|
|
|
2220
|
-
bool should_sample = waiting_for_gvl_duration_ns >=
|
|
2267
|
+
bool should_sample = waiting_for_gvl_duration_ns >= waiting_for_gvl_threshold_ns;
|
|
2221
2268
|
|
|
2222
2269
|
if (should_sample) {
|
|
2223
2270
|
// We flip the gvl_waiting_at to negative to mark that the thread is now running and no longer waiting
|
|
@@ -2392,8 +2439,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2392
2439
|
gvl_waiting_started_wall_time_ns,
|
|
2393
2440
|
NULL,
|
|
2394
2441
|
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
|
|
2442
|
+
/* 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
2443
|
);
|
|
2398
2444
|
}
|
|
2399
2445
|
|
|
@@ -2413,15 +2459,18 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2413
2459
|
return result;
|
|
2414
2460
|
}
|
|
2415
2461
|
|
|
2416
|
-
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE
|
|
2462
|
+
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE waiting_for_gvl_threshold_ns) {
|
|
2417
2463
|
ENFORCE_THREAD(thread);
|
|
2464
|
+
ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
|
|
2418
2465
|
|
|
2419
2466
|
debug_enter_unsafe_context();
|
|
2420
2467
|
|
|
2421
2468
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2422
2469
|
VALUE result;
|
|
2423
2470
|
if (thread_context) {
|
|
2424
|
-
|
|
2471
|
+
on_gvl_running_result gvl_running_result =
|
|
2472
|
+
thread_context_collector_on_gvl_running(thread, thread_context, NUM2UINT(waiting_for_gvl_threshold_ns));
|
|
2473
|
+
result = gvl_running_result.action == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
|
|
2425
2474
|
} else {
|
|
2426
2475
|
result = Qfalse;
|
|
2427
2476
|
}
|
|
@@ -2431,11 +2480,10 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2431
2480
|
return result;
|
|
2432
2481
|
}
|
|
2433
2482
|
|
|
2434
|
-
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread
|
|
2483
|
+
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
|
|
2435
2484
|
ENFORCE_THREAD(thread);
|
|
2436
|
-
ENFORCE_BOOLEAN(allow_exception);
|
|
2437
2485
|
|
|
2438
|
-
|
|
2486
|
+
debug_enter_unsafe_context();
|
|
2439
2487
|
|
|
2440
2488
|
VALUE result = thread_context_collector_sample_after_gvl_running(
|
|
2441
2489
|
collector_instance,
|
|
@@ -2443,7 +2491,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2443
2491
|
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
2444
2492
|
);
|
|
2445
2493
|
|
|
2446
|
-
|
|
2494
|
+
debug_leave_unsafe_context();
|
|
2447
2495
|
|
|
2448
2496
|
return result;
|
|
2449
2497
|
}
|