datadog 2.44.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 +26 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +114 -141
- 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 +13 -13
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +5 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +1 -0
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +106 -103
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -3
- data/ext/datadog_profiling_native_extension/extconf.rb +2 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.c +2 -2
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +98 -54
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +5 -2
- data/ext/datadog_profiling_native_extension/profiling.c +21 -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 +6 -24
- 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 +10 -11
- data/ext/libdatadog_api/trace_exporter.c +77 -11
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +5 -0
- data/lib/datadog/profiling/component.rb +2 -3
- data/lib/datadog/profiling/exporter.rb +7 -5
- data/lib/datadog/tracing/component.rb +1 -1
- data/lib/datadog/tracing/configuration/settings.rb +3 -2
- 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/transport/native.rb +22 -21
- data/lib/datadog/version.rb +1 -1
- metadata +5 -5
|
@@ -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.
|
|
@@ -207,7 +208,6 @@ typedef struct {
|
|
|
207
208
|
// however this only matters for a very short period when a thread starts.
|
|
208
209
|
struct per_thread_context {
|
|
209
210
|
sampling_buffer sampling_buffer;
|
|
210
|
-
bool prepare_sample_on_gc_finish;
|
|
211
211
|
char thread_id[THREAD_ID_LIMIT_CHARS];
|
|
212
212
|
ddog_CharSlice thread_id_char_slice;
|
|
213
213
|
char thread_invoke_location[THREAD_INVOKE_LOCATION_LIMIT_CHARS];
|
|
@@ -269,7 +269,7 @@ struct per_thread_context {
|
|
|
269
269
|
bool was_skipped_at_last_sample;
|
|
270
270
|
// Set as true for CpuAndWallTimeWorker and IdleSamplingHelper threads.
|
|
271
271
|
// When true, per-tick samples are skipped entirely; the thread is sampled only once per
|
|
272
|
-
// reporting period during
|
|
272
|
+
// reporting period during prepare_serialize.
|
|
273
273
|
//
|
|
274
274
|
// These threads are always in native code so their stacks aren't interesting;
|
|
275
275
|
// the Profiling::Scheduler thread on the other hand does a lot of different
|
|
@@ -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,
|
|
@@ -385,8 +386,8 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
|
|
|
385
386
|
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
|
|
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
|
-
static VALUE _native_request_prepare_on_gc_finish(DDTRACE_UNUSED VALUE self);
|
|
389
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);
|
|
390
391
|
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
391
392
|
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
392
393
|
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample);
|
|
@@ -425,10 +426,10 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
425
426
|
rb_define_singleton_method(testing_module, "_native_sample_skipped_allocation_samples", _native_sample_skipped_allocation_samples, 2);
|
|
426
427
|
rb_define_singleton_method(testing_module, "_native_system_epoch_time_now_ns", _native_system_epoch_time_now_ns, 1);
|
|
427
428
|
rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler, 0);
|
|
428
|
-
rb_define_singleton_method(testing_module, "_native_request_prepare_on_gc_finish", _native_request_prepare_on_gc_finish, 0);
|
|
429
429
|
rb_define_singleton_method(testing_module, "_native_remove_per_thread_context_for", _native_remove_per_thread_context_for, 1);
|
|
430
430
|
rb_define_singleton_method(testing_module, "_native_global_reset_per_thread_context", _native_global_reset_per_thread_context, 1);
|
|
431
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);
|
|
432
433
|
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
433
434
|
rb_define_singleton_method(testing_module, "_native_on_gvl_released", _native_on_gvl_released, 1);
|
|
434
435
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
@@ -436,7 +437,8 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
436
437
|
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 2);
|
|
437
438
|
rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 2);
|
|
438
439
|
#endif
|
|
439
|
-
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);
|
|
440
442
|
|
|
441
443
|
at_active_span_id = rb_intern_const("@active_span");
|
|
442
444
|
at_active_trace_id = rb_intern_const("@active_trace");
|
|
@@ -615,7 +617,6 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
615
617
|
// Update this when modifying state struct
|
|
616
618
|
sample_locations_initialize(&state->locations, max_frame_int, show_classes == Qtrue);
|
|
617
619
|
state->recorder_instance = enforce_recorder_instance(recorder_instance);
|
|
618
|
-
recorder_install_on_serialize(recorder_instance, self_instance);
|
|
619
620
|
state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
|
|
620
621
|
state->native_filenames_enabled = (native_filenames_enabled == Qtrue);
|
|
621
622
|
state->show_classes = (show_classes == Qtrue);
|
|
@@ -654,7 +655,7 @@ static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance
|
|
|
654
655
|
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
655
656
|
|
|
656
657
|
bool needs_otel_span_key =
|
|
657
|
-
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(
|
|
658
|
+
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns());
|
|
658
659
|
|
|
659
660
|
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
|
660
661
|
|
|
@@ -681,7 +682,7 @@ static VALUE _native_on_gc_start(DDTRACE_UNUSED VALUE self, VALUE collector_inst
|
|
|
681
682
|
static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
682
683
|
debug_enter_unsafe_context();
|
|
683
684
|
|
|
684
|
-
(void) !thread_context_collector_on_gc_finish(collector_instance
|
|
685
|
+
(void) !thread_context_collector_on_gc_finish(collector_instance);
|
|
685
686
|
|
|
686
687
|
debug_leave_unsafe_context();
|
|
687
688
|
|
|
@@ -700,7 +701,7 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_
|
|
|
700
701
|
|
|
701
702
|
// Record profiler sampling overhead as a placeholder stack
|
|
702
703
|
static void record_sampling_overhead(thread_context_collector_state *state, per_thread_context *current_thread_context) {
|
|
703
|
-
long wall_time_after_sampling = monotonic_wall_time_now_ns(
|
|
704
|
+
long wall_time_after_sampling = monotonic_wall_time_now_ns();
|
|
704
705
|
long cpu_time_after_sampling = cpu_time_now_ns(current_thread_context);
|
|
705
706
|
|
|
706
707
|
long overhead_cpu_time_ns = update_cpu_time_since_previous_sample(current_thread_context, cpu_time_after_sampling);
|
|
@@ -750,7 +751,7 @@ bool thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
|
750
751
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
751
752
|
|
|
752
753
|
VALUE current_thread = rb_thread_current();
|
|
753
|
-
per_thread_context *current_thread_context = get_or_create_context_for(current_thread);
|
|
754
|
+
per_thread_context *current_thread_context = get_or_create_context_for(current_thread, current_monotonic_wall_time_ns);
|
|
754
755
|
|
|
755
756
|
// Sample the current thread (which will use the current CPU-time) first.
|
|
756
757
|
// This ensures any CPU-time the profiler spends sampling (e.g. the rest of the function) is not wrongly blamed on this thread,
|
|
@@ -774,7 +775,7 @@ bool thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
|
774
775
|
update_metrics_and_sample(
|
|
775
776
|
state,
|
|
776
777
|
thread,
|
|
777
|
-
get_or_create_context_for(thread),
|
|
778
|
+
get_or_create_context_for(thread, current_monotonic_wall_time_ns),
|
|
778
779
|
current_monotonic_wall_time_ns,
|
|
779
780
|
false);
|
|
780
781
|
}
|
|
@@ -807,26 +808,27 @@ static void update_metrics_and_sample(
|
|
|
807
808
|
|
|
808
809
|
if (skip_sample(state, thread_context, is_gvl_waiting_state, force_sample)) return;
|
|
809
810
|
|
|
810
|
-
// Don't assign/update cpu during "Waiting for GVL"
|
|
811
|
-
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
812
|
-
|
|
813
|
-
long wall_time_elapsed_ns = update_wall_time_since_previous_sample(thread_context, current_monotonic_wall_time_ns);
|
|
814
|
-
|
|
815
811
|
// A thread enters "Waiting for GVL", well, as the name implies, without the GVL.
|
|
816
812
|
//
|
|
817
|
-
// 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
|
|
818
814
|
// on sampling, and thus for the `current_monotonic_wall_time_ns` (which is recorded at the start of sampling)
|
|
819
|
-
// to be
|
|
815
|
+
// to be <= the time we observe for a "Waiting for GVL".
|
|
820
816
|
//
|
|
821
|
-
//
|
|
822
|
-
//
|
|
823
|
-
//
|
|
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
|
|
824
821
|
// thus we skip creating such a sample.
|
|
825
|
-
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;
|
|
826
823
|
// ...you may also wonder: is there any other situation where it makes sense to produce a sample with
|
|
827
824
|
// wall_time_elapsed_ns == 0? I believe that yes, because the sample still includes a timestamp and a stack, but we
|
|
828
825
|
// may revisit/change our minds on this in the future.
|
|
829
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
|
+
|
|
830
832
|
trigger_sample_for_thread(
|
|
831
833
|
state,
|
|
832
834
|
thread_being_sampled,
|
|
@@ -851,7 +853,7 @@ static bool skip_sample(thread_context_collector_state *state, per_thread_contex
|
|
|
851
853
|
// Skip this per-tick sample entirely when the thread does not have the GVL and did not acquire
|
|
852
854
|
// it since the previous sample: its Ruby-level stack has not changed. The skipped wall-time will
|
|
853
855
|
// be picked up by either by an extra sample when the thread acquires the GVL, or by
|
|
854
|
-
//
|
|
856
|
+
// prepare_serialize (using was_skipped_at_last_sample).
|
|
855
857
|
// The check is gated by `!is_gvl_waiting_state` so the existing "Waiting for GVL" machinery
|
|
856
858
|
// in handle_gvl_waiting (situation 1 extra sample, situation 2 regular sample) keeps running.
|
|
857
859
|
// TODO: we could probably also skip while "Waiting for GVL"
|
|
@@ -896,16 +898,14 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
|
896
898
|
}
|
|
897
899
|
|
|
898
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
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
// If our start timestamp is not OK, we skip tracking this GC as well
|
|
902
|
-
if (wall_time_at_start_ns == 0) return;
|
|
903
|
-
|
|
904
|
-
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();
|
|
905
902
|
thread_context->gc_tracking.cpu_time_at_start_ns = cpu_time_now_ns(thread_context);
|
|
906
903
|
}
|
|
907
904
|
|
|
908
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
|
+
//
|
|
909
909
|
// It records the cpu/wall-time observed during GC, which will be used to later
|
|
910
910
|
// create an event including the cpu/wall time spent from the start of garbage collector work until now.
|
|
911
911
|
//
|
|
@@ -915,9 +915,8 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
|
915
915
|
//
|
|
916
916
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
917
917
|
// Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
918
|
-
// Assumption 3: If allow_prepare_sample is true while profiling is active, during_sample MUST be set by the caller.
|
|
919
918
|
__attribute__((warn_unused_result))
|
|
920
|
-
bool thread_context_collector_on_gc_finish(VALUE self_instance
|
|
919
|
+
bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
921
920
|
thread_context_collector_state *state;
|
|
922
921
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
|
923
922
|
// This should never fail when the above check passes
|
|
@@ -928,11 +927,6 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance, bool allow_prepa
|
|
|
928
927
|
// Context is created eagerly, so this should not normally be NULL (see on_gc_start).
|
|
929
928
|
if (thread_context == NULL) return false;
|
|
930
929
|
|
|
931
|
-
if (thread_context->prepare_sample_on_gc_finish) {
|
|
932
|
-
thread_context->prepare_sample_on_gc_finish = false;
|
|
933
|
-
if (allow_prepare_sample) prepare_sample_thread(rb_thread_current(), &thread_context->sampling_buffer);
|
|
934
|
-
}
|
|
935
|
-
|
|
936
930
|
long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
|
|
937
931
|
long wall_time_at_start_ns = thread_context->gc_tracking.wall_time_at_start_ns;
|
|
938
932
|
|
|
@@ -950,16 +944,13 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance, bool allow_prepa
|
|
|
950
944
|
|
|
951
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
|
|
952
946
|
long cpu_time_at_finish_ns = cpu_time_now_ns(thread_context);
|
|
953
|
-
long wall_time_at_finish_ns = monotonic_wall_time_now_ns(
|
|
954
|
-
|
|
955
|
-
// If our end timestamp is not OK, we bail out
|
|
956
|
-
if (wall_time_at_finish_ns == 0) return false;
|
|
947
|
+
long wall_time_at_finish_ns = monotonic_wall_time_now_ns();
|
|
957
948
|
|
|
958
949
|
long gc_cpu_time_elapsed_ns = cpu_time_at_finish_ns - cpu_time_at_start_ns;
|
|
959
950
|
long gc_wall_time_elapsed_ns = wall_time_at_finish_ns - wall_time_at_start_ns;
|
|
960
951
|
|
|
961
|
-
//
|
|
962
|
-
//
|
|
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.
|
|
963
954
|
gc_cpu_time_elapsed_ns = long_max_of(gc_cpu_time_elapsed_ns, 0);
|
|
964
955
|
gc_wall_time_elapsed_ns = long_max_of(gc_wall_time_elapsed_ns, 0);
|
|
965
956
|
|
|
@@ -982,10 +973,10 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance, bool allow_prepa
|
|
|
982
973
|
// Let the caller know if it should schedule a flush or not. Returning true every time would cause a lot of overhead
|
|
983
974
|
// on the application (see GC tracking introduction at the top of the file), so instead we try to accumulate a few
|
|
984
975
|
// samples first.
|
|
985
|
-
bool
|
|
976
|
+
bool over_flush_time_threshold =
|
|
986
977
|
(wall_time_at_finish_ns - state->gc_tracking.wall_time_at_last_flushed_gc_event_ns) >= TIME_BETWEEN_GC_EVENTS_NS;
|
|
987
978
|
|
|
988
|
-
if (
|
|
979
|
+
if (over_flush_time_threshold) {
|
|
989
980
|
return true;
|
|
990
981
|
} else {
|
|
991
982
|
return gc_profiling_has_major_gc_finished();
|
|
@@ -1224,14 +1215,14 @@ static void check_frozen_thread(VALUE thread) {
|
|
|
1224
1215
|
|
|
1225
1216
|
// See the docs on struct per_thread_context.
|
|
1226
1217
|
// This allocates a Ruby object and therefore needs the GVL and is not safe to call from RUBY_INTERNAL_EVENT_* hooks.
|
|
1227
|
-
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) {
|
|
1228
1219
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1229
1220
|
if (thread_context != NULL) return thread_context;
|
|
1230
1221
|
|
|
1231
1222
|
check_frozen_thread(thread);
|
|
1232
1223
|
|
|
1233
1224
|
thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
|
|
1234
|
-
initialize_context(thread, thread_context);
|
|
1225
|
+
initialize_context(thread, thread_context, current_monotonic_wall_time_ns);
|
|
1235
1226
|
|
|
1236
1227
|
VALUE wrapper = TypedData_Wrap_Struct(rb_cObject, &per_thread_context_typed_data, thread_context);
|
|
1237
1228
|
rb_ivar_set(thread, dd_per_thread_context_id, wrapper);
|
|
@@ -1245,7 +1236,8 @@ static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *un
|
|
|
1245
1236
|
|
|
1246
1237
|
VALUE thread = rb_tracearg_self(rb_tracearg_from_tracepoint(tracepoint_data));
|
|
1247
1238
|
ENFORCE_THREAD(thread);
|
|
1248
|
-
|
|
1239
|
+
|
|
1240
|
+
get_or_create_context_for(thread, monotonic_wall_time_now_ns());
|
|
1249
1241
|
}
|
|
1250
1242
|
|
|
1251
1243
|
#define LOGGING_GEM_PATH "/lib/logging/diagnostic_context.rb"
|
|
@@ -1269,7 +1261,7 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
|
|
|
1269
1261
|
return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
|
|
1270
1262
|
}
|
|
1271
1263
|
|
|
1272
|
-
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) {
|
|
1273
1265
|
// We always create per_thread_context's with latest_max_frames; that value is kept in sync with the
|
|
1274
1266
|
// active profiler's max_frames by the global reset that runs when profiling starts
|
|
1275
1267
|
// so we expect to always see here the latest correct value to be used.
|
|
@@ -1306,7 +1298,7 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context)
|
|
|
1306
1298
|
|
|
1307
1299
|
thread_context->thread_cpu_time_id = thread_cpu_time_id_for(thread);
|
|
1308
1300
|
|
|
1309
|
-
thread_context->wall_time_at_previous_sample_ns =
|
|
1301
|
+
thread_context->wall_time_at_previous_sample_ns = current_monotonic_wall_time_ns;
|
|
1310
1302
|
thread_context->cpu_time_at_previous_sample_ns = cpu_time_now_ns(thread_context);
|
|
1311
1303
|
|
|
1312
1304
|
// These will only be used during a GC operation
|
|
@@ -1336,6 +1328,8 @@ void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance)
|
|
|
1336
1328
|
|
|
1337
1329
|
latest_max_frames = state->locations.len;
|
|
1338
1330
|
|
|
1331
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns();
|
|
1332
|
+
|
|
1339
1333
|
VALUE threads = thread_list(state);
|
|
1340
1334
|
const long thread_count = RARRAY_LEN(threads);
|
|
1341
1335
|
for (long i = 0; i < thread_count; i++) {
|
|
@@ -1348,12 +1342,12 @@ void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance)
|
|
|
1348
1342
|
|
|
1349
1343
|
sampling_buffer_free(&thread_context->sampling_buffer);
|
|
1350
1344
|
memset(thread_context, 0, sizeof(per_thread_context));
|
|
1351
|
-
initialize_context(thread, thread_context);
|
|
1345
|
+
initialize_context(thread, thread_context, current_monotonic_wall_time_ns);
|
|
1352
1346
|
|
|
1353
1347
|
thread_context->is_profiler_internal_thread = is_profiler_internal_thread;
|
|
1354
1348
|
} else {
|
|
1355
1349
|
// If thread didn't have a context, let's trigger its creation
|
|
1356
|
-
get_or_create_context_for(thread);
|
|
1350
|
+
get_or_create_context_for(thread, current_monotonic_wall_time_ns);
|
|
1357
1351
|
}
|
|
1358
1352
|
}
|
|
1359
1353
|
}
|
|
@@ -1410,7 +1404,6 @@ static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context)
|
|
|
1410
1404
|
ID2SYM(rb_intern("gvl_state_change_count")), /* => */ ULL2NUM(thread_context->gvl_state_change_count),
|
|
1411
1405
|
ID2SYM(rb_intern("gvl_state_change_count_at_previous_sample")), /* => */ ULL2NUM(thread_context->gvl_state_change_count_at_previous_sample),
|
|
1412
1406
|
ID2SYM(rb_intern("was_skipped_at_last_sample")), /* => */ thread_context->was_skipped_at_last_sample ? Qtrue : Qfalse,
|
|
1413
|
-
ID2SYM(rb_intern("prepare_sample_on_gc_finish")), /* => */ thread_context->prepare_sample_on_gc_finish ? Qtrue : Qfalse,
|
|
1414
1407
|
ID2SYM(rb_intern("is_profiler_internal_thread")), /* => */ thread_context->is_profiler_internal_thread ? Qtrue : Qfalse,
|
|
1415
1408
|
};
|
|
1416
1409
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(context_as_hash, arguments[i], arguments[i+1]);
|
|
@@ -1479,7 +1472,7 @@ static long update_time_since_previous_sample(long *time_at_previous_sample_ns,
|
|
|
1479
1472
|
thread_context->thread_id,
|
|
1480
1473
|
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
1481
1474
|
thread_context->gc_tracking.wall_time_at_start_ns,
|
|
1482
|
-
monotonic_wall_time_now_ns(
|
|
1475
|
+
monotonic_wall_time_now_ns()
|
|
1483
1476
|
);
|
|
1484
1477
|
}
|
|
1485
1478
|
|
|
@@ -1503,10 +1496,7 @@ static long update_cpu_time_since_previous_sample(per_thread_context *thread_con
|
|
|
1503
1496
|
thread_context
|
|
1504
1497
|
);
|
|
1505
1498
|
|
|
1506
|
-
|
|
1507
|
-
if (elapsed_time_ns < 0) {
|
|
1508
|
-
raise_error(rb_eRuntimeError, "BUG: Unexpected CPU time going backwards between samples");
|
|
1509
|
-
}
|
|
1499
|
+
if (elapsed_time_ns < 0) raise_error(rb_eRuntimeError, "BUG: Unexpected CPU time going backwards between samples");
|
|
1510
1500
|
|
|
1511
1501
|
return elapsed_time_ns;
|
|
1512
1502
|
}
|
|
@@ -1518,11 +1508,9 @@ static long update_wall_time_since_previous_sample(per_thread_context *thread_co
|
|
|
1518
1508
|
thread_context
|
|
1519
1509
|
);
|
|
1520
1510
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
// https://github.com/DataDog/dd-trace-rb/pull/2336.
|
|
1525
|
-
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;
|
|
1526
1514
|
}
|
|
1527
1515
|
|
|
1528
1516
|
// Safety: This function is assumed never to raise exceptions by callers
|
|
@@ -1695,14 +1683,7 @@ static VALUE thread_list(thread_context_collector_state *state) {
|
|
|
1695
1683
|
return result;
|
|
1696
1684
|
}
|
|
1697
1685
|
|
|
1698
|
-
|
|
1699
|
-
// the current thread.
|
|
1700
|
-
//
|
|
1701
|
-
// Assumptions for this function are same as for `thread_context_collector_sample` except that this function is
|
|
1702
|
-
// expected to be called from a signal handler and to be async-signal-safe, and `during_sample` MUST be unset.
|
|
1703
|
-
//
|
|
1704
|
-
// Also, no allocation (Ruby or malloc) can happen.
|
|
1705
|
-
bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
1686
|
+
static inline bool thread_context_collector_prepare_sample_internal(void) {
|
|
1706
1687
|
VALUE current_thread = rb_thread_current();
|
|
1707
1688
|
per_thread_context *thread_context = get_per_thread_context(current_thread);
|
|
1708
1689
|
if (thread_context == NULL) return false;
|
|
@@ -1710,10 +1691,16 @@ bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
|
1710
1691
|
return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
|
|
1711
1692
|
}
|
|
1712
1693
|
|
|
1713
|
-
// Called from
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
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();
|
|
1717
1704
|
}
|
|
1718
1705
|
|
|
1719
1706
|
// This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
|
|
@@ -2169,19 +2156,19 @@ static void mark_thread_as_profiler_internal(per_thread_context *ctx) {
|
|
|
2169
2156
|
}
|
|
2170
2157
|
|
|
2171
2158
|
void thread_context_collector_profiler_internal_thread_started(void) {
|
|
2172
|
-
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());
|
|
2173
2160
|
mark_thread_as_profiler_internal(ctx);
|
|
2174
2161
|
}
|
|
2175
2162
|
|
|
2176
2163
|
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2177
|
-
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());
|
|
2178
2165
|
mark_thread_as_profiler_internal(ctx);
|
|
2179
2166
|
return Qnil;
|
|
2180
2167
|
}
|
|
2181
2168
|
|
|
2182
2169
|
// Called via rb_ensure when a profiler-internal thread (worker or idle helper) is about to exit.
|
|
2183
|
-
// Records a final sample so the thread's accumulated cpu/wall time since the last
|
|
2184
|
-
//
|
|
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
|
|
2185
2172
|
// serialization, but it can't help at shutdown: by the time the final serialize runs, these
|
|
2186
2173
|
// threads are already dead and absent from thread_list.
|
|
2187
2174
|
//
|
|
@@ -2191,13 +2178,12 @@ VALUE thread_context_collector_profiler_internal_thread_done(VALUE self_instance
|
|
|
2191
2178
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2192
2179
|
|
|
2193
2180
|
VALUE current_thread = rb_thread_current();
|
|
2194
|
-
|
|
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);
|
|
2195
2183
|
if (!thread_context->is_profiler_internal_thread) {
|
|
2196
2184
|
rb_raise(rb_eRuntimeError, "current thread %"PRIsVALUE" is not profiler-internal thread", current_thread);
|
|
2197
2185
|
}
|
|
2198
2186
|
|
|
2199
|
-
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2200
|
-
|
|
2201
2187
|
update_metrics_and_sample(
|
|
2202
2188
|
state,
|
|
2203
2189
|
current_thread,
|
|
@@ -2210,14 +2196,14 @@ VALUE thread_context_collector_profiler_internal_thread_done(VALUE self_instance
|
|
|
2210
2196
|
|
|
2211
2197
|
// Flushes threads whose last per-tick sample was skipped (either by the SUSPENDED-skip
|
|
2212
2198
|
// optimization, or by is_profiler_internal_thread) so their accumulated time is recorded.
|
|
2213
|
-
// Called
|
|
2199
|
+
// Called before serialization so that threads suspended across the whole profile period still get sampled.
|
|
2214
2200
|
//
|
|
2215
2201
|
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2216
|
-
|
|
2202
|
+
VALUE thread_context_prepare_serialize(VALUE self_instance) {
|
|
2217
2203
|
thread_context_collector_state *state;
|
|
2218
2204
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2219
2205
|
|
|
2220
|
-
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(
|
|
2206
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns();
|
|
2221
2207
|
VALUE threads = thread_list(state);
|
|
2222
2208
|
const long thread_count = RARRAY_LEN(threads);
|
|
2223
2209
|
|
|
@@ -2237,6 +2223,12 @@ void thread_context_collector_on_serialize(VALUE self_instance) {
|
|
|
2237
2223
|
);
|
|
2238
2224
|
}
|
|
2239
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);
|
|
2240
2232
|
}
|
|
2241
2233
|
|
|
2242
2234
|
void thread_context_collector_on_gvl_released(per_thread_context *thread_context) {
|
|
@@ -2244,10 +2236,7 @@ void thread_context_collector_on_gvl_released(per_thread_context *thread_context
|
|
|
2244
2236
|
}
|
|
2245
2237
|
|
|
2246
2238
|
void thread_context_collector_on_gvl_waiting(per_thread_context *thread_context) {
|
|
2247
|
-
|
|
2248
|
-
if (current_monotonic_wall_time_ns <= 0) return;
|
|
2249
|
-
|
|
2250
|
-
thread_context->gvl_waiting_at = current_monotonic_wall_time_ns;
|
|
2239
|
+
thread_context->gvl_waiting_at = monotonic_wall_time_now_ns();
|
|
2251
2240
|
}
|
|
2252
2241
|
|
|
2253
2242
|
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
@@ -2296,7 +2285,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2296
2285
|
return (on_gvl_running_result) {.action = ON_GVL_RUNNING_SAMPLE, .waiting_for_gvl_duration_ns = 0};
|
|
2297
2286
|
}
|
|
2298
2287
|
|
|
2299
|
-
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;
|
|
2300
2289
|
|
|
2301
2290
|
bool should_sample = waiting_for_gvl_duration_ns >= waiting_for_gvl_threshold_ns;
|
|
2302
2291
|
|
|
@@ -2365,7 +2354,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2365
2354
|
thread_context_collector_state *state;
|
|
2366
2355
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2367
2356
|
|
|
2368
|
-
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);
|
|
2369
2358
|
|
|
2370
2359
|
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
2371
2360
|
|
|
@@ -2517,7 +2506,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2517
2506
|
VALUE result = thread_context_collector_sample_after_gvl_running(
|
|
2518
2507
|
collector_instance,
|
|
2519
2508
|
thread,
|
|
2520
|
-
monotonic_wall_time_now_ns(
|
|
2509
|
+
monotonic_wall_time_now_ns()
|
|
2521
2510
|
);
|
|
2522
2511
|
|
|
2523
2512
|
debug_leave_unsafe_context();
|
|
@@ -2535,13 +2524,33 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2535
2524
|
|
|
2536
2525
|
#endif // NO_GVL_INSTRUMENTATION
|
|
2537
2526
|
|
|
2538
|
-
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
|
+
|
|
2539
2536
|
ENFORCE_THREAD(thread);
|
|
2540
2537
|
|
|
2541
2538
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2542
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");
|
|
2543
2540
|
|
|
2544
|
-
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(
|
|
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) {
|
|
2548
|
+
ENFORCE_THREAD(thread);
|
|
2549
|
+
|
|
2550
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
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");
|
|
2552
|
+
|
|
2553
|
+
thread_context->gvl_waiting_at += NUM2LONG(delta_ns);
|
|
2545
2554
|
|
|
2546
2555
|
return Qtrue;
|
|
2547
2556
|
}
|
|
@@ -2584,8 +2593,7 @@ static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE c
|
|
|
2584
2593
|
thread_context_collector_state *state;
|
|
2585
2594
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2586
2595
|
|
|
2587
|
-
long
|
|
2588
|
-
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());
|
|
2589
2597
|
|
|
2590
2598
|
return LONG2NUM(system_epoch_time_ns);
|
|
2591
2599
|
}
|
|
@@ -2593,8 +2601,3 @@ static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE c
|
|
|
2593
2601
|
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self) {
|
|
2594
2602
|
return thread_context_collector_prepare_sample_inside_signal_handler() ? Qtrue : Qfalse;
|
|
2595
2603
|
}
|
|
2596
|
-
|
|
2597
|
-
static VALUE _native_request_prepare_on_gc_finish(DDTRACE_UNUSED VALUE self) {
|
|
2598
|
-
thread_context_collector_request_prepare_on_gc_finish();
|
|
2599
|
-
return Qnil;
|
|
2600
|
-
}
|
|
@@ -13,7 +13,7 @@ __attribute__((warn_unused_result)) bool thread_context_collector_sample(
|
|
|
13
13
|
long current_monotonic_wall_time_ns
|
|
14
14
|
);
|
|
15
15
|
__attribute__((warn_unused_result)) bool thread_context_collector_prepare_sample_inside_signal_handler(void);
|
|
16
|
-
void
|
|
16
|
+
void thread_context_collector_prepare_sample_on_gc_finish(void);
|
|
17
17
|
__attribute__((warn_unused_result)) bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object);
|
|
18
18
|
void thread_context_collector_commit_heap_recordings_may_lose_gvl(VALUE self_instance);
|
|
19
19
|
void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples);
|
|
@@ -21,11 +21,11 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance);
|
|
|
21
21
|
VALUE thread_context_collector_heap_update_may_lose_gvl(VALUE self_instance);
|
|
22
22
|
void thread_context_collector_resolve_otel_span_key_may_lose_gvl(VALUE self_instance);
|
|
23
23
|
void thread_context_collector_on_gc_start(VALUE self_instance);
|
|
24
|
-
__attribute__((warn_unused_result)) bool thread_context_collector_on_gc_finish(VALUE self_instance
|
|
24
|
+
__attribute__((warn_unused_result)) bool thread_context_collector_on_gc_finish(VALUE self_instance);
|
|
25
25
|
VALUE enforce_thread_context_collector_instance(VALUE object);
|
|
26
26
|
void thread_context_collector_stats(VALUE self_instance, VALUE stats_hash);
|
|
27
27
|
void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance);
|
|
28
|
-
|
|
28
|
+
VALUE thread_context_prepare_serialize(VALUE self_instance);
|
|
29
29
|
void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance);
|
|
30
30
|
void thread_context_collector_profiler_internal_thread_started(void);
|
|
31
31
|
VALUE thread_context_collector_profiler_internal_thread_done(VALUE self_instance);
|
|
@@ -139,6 +139,8 @@ end
|
|
|
139
139
|
|
|
140
140
|
have_func "malloc_stats"
|
|
141
141
|
|
|
142
|
+
$defs << "-DHAVE_ZJIT_FRAME" if RUBY_VERSION >= "4.1"
|
|
143
|
+
|
|
142
144
|
# On older Rubies, there was no primitive mutex and condition variable implemented in `thread_sync.rb` (internal)
|
|
143
145
|
$defs << "-DNO_PRIMITIVE_MUTEX_AND_CONDITION_VARIABLE" if RUBY_VERSION < "4"
|
|
144
146
|
|
|
@@ -627,7 +627,7 @@ static VALUE heap_recorder_update_locked(VALUE heap_recorder_update_locked_args_
|
|
|
627
627
|
}
|
|
628
628
|
|
|
629
629
|
size_t current_gc_gen = rb_gc_count();
|
|
630
|
-
long now_ns = monotonic_wall_time_now_ns(
|
|
630
|
+
long now_ns = monotonic_wall_time_now_ns();
|
|
631
631
|
|
|
632
632
|
if (!full_update) {
|
|
633
633
|
if (current_gc_gen == heap_recorder->update_gen) {
|
|
@@ -641,7 +641,7 @@ static VALUE heap_recorder_update_locked(VALUE heap_recorder_update_locked_args_
|
|
|
641
641
|
return Qnil;
|
|
642
642
|
}
|
|
643
643
|
|
|
644
|
-
if (
|
|
644
|
+
if ((now_ns - heap_recorder->last_update_ns) < MIN_TIME_BETWEEN_HEAP_RECORDER_UPDATES_NS) {
|
|
645
645
|
// We did an update not too long ago. Let's skip this one to avoid over-taxing the system.
|
|
646
646
|
heap_recorder->stats_lifetime.updates_skipped_time++;
|
|
647
647
|
return Qnil;
|