datadog 2.43.0 → 2.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +136 -132
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -5
- data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +0 -2
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +19 -20
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +5 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
- data/ext/datadog_profiling_native_extension/collectors_stack.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +146 -109
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -2
- data/ext/datadog_profiling_native_extension/extconf.rb +6 -2
- data/ext/datadog_profiling_native_extension/heap_recorder.c +2 -2
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +122 -53
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +6 -2
- data/ext/datadog_profiling_native_extension/profiling.c +22 -23
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +3 -1
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -0
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +0 -2
- data/ext/datadog_profiling_native_extension/stack_recorder.c +10 -28
- data/ext/datadog_profiling_native_extension/stack_recorder.h +0 -1
- data/ext/datadog_profiling_native_extension/time_helpers.c +2 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +13 -14
- data/ext/libdatadog_api/di.c +8 -3
- data/ext/libdatadog_api/extconf.rb +1 -1
- data/ext/libdatadog_api/feature_flags.c +8 -0
- data/ext/libdatadog_api/init.c +3 -1
- data/ext/libdatadog_api/otel_thread_context.c +38 -17
- data/ext/libdatadog_api/trace_exporter.c +77 -11
- data/ext/libdatadog_extconf_helpers.rb +14 -9
- data/lib/datadog/ai_guard/component.rb +17 -8
- data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
- data/lib/datadog/ai_guard/configuration.rb +13 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
- data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
- data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
- data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
- data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
- data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
- data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +4 -60
- data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
- data/lib/datadog/ai_guard/evaluation.rb +25 -11
- data/lib/datadog/ai_guard/ext.rb +1 -0
- data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +6 -4
- data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
- data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
- data/lib/datadog/ai_guard/redaction/result.rb +29 -0
- data/lib/datadog/ai_guard/redaction.rb +80 -0
- data/lib/datadog/ai_guard.rb +58 -57
- data/lib/datadog/core/configuration/components.rb +9 -1
- data/lib/datadog/core/configuration/settings.rb +5 -2
- data/lib/datadog/core/configuration/supported_configurations.rb +8 -0
- data/lib/datadog/core/remote/client/capabilities.rb +10 -4
- data/lib/datadog/core/remote/component.rb +9 -2
- data/lib/datadog/core/remote/worker.rb +8 -0
- data/lib/datadog/di/instrumenter.rb +1 -1
- data/lib/datadog/di/probe_manager.rb +10 -5
- data/lib/datadog/di/proc_responder.rb +1 -1
- data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
- data/lib/datadog/open_feature/agentless/parser.rb +43 -0
- data/lib/datadog/open_feature/agentless/response.rb +19 -0
- data/lib/datadog/open_feature/agentless/transport.rb +103 -0
- data/lib/datadog/open_feature/component.rb +57 -1
- data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
- data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
- data/lib/datadog/open_feature/configuration/source.rb +43 -0
- data/lib/datadog/open_feature/configuration.rb +118 -0
- data/lib/datadog/open_feature/native_evaluator.rb +1 -14
- data/lib/datadog/open_feature/remote.rb +5 -5
- data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +7 -2
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
- data/lib/datadog/profiling/component.rb +2 -3
- data/lib/datadog/profiling/exporter.rb +7 -5
- data/lib/datadog/symbol_database/extractor.rb +84 -102
- data/lib/datadog/tracing/component.rb +3 -1
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/configuration/settings.rb +9 -2
- data/lib/datadog/tracing/context.rb +24 -2
- data/lib/datadog/tracing/context_provider.rb +9 -5
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +6 -3
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -0
- data/lib/datadog/tracing/metadata/ext.rb +3 -0
- data/lib/datadog/tracing/otel_thread_context.rb +76 -11
- data/lib/datadog/tracing/tracer.rb +15 -2
- data/lib/datadog/tracing/transport/native.rb +23 -22
- data/lib/datadog/tracing/transport/trace_formatter.rb +13 -3
- data/lib/datadog/tracing/transport/traces.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- metadata +23 -7
- data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
//
|
|
43
43
|
// When `thread_context_collector_on_gc_start` gets called, the current cpu and wall-time get recorded to the thread
|
|
44
44
|
// context: `cpu_time_at_gc_start_ns` and `wall_time_at_gc_start_ns`.
|
|
45
|
+
// (Ruby won't switch threads during GC, so `gc_start` and `gc_finish` will happen on the same thread)
|
|
45
46
|
//
|
|
46
47
|
// While `cpu_time_at_gc_start_ns` is set, we don't expect the thread to be sampled: the VM is doing GC
|
|
47
48
|
// on the thread holding the GVL so no other samples can/will be triggered until GC finishes.
|
|
@@ -268,7 +269,7 @@ struct per_thread_context {
|
|
|
268
269
|
bool was_skipped_at_last_sample;
|
|
269
270
|
// Set as true for CpuAndWallTimeWorker and IdleSamplingHelper threads.
|
|
270
271
|
// When true, per-tick samples are skipped entirely; the thread is sampled only once per
|
|
271
|
-
// reporting period during
|
|
272
|
+
// reporting period during prepare_serialize.
|
|
272
273
|
//
|
|
273
274
|
// These threads are always in native code so their stacks aren't interesting;
|
|
274
275
|
// the Profiling::Scheduler thread on the other hand does a lot of different
|
|
@@ -311,7 +312,6 @@ static void update_metrics_and_sample(
|
|
|
311
312
|
thread_context_collector_state *state,
|
|
312
313
|
VALUE thread_being_sampled,
|
|
313
314
|
per_thread_context *thread_context,
|
|
314
|
-
long current_cpu_time_ns,
|
|
315
315
|
long current_monotonic_wall_time_ns,
|
|
316
316
|
bool force_sample
|
|
317
317
|
);
|
|
@@ -327,8 +327,8 @@ static void trigger_sample_for_thread(
|
|
|
327
327
|
);
|
|
328
328
|
static VALUE _native_thread_list(VALUE self);
|
|
329
329
|
static void check_frozen_thread(VALUE thread);
|
|
330
|
-
static per_thread_context *get_or_create_context_for(VALUE thread);
|
|
331
|
-
static void initialize_context(VALUE thread, per_thread_context *thread_context);
|
|
330
|
+
static per_thread_context *get_or_create_context_for(VALUE thread, long current_monotonic_wall_time_ns);
|
|
331
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context, long current_monotonic_wall_time_ns);
|
|
332
332
|
static VALUE _native_inspect(VALUE self, VALUE collector_instance);
|
|
333
333
|
static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context);
|
|
334
334
|
static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash);
|
|
@@ -373,7 +373,8 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
|
373
373
|
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE waiting_for_gvl_threshold_ns);
|
|
374
374
|
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
|
|
375
375
|
#endif
|
|
376
|
-
static VALUE
|
|
376
|
+
static VALUE _native_apply_delta_to_time_at_previous_sample_ns(int argc, VALUE *argv, DDTRACE_UNUSED VALUE self);
|
|
377
|
+
static VALUE _native_apply_delta_to_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns);
|
|
377
378
|
static void otel_without_ddtrace_trace_identifiers_for(
|
|
378
379
|
thread_context_collector_state *state,
|
|
379
380
|
VALUE thread,
|
|
@@ -386,6 +387,7 @@ static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE k
|
|
|
386
387
|
static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
387
388
|
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
388
389
|
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
390
|
+
static VALUE _native_prepare_serialize(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
389
391
|
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
390
392
|
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
391
393
|
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample);
|
|
@@ -427,6 +429,7 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
427
429
|
rb_define_singleton_method(testing_module, "_native_remove_per_thread_context_for", _native_remove_per_thread_context_for, 1);
|
|
428
430
|
rb_define_singleton_method(testing_module, "_native_global_reset_per_thread_context", _native_global_reset_per_thread_context, 1);
|
|
429
431
|
rb_define_singleton_method(testing_module, "_native_mark_thread_as_profiler_internal", _native_mark_thread_as_profiler_internal, 1);
|
|
432
|
+
rb_define_singleton_method(testing_module, "_native_prepare_serialize", _native_prepare_serialize, 1);
|
|
430
433
|
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
431
434
|
rb_define_singleton_method(testing_module, "_native_on_gvl_released", _native_on_gvl_released, 1);
|
|
432
435
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
@@ -434,7 +437,8 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
434
437
|
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 2);
|
|
435
438
|
rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 2);
|
|
436
439
|
#endif
|
|
437
|
-
rb_define_singleton_method(testing_module, "
|
|
440
|
+
rb_define_singleton_method(testing_module, "_native_apply_delta_to_time_at_previous_sample_ns", _native_apply_delta_to_time_at_previous_sample_ns, -1);
|
|
441
|
+
rb_define_singleton_method(testing_module, "_native_apply_delta_to_gvl_waiting_at_for", _native_apply_delta_to_gvl_waiting_at_for, 2);
|
|
438
442
|
|
|
439
443
|
at_active_span_id = rb_intern_const("@active_span");
|
|
440
444
|
at_active_trace_id = rb_intern_const("@active_trace");
|
|
@@ -613,7 +617,6 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
613
617
|
// Update this when modifying state struct
|
|
614
618
|
sample_locations_initialize(&state->locations, max_frame_int, show_classes == Qtrue);
|
|
615
619
|
state->recorder_instance = enforce_recorder_instance(recorder_instance);
|
|
616
|
-
recorder_install_on_serialize(recorder_instance, self_instance);
|
|
617
620
|
state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
|
|
618
621
|
state->native_filenames_enabled = (native_filenames_enabled == Qtrue);
|
|
619
622
|
state->show_classes = (show_classes == Qtrue);
|
|
@@ -652,7 +655,7 @@ static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance
|
|
|
652
655
|
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
653
656
|
|
|
654
657
|
bool needs_otel_span_key =
|
|
655
|
-
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(
|
|
658
|
+
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns());
|
|
656
659
|
|
|
657
660
|
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
|
658
661
|
|
|
@@ -698,7 +701,7 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_
|
|
|
698
701
|
|
|
699
702
|
// Record profiler sampling overhead as a placeholder stack
|
|
700
703
|
static void record_sampling_overhead(thread_context_collector_state *state, per_thread_context *current_thread_context) {
|
|
701
|
-
long wall_time_after_sampling = monotonic_wall_time_now_ns(
|
|
704
|
+
long wall_time_after_sampling = monotonic_wall_time_now_ns();
|
|
702
705
|
long cpu_time_after_sampling = cpu_time_now_ns(current_thread_context);
|
|
703
706
|
|
|
704
707
|
long overhead_cpu_time_ns = update_cpu_time_since_previous_sample(current_thread_context, cpu_time_after_sampling);
|
|
@@ -741,32 +744,38 @@ static void record_sampling_overhead(thread_context_collector_state *state, per_
|
|
|
741
744
|
// Assumption 3: This function IS NOT called from a signal handler. This function is not async-signal-safe.
|
|
742
745
|
// Assumption 4: This function IS NOT called in a reentrant way.
|
|
743
746
|
// Assumption 5: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
747
|
+
// Assumption 6: When called while the profiler is active, `during_sample` MUST be set.
|
|
744
748
|
//
|
|
745
749
|
bool thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns) {
|
|
746
750
|
thread_context_collector_state *state;
|
|
747
751
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
748
752
|
|
|
749
753
|
VALUE current_thread = rb_thread_current();
|
|
750
|
-
per_thread_context *current_thread_context = get_or_create_context_for(current_thread);
|
|
751
|
-
|
|
754
|
+
per_thread_context *current_thread_context = get_or_create_context_for(current_thread, current_monotonic_wall_time_ns);
|
|
755
|
+
|
|
756
|
+
// Sample the current thread (which will use the current CPU-time) first.
|
|
757
|
+
// This ensures any CPU-time the profiler spends sampling (e.g. the rest of the function) is not wrongly blamed on this thread,
|
|
758
|
+
// and instead is separately accounted by `record_sampling_overhead()` below.
|
|
759
|
+
update_metrics_and_sample(
|
|
760
|
+
state,
|
|
761
|
+
current_thread,
|
|
762
|
+
current_thread_context,
|
|
763
|
+
current_monotonic_wall_time_ns,
|
|
764
|
+
false);
|
|
752
765
|
|
|
753
766
|
VALUE threads = thread_list(state);
|
|
754
767
|
|
|
755
768
|
const long thread_count = RARRAY_LEN(threads);
|
|
756
769
|
for (long i = 0; i < thread_count; i++) {
|
|
757
|
-
VALUE thread =
|
|
758
|
-
|
|
770
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
771
|
+
ENFORCE_THREAD(thread);
|
|
759
772
|
|
|
760
|
-
|
|
761
|
-
// to avoid blaming the time the profiler took on whatever is currently running on the thread,
|
|
762
|
-
// and instead we report that time the profiler took as sampling overhead below.
|
|
763
|
-
long current_cpu_time_ns = (thread == current_thread) ? cpu_time_at_sample_start_for_current_thread : cpu_time_now_ns(thread_context);
|
|
773
|
+
if (thread == current_thread) continue; // Already sampled above
|
|
764
774
|
|
|
765
775
|
update_metrics_and_sample(
|
|
766
776
|
state,
|
|
767
777
|
thread,
|
|
768
|
-
|
|
769
|
-
current_cpu_time_ns,
|
|
778
|
+
get_or_create_context_for(thread, current_monotonic_wall_time_ns),
|
|
770
779
|
current_monotonic_wall_time_ns,
|
|
771
780
|
false);
|
|
772
781
|
}
|
|
@@ -790,35 +799,36 @@ static void update_metrics_and_sample(
|
|
|
790
799
|
thread_context_collector_state *state,
|
|
791
800
|
VALUE thread_being_sampled,
|
|
792
801
|
per_thread_context *thread_context,
|
|
793
|
-
long current_cpu_time_ns,
|
|
794
802
|
long current_monotonic_wall_time_ns,
|
|
795
803
|
bool force_sample
|
|
796
804
|
) {
|
|
805
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
797
806
|
bool is_gvl_waiting_state =
|
|
798
807
|
handle_gvl_waiting(state, thread_being_sampled, thread_context, current_cpu_time_ns);
|
|
799
808
|
|
|
800
809
|
if (skip_sample(state, thread_context, is_gvl_waiting_state, force_sample)) return;
|
|
801
810
|
|
|
802
|
-
// Don't assign/update cpu during "Waiting for GVL"
|
|
803
|
-
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
804
|
-
|
|
805
|
-
long wall_time_elapsed_ns = update_wall_time_since_previous_sample(thread_context, current_monotonic_wall_time_ns);
|
|
806
|
-
|
|
807
811
|
// A thread enters "Waiting for GVL", well, as the name implies, without the GVL.
|
|
808
812
|
//
|
|
809
|
-
// As a consequence, it's possible
|
|
813
|
+
// As a consequence, it's possible for another thread to enter "Waiting for GVL" in parallel with the current thread working
|
|
810
814
|
// on sampling, and thus for the `current_monotonic_wall_time_ns` (which is recorded at the start of sampling)
|
|
811
|
-
// to be
|
|
815
|
+
// to be <= the time we observe for a "Waiting for GVL".
|
|
812
816
|
//
|
|
813
|
-
//
|
|
814
|
-
//
|
|
815
|
-
//
|
|
817
|
+
// When that happens, `handle_gvl_waiting` has already accounted for all wall-time up to the (later) start of the
|
|
818
|
+
// wait via the extra sample it pushes (see comments on that function), so there may be no remaining time
|
|
819
|
+
// to account for here.
|
|
820
|
+
// Thus, in this case, we don't want to produce a sample representing "Waiting for GVL" with a wall-time of 0 (nor negative!), and
|
|
816
821
|
// thus we skip creating such a sample.
|
|
817
|
-
if (is_gvl_waiting_state &&
|
|
822
|
+
if (is_gvl_waiting_state && current_monotonic_wall_time_ns <= thread_context->wall_time_at_previous_sample_ns) return;
|
|
818
823
|
// ...you may also wonder: is there any other situation where it makes sense to produce a sample with
|
|
819
824
|
// wall_time_elapsed_ns == 0? I believe that yes, because the sample still includes a timestamp and a stack, but we
|
|
820
825
|
// may revisit/change our minds on this in the future.
|
|
821
826
|
|
|
827
|
+
// Don't assign/update cpu during "Waiting for GVL"
|
|
828
|
+
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
829
|
+
|
|
830
|
+
long wall_time_elapsed_ns = update_wall_time_since_previous_sample(thread_context, current_monotonic_wall_time_ns);
|
|
831
|
+
|
|
822
832
|
trigger_sample_for_thread(
|
|
823
833
|
state,
|
|
824
834
|
thread_being_sampled,
|
|
@@ -843,7 +853,7 @@ static bool skip_sample(thread_context_collector_state *state, per_thread_contex
|
|
|
843
853
|
// Skip this per-tick sample entirely when the thread does not have the GVL and did not acquire
|
|
844
854
|
// it since the previous sample: its Ruby-level stack has not changed. The skipped wall-time will
|
|
845
855
|
// be picked up by either by an extra sample when the thread acquires the GVL, or by
|
|
846
|
-
//
|
|
856
|
+
// prepare_serialize (using was_skipped_at_last_sample).
|
|
847
857
|
// The check is gated by `!is_gvl_waiting_state` so the existing "Waiting for GVL" machinery
|
|
848
858
|
// in handle_gvl_waiting (situation 1 extra sample, situation 2 regular sample) keeps running.
|
|
849
859
|
// TODO: we could probably also skip while "Waiting for GVL"
|
|
@@ -888,16 +898,14 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
|
888
898
|
}
|
|
889
899
|
|
|
890
900
|
// 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
|
|
891
|
-
|
|
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;
|
|
901
|
+
thread_context->gc_tracking.wall_time_at_start_ns = monotonic_wall_time_now_ns();
|
|
897
902
|
thread_context->gc_tracking.cpu_time_at_start_ns = cpu_time_now_ns(thread_context);
|
|
898
903
|
}
|
|
899
904
|
|
|
900
905
|
// This function gets called when Ruby has finished running the Garbage Collector on the current thread.
|
|
906
|
+
// (Note: The `GC_EXIT` event gets emitted by the VM while `during_gc` is still set to true -- the flag
|
|
907
|
+
// is only cleared after the event gets emitted -- see top of the file for more details)
|
|
908
|
+
//
|
|
901
909
|
// It records the cpu/wall-time observed during GC, which will be used to later
|
|
902
910
|
// create an event including the cpu/wall time spent from the start of garbage collector work until now.
|
|
903
911
|
//
|
|
@@ -936,16 +944,13 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
936
944
|
|
|
937
945
|
// Here we record the wall-time second and in on_gc_start we record it first to try to avoid having wall-time be slightly < cpu-time
|
|
938
946
|
long cpu_time_at_finish_ns = cpu_time_now_ns(thread_context);
|
|
939
|
-
long wall_time_at_finish_ns = monotonic_wall_time_now_ns(
|
|
940
|
-
|
|
941
|
-
// If our end timestamp is not OK, we bail out
|
|
942
|
-
if (wall_time_at_finish_ns == 0) return false;
|
|
947
|
+
long wall_time_at_finish_ns = monotonic_wall_time_now_ns();
|
|
943
948
|
|
|
944
949
|
long gc_cpu_time_elapsed_ns = cpu_time_at_finish_ns - cpu_time_at_start_ns;
|
|
945
950
|
long gc_wall_time_elapsed_ns = wall_time_at_finish_ns - wall_time_at_start_ns;
|
|
946
951
|
|
|
947
|
-
//
|
|
948
|
-
//
|
|
952
|
+
// @ivoanjo: We don't expect these clocks to ever go backwards (and in other code paths we enforce that with an exception)
|
|
953
|
+
// but during GC it's really really awkward to signal back failures so for now I left the clamping.
|
|
949
954
|
gc_cpu_time_elapsed_ns = long_max_of(gc_cpu_time_elapsed_ns, 0);
|
|
950
955
|
gc_wall_time_elapsed_ns = long_max_of(gc_wall_time_elapsed_ns, 0);
|
|
951
956
|
|
|
@@ -968,10 +973,10 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
968
973
|
// Let the caller know if it should schedule a flush or not. Returning true every time would cause a lot of overhead
|
|
969
974
|
// on the application (see GC tracking introduction at the top of the file), so instead we try to accumulate a few
|
|
970
975
|
// samples first.
|
|
971
|
-
bool
|
|
976
|
+
bool over_flush_time_threshold =
|
|
972
977
|
(wall_time_at_finish_ns - state->gc_tracking.wall_time_at_last_flushed_gc_event_ns) >= TIME_BETWEEN_GC_EVENTS_NS;
|
|
973
978
|
|
|
974
|
-
if (
|
|
979
|
+
if (over_flush_time_threshold) {
|
|
975
980
|
return true;
|
|
976
981
|
} else {
|
|
977
982
|
return gc_profiling_has_major_gc_finished();
|
|
@@ -986,6 +991,7 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
986
991
|
// Assumption 2: This function is allowed to raise exceptions. Caller is responsible for handling them, if needed.
|
|
987
992
|
// Assumption 3: Unlike `on_gc_start` and `on_gc_finish`, this method is allowed to allocate memory as needed.
|
|
988
993
|
// Assumption 4: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
994
|
+
// Assumption 5: When called while the profiler is active, `during_sample` MUST be set.
|
|
989
995
|
VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
990
996
|
thread_context_collector_state *state;
|
|
991
997
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
@@ -1209,14 +1215,14 @@ static void check_frozen_thread(VALUE thread) {
|
|
|
1209
1215
|
|
|
1210
1216
|
// See the docs on struct per_thread_context.
|
|
1211
1217
|
// This allocates a Ruby object and therefore needs the GVL and is not safe to call from RUBY_INTERNAL_EVENT_* hooks.
|
|
1212
|
-
static per_thread_context *get_or_create_context_for(VALUE thread) {
|
|
1218
|
+
static per_thread_context *get_or_create_context_for(VALUE thread, long current_monotonic_wall_time_ns) {
|
|
1213
1219
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1214
1220
|
if (thread_context != NULL) return thread_context;
|
|
1215
1221
|
|
|
1216
1222
|
check_frozen_thread(thread);
|
|
1217
1223
|
|
|
1218
1224
|
thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
|
|
1219
|
-
initialize_context(thread, thread_context);
|
|
1225
|
+
initialize_context(thread, thread_context, current_monotonic_wall_time_ns);
|
|
1220
1226
|
|
|
1221
1227
|
VALUE wrapper = TypedData_Wrap_Struct(rb_cObject, &per_thread_context_typed_data, thread_context);
|
|
1222
1228
|
rb_ivar_set(thread, dd_per_thread_context_id, wrapper);
|
|
@@ -1230,7 +1236,8 @@ static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *un
|
|
|
1230
1236
|
|
|
1231
1237
|
VALUE thread = rb_tracearg_self(rb_tracearg_from_tracepoint(tracepoint_data));
|
|
1232
1238
|
ENFORCE_THREAD(thread);
|
|
1233
|
-
|
|
1239
|
+
|
|
1240
|
+
get_or_create_context_for(thread, monotonic_wall_time_now_ns());
|
|
1234
1241
|
}
|
|
1235
1242
|
|
|
1236
1243
|
#define LOGGING_GEM_PATH "/lib/logging/diagnostic_context.rb"
|
|
@@ -1254,7 +1261,7 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
|
|
|
1254
1261
|
return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
|
|
1255
1262
|
}
|
|
1256
1263
|
|
|
1257
|
-
static void initialize_context(VALUE thread, per_thread_context *thread_context) {
|
|
1264
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context, long current_monotonic_wall_time_ns) {
|
|
1258
1265
|
// We always create per_thread_context's with latest_max_frames; that value is kept in sync with the
|
|
1259
1266
|
// active profiler's max_frames by the global reset that runs when profiling starts
|
|
1260
1267
|
// so we expect to always see here the latest correct value to be used.
|
|
@@ -1291,7 +1298,7 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context)
|
|
|
1291
1298
|
|
|
1292
1299
|
thread_context->thread_cpu_time_id = thread_cpu_time_id_for(thread);
|
|
1293
1300
|
|
|
1294
|
-
thread_context->wall_time_at_previous_sample_ns =
|
|
1301
|
+
thread_context->wall_time_at_previous_sample_ns = current_monotonic_wall_time_ns;
|
|
1295
1302
|
thread_context->cpu_time_at_previous_sample_ns = cpu_time_now_ns(thread_context);
|
|
1296
1303
|
|
|
1297
1304
|
// These will only be used during a GC operation
|
|
@@ -1321,22 +1328,26 @@ void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance)
|
|
|
1321
1328
|
|
|
1322
1329
|
latest_max_frames = state->locations.len;
|
|
1323
1330
|
|
|
1331
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns();
|
|
1332
|
+
|
|
1324
1333
|
VALUE threads = thread_list(state);
|
|
1325
1334
|
const long thread_count = RARRAY_LEN(threads);
|
|
1326
1335
|
for (long i = 0; i < thread_count; i++) {
|
|
1327
1336
|
VALUE thread = rb_ary_entry(threads, i);
|
|
1337
|
+
ENFORCE_THREAD(thread);
|
|
1338
|
+
|
|
1328
1339
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1329
1340
|
if (thread_context != NULL) {
|
|
1330
1341
|
bool is_profiler_internal_thread = thread_context->is_profiler_internal_thread;
|
|
1331
1342
|
|
|
1332
1343
|
sampling_buffer_free(&thread_context->sampling_buffer);
|
|
1333
1344
|
memset(thread_context, 0, sizeof(per_thread_context));
|
|
1334
|
-
initialize_context(thread, thread_context);
|
|
1345
|
+
initialize_context(thread, thread_context, current_monotonic_wall_time_ns);
|
|
1335
1346
|
|
|
1336
1347
|
thread_context->is_profiler_internal_thread = is_profiler_internal_thread;
|
|
1337
1348
|
} else {
|
|
1338
1349
|
// If thread didn't have a context, let's trigger its creation
|
|
1339
|
-
get_or_create_context_for(thread);
|
|
1350
|
+
get_or_create_context_for(thread, current_monotonic_wall_time_ns);
|
|
1340
1351
|
}
|
|
1341
1352
|
}
|
|
1342
1353
|
}
|
|
@@ -1439,7 +1450,9 @@ static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collec
|
|
|
1439
1450
|
VALUE threads = thread_list(state);
|
|
1440
1451
|
const long thread_count = RARRAY_LEN(threads);
|
|
1441
1452
|
for (long i = 0; i < thread_count; i++) {
|
|
1442
|
-
VALUE thread =
|
|
1453
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
1454
|
+
ENFORCE_THREAD(thread);
|
|
1455
|
+
|
|
1443
1456
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1444
1457
|
if (thread_context != NULL) {
|
|
1445
1458
|
rb_hash_aset(result, thread, per_thread_context_to_ruby_hash(thread_context));
|
|
@@ -1459,7 +1472,7 @@ static long update_time_since_previous_sample(long *time_at_previous_sample_ns,
|
|
|
1459
1472
|
thread_context->thread_id,
|
|
1460
1473
|
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
1461
1474
|
thread_context->gc_tracking.wall_time_at_start_ns,
|
|
1462
|
-
monotonic_wall_time_now_ns(
|
|
1475
|
+
monotonic_wall_time_now_ns()
|
|
1463
1476
|
);
|
|
1464
1477
|
}
|
|
1465
1478
|
|
|
@@ -1483,10 +1496,7 @@ static long update_cpu_time_since_previous_sample(per_thread_context *thread_con
|
|
|
1483
1496
|
thread_context
|
|
1484
1497
|
);
|
|
1485
1498
|
|
|
1486
|
-
|
|
1487
|
-
if (elapsed_time_ns < 0) {
|
|
1488
|
-
raise_error(rb_eRuntimeError, "BUG: Unexpected CPU time going backwards between samples");
|
|
1489
|
-
}
|
|
1499
|
+
if (elapsed_time_ns < 0) raise_error(rb_eRuntimeError, "BUG: Unexpected CPU time going backwards between samples");
|
|
1490
1500
|
|
|
1491
1501
|
return elapsed_time_ns;
|
|
1492
1502
|
}
|
|
@@ -1498,11 +1508,9 @@ static long update_wall_time_since_previous_sample(per_thread_context *thread_co
|
|
|
1498
1508
|
thread_context
|
|
1499
1509
|
);
|
|
1500
1510
|
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
// https://github.com/DataDog/dd-trace-rb/pull/2336.
|
|
1505
|
-
return long_max_of(elapsed_time_ns, 0);
|
|
1511
|
+
if (elapsed_time_ns < 0) raise_error(rb_eRuntimeError, "BUG: Unexpected wall time going backwards between samples");
|
|
1512
|
+
|
|
1513
|
+
return elapsed_time_ns;
|
|
1506
1514
|
}
|
|
1507
1515
|
|
|
1508
1516
|
// Safety: This function is assumed never to raise exceptions by callers
|
|
@@ -1675,14 +1683,7 @@ static VALUE thread_list(thread_context_collector_state *state) {
|
|
|
1675
1683
|
return result;
|
|
1676
1684
|
}
|
|
1677
1685
|
|
|
1678
|
-
|
|
1679
|
-
// the current thread.
|
|
1680
|
-
//
|
|
1681
|
-
// Assumptions for this function are same as for `thread_context_collector_sample` except that this function is
|
|
1682
|
-
// expected to be called from a signal handler and to be async-signal-safe.
|
|
1683
|
-
//
|
|
1684
|
-
// Also, no allocation (Ruby or malloc) can happen.
|
|
1685
|
-
bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
1686
|
+
static inline bool thread_context_collector_prepare_sample_internal(void) {
|
|
1686
1687
|
VALUE current_thread = rb_thread_current();
|
|
1687
1688
|
per_thread_context *thread_context = get_per_thread_context(current_thread);
|
|
1688
1689
|
if (thread_context == NULL) return false;
|
|
@@ -1690,13 +1691,26 @@ bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
|
1690
1691
|
return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
|
|
1691
1692
|
}
|
|
1692
1693
|
|
|
1694
|
+
// Called from a signal handler on the main Ractor with the GVL held, outside GC and with `during_sample` unset.
|
|
1695
|
+
// Must remain async-signal-safe: no allocation (Ruby or malloc), exceptions, or releasing the GVL.
|
|
1696
|
+
bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
1697
|
+
return thread_context_collector_prepare_sample_internal();
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// Called at RUBY_INTERNAL_EVENT_GC_EXIT on the main Ractor with the GVL held; `during_sample` MUST be set to prevent reentrancy.
|
|
1701
|
+
// Ruby is still doing GC: no allocation (Ruby or malloc), exceptions, or releasing the GVL.
|
|
1702
|
+
void thread_context_collector_prepare_sample_on_gc_finish(void) {
|
|
1703
|
+
thread_context_collector_prepare_sample_internal();
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1693
1706
|
// This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
|
|
1694
1707
|
// Ruby heap nor release the GVL (https://github.com/DataDog/dd-trace-rb/pull/4240).
|
|
1695
1708
|
//
|
|
1696
1709
|
// Returns true if `thread_context_collector_commit_heap_recordings_may_lose_gvl` needs to be called (to do work
|
|
1697
1710
|
// that can't be done from inside the tracepoint, such as allocate new objects), and false if it doesn't
|
|
1698
1711
|
//
|
|
1699
|
-
// The callers must ensure thread_context is non-NULL.
|
|
1712
|
+
// Assumption 1: The callers must ensure thread_context is non-NULL.
|
|
1713
|
+
// Assumption 2: When called while the profiler is active, `during_sample` MUST be set.
|
|
1700
1714
|
bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object) {
|
|
1701
1715
|
thread_context_collector_state *state;
|
|
1702
1716
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
@@ -1942,6 +1956,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
1942
1956
|
*numeric_span_id = resolved_numeric_span_id;
|
|
1943
1957
|
}
|
|
1944
1958
|
|
|
1959
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
1945
1960
|
void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples) {
|
|
1946
1961
|
thread_context_collector_state *state;
|
|
1947
1962
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
@@ -2141,70 +2156,79 @@ static void mark_thread_as_profiler_internal(per_thread_context *ctx) {
|
|
|
2141
2156
|
}
|
|
2142
2157
|
|
|
2143
2158
|
void thread_context_collector_profiler_internal_thread_started(void) {
|
|
2144
|
-
per_thread_context *ctx = get_or_create_context_for(rb_thread_current());
|
|
2159
|
+
per_thread_context *ctx = get_or_create_context_for(rb_thread_current(), monotonic_wall_time_now_ns());
|
|
2145
2160
|
mark_thread_as_profiler_internal(ctx);
|
|
2146
2161
|
}
|
|
2147
2162
|
|
|
2148
2163
|
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2149
|
-
per_thread_context *ctx = get_or_create_context_for(thread);
|
|
2164
|
+
per_thread_context *ctx = get_or_create_context_for(thread, monotonic_wall_time_now_ns());
|
|
2150
2165
|
mark_thread_as_profiler_internal(ctx);
|
|
2151
2166
|
return Qnil;
|
|
2152
2167
|
}
|
|
2153
2168
|
|
|
2154
2169
|
// Called via rb_ensure when a profiler-internal thread (worker or idle helper) is about to exit.
|
|
2155
|
-
// Records a final sample so the thread's accumulated cpu/wall time since the last
|
|
2156
|
-
//
|
|
2170
|
+
// Records a final sample so the thread's accumulated cpu/wall time since the last prepare_serialize
|
|
2171
|
+
// is not lost. prepare_serialize (below) also flushes profiler-internal threads during periodic
|
|
2157
2172
|
// serialization, but it can't help at shutdown: by the time the final serialize runs, these
|
|
2158
2173
|
// threads are already dead and absent from thread_list.
|
|
2159
|
-
|
|
2174
|
+
//
|
|
2175
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2176
|
+
VALUE thread_context_collector_profiler_internal_thread_done(VALUE self_instance) {
|
|
2160
2177
|
thread_context_collector_state *state;
|
|
2161
2178
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2162
2179
|
|
|
2163
2180
|
VALUE current_thread = rb_thread_current();
|
|
2164
|
-
|
|
2181
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns();
|
|
2182
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread, current_monotonic_wall_time_ns);
|
|
2165
2183
|
if (!thread_context->is_profiler_internal_thread) {
|
|
2166
2184
|
rb_raise(rb_eRuntimeError, "current thread %"PRIsVALUE" is not profiler-internal thread", current_thread);
|
|
2167
2185
|
}
|
|
2168
2186
|
|
|
2169
|
-
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2170
|
-
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2171
|
-
|
|
2172
2187
|
update_metrics_and_sample(
|
|
2173
2188
|
state,
|
|
2174
2189
|
current_thread,
|
|
2175
2190
|
thread_context,
|
|
2176
|
-
current_cpu_time_ns,
|
|
2177
2191
|
current_monotonic_wall_time_ns,
|
|
2178
2192
|
true);
|
|
2193
|
+
|
|
2194
|
+
return Qnil;
|
|
2179
2195
|
}
|
|
2180
2196
|
|
|
2181
2197
|
// Flushes threads whose last per-tick sample was skipped (either by the SUSPENDED-skip
|
|
2182
2198
|
// optimization, or by is_profiler_internal_thread) so their accumulated time is recorded.
|
|
2183
|
-
// Called
|
|
2184
|
-
|
|
2199
|
+
// Called before serialization so that threads suspended across the whole profile period still get sampled.
|
|
2200
|
+
//
|
|
2201
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2202
|
+
VALUE thread_context_prepare_serialize(VALUE self_instance) {
|
|
2185
2203
|
thread_context_collector_state *state;
|
|
2186
2204
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2187
2205
|
|
|
2188
|
-
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(
|
|
2206
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns();
|
|
2189
2207
|
VALUE threads = thread_list(state);
|
|
2190
2208
|
const long thread_count = RARRAY_LEN(threads);
|
|
2191
2209
|
|
|
2192
2210
|
for (long i = 0; i < thread_count; i++) {
|
|
2193
|
-
VALUE thread =
|
|
2211
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
2212
|
+
ENFORCE_THREAD(thread);
|
|
2213
|
+
|
|
2194
2214
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2195
2215
|
|
|
2196
2216
|
if (thread_context != NULL && (thread_context->was_skipped_at_last_sample || thread_context->is_profiler_internal_thread)) {
|
|
2197
|
-
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2198
|
-
// We need to force_sample=true otherwise this sample would be skipped too
|
|
2199
2217
|
update_metrics_and_sample(
|
|
2200
2218
|
state,
|
|
2201
2219
|
thread,
|
|
2202
2220
|
thread_context,
|
|
2203
|
-
current_cpu_time_ns,
|
|
2204
2221
|
current_monotonic_wall_time_ns,
|
|
2205
|
-
true
|
|
2222
|
+
true // We need to force_sample=true otherwise this sample would be skipped too
|
|
2223
|
+
);
|
|
2206
2224
|
}
|
|
2207
2225
|
}
|
|
2226
|
+
|
|
2227
|
+
return state->recorder_instance;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
static VALUE _native_prepare_serialize(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
2231
|
+
return thread_context_prepare_serialize(collector_instance);
|
|
2208
2232
|
}
|
|
2209
2233
|
|
|
2210
2234
|
void thread_context_collector_on_gvl_released(per_thread_context *thread_context) {
|
|
@@ -2212,10 +2236,7 @@ void thread_context_collector_on_gvl_released(per_thread_context *thread_context
|
|
|
2212
2236
|
}
|
|
2213
2237
|
|
|
2214
2238
|
void thread_context_collector_on_gvl_waiting(per_thread_context *thread_context) {
|
|
2215
|
-
|
|
2216
|
-
if (current_monotonic_wall_time_ns <= 0) return;
|
|
2217
|
-
|
|
2218
|
-
thread_context->gvl_waiting_at = current_monotonic_wall_time_ns;
|
|
2239
|
+
thread_context->gvl_waiting_at = monotonic_wall_time_now_ns();
|
|
2219
2240
|
}
|
|
2220
2241
|
|
|
2221
2242
|
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
@@ -2246,6 +2267,8 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2246
2267
|
|
|
2247
2268
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
2248
2269
|
// We must only use async-signal-safe functions here, see notes in the caller
|
|
2270
|
+
//
|
|
2271
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2249
2272
|
__attribute__((warn_unused_result))
|
|
2250
2273
|
on_gvl_running_result thread_context_collector_on_gvl_running(VALUE thread, per_thread_context *thread_context, uint32_t waiting_for_gvl_threshold_ns) {
|
|
2251
2274
|
// Bump the event counter and clears the state bit to "running"
|
|
@@ -2262,7 +2285,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2262
2285
|
return (on_gvl_running_result) {.action = ON_GVL_RUNNING_SAMPLE, .waiting_for_gvl_duration_ns = 0};
|
|
2263
2286
|
}
|
|
2264
2287
|
|
|
2265
|
-
long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns(
|
|
2288
|
+
long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns() - gvl_waiting_at;
|
|
2266
2289
|
|
|
2267
2290
|
bool should_sample = waiting_for_gvl_duration_ns >= waiting_for_gvl_threshold_ns;
|
|
2268
2291
|
|
|
@@ -2322,7 +2345,8 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2322
2345
|
//
|
|
2323
2346
|
// ---
|
|
2324
2347
|
//
|
|
2325
|
-
// Always called with the GVL, either from a postponed_job or from tests.
|
|
2348
|
+
// Assumption 1: Always called with the GVL, either from a postponed_job or from tests.
|
|
2349
|
+
// Assumption 2: When called while the profiler is active, `during_sample` MUST be set.
|
|
2326
2350
|
//
|
|
2327
2351
|
// NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
|
|
2328
2352
|
// exists to enable testing.
|
|
@@ -2330,7 +2354,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2330
2354
|
thread_context_collector_state *state;
|
|
2331
2355
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2332
2356
|
|
|
2333
|
-
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
2357
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread, current_monotonic_wall_time_ns);
|
|
2334
2358
|
|
|
2335
2359
|
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
2336
2360
|
|
|
@@ -2342,18 +2366,12 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2342
2366
|
return Qfalse;
|
|
2343
2367
|
}
|
|
2344
2368
|
|
|
2345
|
-
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may choose to push an
|
|
2346
|
-
// extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
|
|
2347
|
-
// cpu-time of the thread and let `update_metrics_and_sample` decide what to do with it.
|
|
2348
|
-
long cpu_time_for_thread = cpu_time_now_ns(thread_context);
|
|
2349
|
-
|
|
2350
2369
|
// TODO: Should we update the dynamic sampling rate overhead tracking with this sample as well?
|
|
2351
2370
|
|
|
2352
2371
|
update_metrics_and_sample(
|
|
2353
2372
|
state,
|
|
2354
2373
|
current_thread,
|
|
2355
2374
|
thread_context,
|
|
2356
|
-
cpu_time_for_thread,
|
|
2357
2375
|
current_monotonic_wall_time_ns,
|
|
2358
2376
|
false);
|
|
2359
2377
|
|
|
@@ -2488,7 +2506,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2488
2506
|
VALUE result = thread_context_collector_sample_after_gvl_running(
|
|
2489
2507
|
collector_instance,
|
|
2490
2508
|
thread,
|
|
2491
|
-
monotonic_wall_time_now_ns(
|
|
2509
|
+
monotonic_wall_time_now_ns()
|
|
2492
2510
|
);
|
|
2493
2511
|
|
|
2494
2512
|
debug_leave_unsafe_context();
|
|
@@ -2506,13 +2524,33 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2506
2524
|
|
|
2507
2525
|
#endif // NO_GVL_INSTRUMENTATION
|
|
2508
2526
|
|
|
2509
|
-
static VALUE
|
|
2527
|
+
static VALUE _native_apply_delta_to_time_at_previous_sample_ns(int argc, VALUE *argv, DDTRACE_UNUSED VALUE self) {
|
|
2528
|
+
VALUE thread;
|
|
2529
|
+
VALUE options;
|
|
2530
|
+
rb_scan_args(argc, argv, "1:", &thread, &options);
|
|
2531
|
+
if (options == Qnil) options = rb_hash_new();
|
|
2532
|
+
|
|
2533
|
+
VALUE cpu_time_delta_ns = rb_hash_lookup2(options, ID2SYM(rb_intern("cpu_time")), INT2FIX(0));
|
|
2534
|
+
VALUE wall_time_delta_ns = rb_hash_lookup2(options, ID2SYM(rb_intern("wall_time")), INT2FIX(0));
|
|
2535
|
+
|
|
2536
|
+
ENFORCE_THREAD(thread);
|
|
2537
|
+
|
|
2538
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2539
|
+
if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2540
|
+
|
|
2541
|
+
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(cpu_time_delta_ns);
|
|
2542
|
+
thread_context->wall_time_at_previous_sample_ns += NUM2LONG(wall_time_delta_ns);
|
|
2543
|
+
|
|
2544
|
+
return Qtrue;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
static VALUE _native_apply_delta_to_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns) {
|
|
2510
2548
|
ENFORCE_THREAD(thread);
|
|
2511
2549
|
|
|
2512
2550
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2513
2551
|
if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2514
2552
|
|
|
2515
|
-
thread_context->
|
|
2553
|
+
thread_context->gvl_waiting_at += NUM2LONG(delta_ns);
|
|
2516
2554
|
|
|
2517
2555
|
return Qtrue;
|
|
2518
2556
|
}
|
|
@@ -2555,8 +2593,7 @@ static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE c
|
|
|
2555
2593
|
thread_context_collector_state *state;
|
|
2556
2594
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2557
2595
|
|
|
2558
|
-
long
|
|
2559
|
-
long system_epoch_time_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, current_monotonic_wall_time_ns);
|
|
2596
|
+
long system_epoch_time_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, monotonic_wall_time_now_ns());
|
|
2560
2597
|
|
|
2561
2598
|
return LONG2NUM(system_epoch_time_ns);
|
|
2562
2599
|
}
|