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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2ec6218e3a5ff1d1a8e18c9c12e665dd21d88d0e281eb1ff7690958433b7734a
|
|
4
|
+
data.tar.gz: 6bc2e56d66439425d29b7a9f7c843da914d6d7e4fb7509b0d52cd52b5c3e7dba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 323a79d901180e0174cc4052a39cfd52637e7cb496ae8aef6813dfcbca21e943757516a7663acab009e286f74ab8d0306a7fe833d562814f1a78cb47f6f4af6f
|
|
7
|
+
data.tar.gz: fc1a980e41d7f6872acdd1addf5bbcaa91e2609fa69283ca1fd3bdc9cd91e6593c225946bc6e3007fd0069fe5c8e32a6b42311ba69912742f96f504b4b42c0b1
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.45.0] - 2026-10-13
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
* Core: Begin the progressive rollout of the native trace exporter, enabling it by default on CRuby 4.0.0 and later. ([#6419][])
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
* Tracing: Fix missing Agent URLs in startup diagnostics when using the native trace exporter. ([#6433][])
|
|
14
|
+
* Tracing: Fix child processes hanging during garbage collection after closing a native trace transport and then forking. ([#6433][])
|
|
15
|
+
* Tracing: Security: Fixes a security vulnerability, as described in [GHSA-p7cx-x3jr-jj6w](https://github.com/DataDog/dd-trace-rb/security/advisories/GHSA-p7cx-x3jr-jj6w). ([#6441][])
|
|
16
|
+
* Tracing: Fix ignored `c.agent.timeout_seconds` and `DD_TRACE_AGENT_TIMEOUT_SECONDS` settings when using the native trace exporter. ([#6433][])
|
|
17
|
+
* Tracing: Fix custom trace transports being replaced when native transport is enabled, restoring compatibility with `datadog-ci`. ([#6433][])
|
|
18
|
+
* Profiling: Fix a sampling race condition during profile serialization. ([#6377][])
|
|
19
|
+
* Profiling: Fix potential crash due to underflow when sampling. ([#6401][])
|
|
20
|
+
* Profiling: Fix additional crashes during garbage collection compaction when profiling is enabled. ([#6412][])
|
|
21
|
+
* AppSec: Fix APM statistics to be properly excluded when using the native trace exporter with APM tracing disabled. ([#6433][])
|
|
22
|
+
|
|
5
23
|
## [2.44.0] - 2026-10-07
|
|
6
24
|
|
|
7
25
|
### Added
|
|
@@ -3820,7 +3838,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
|
|
|
3820
3838
|
Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
3821
3839
|
|
|
3822
3840
|
|
|
3823
|
-
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.
|
|
3841
|
+
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.45.0...master
|
|
3842
|
+
[2.45.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.44.0...v2.45.0
|
|
3824
3843
|
[2.44.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.43.0...v2.44.0
|
|
3825
3844
|
[2.43.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.42.0...v2.43.0
|
|
3826
3845
|
[2.42.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.41.0...v2.42.0
|
|
@@ -5669,11 +5688,17 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5669
5688
|
[#6364]: https://github.com/DataDog/dd-trace-rb/issues/6364
|
|
5670
5689
|
[#6371]: https://github.com/DataDog/dd-trace-rb/issues/6371
|
|
5671
5690
|
[#6375]: https://github.com/DataDog/dd-trace-rb/issues/6375
|
|
5691
|
+
[#6377]: https://github.com/DataDog/dd-trace-rb/issues/6377
|
|
5672
5692
|
[#6394]: https://github.com/DataDog/dd-trace-rb/issues/6394
|
|
5673
5693
|
[#6395]: https://github.com/DataDog/dd-trace-rb/issues/6395
|
|
5674
5694
|
[#6398]: https://github.com/DataDog/dd-trace-rb/issues/6398
|
|
5695
|
+
[#6401]: https://github.com/DataDog/dd-trace-rb/issues/6401
|
|
5675
5696
|
[#6407]: https://github.com/DataDog/dd-trace-rb/issues/6407
|
|
5697
|
+
[#6412]: https://github.com/DataDog/dd-trace-rb/issues/6412
|
|
5698
|
+
[#6419]: https://github.com/DataDog/dd-trace-rb/issues/6419
|
|
5676
5699
|
[#6427]: https://github.com/DataDog/dd-trace-rb/issues/6427
|
|
5700
|
+
[#6433]: https://github.com/DataDog/dd-trace-rb/issues/6433
|
|
5701
|
+
[#6441]: https://github.com/DataDog/dd-trace-rb/issues/6441
|
|
5677
5702
|
[@AdrianLC]: https://github.com/AdrianLC
|
|
5678
5703
|
[@Azure7111]: https://github.com/Azure7111
|
|
5679
5704
|
[@BabyGroot]: https://github.com/BabyGroot
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
#include "private_vm_api_access.h"
|
|
17
17
|
#include "setup_signal_handler.h"
|
|
18
18
|
#include "time_helpers.h"
|
|
19
|
+
#include "unsafe_api_calls_check.h"
|
|
19
20
|
|
|
20
21
|
#ifdef __APPLE__
|
|
21
22
|
#include "macos_sampler_thread.h"
|
|
@@ -122,6 +123,7 @@ typedef struct {
|
|
|
122
123
|
VALUE self_instance;
|
|
123
124
|
VALUE thread_context_collector_instance;
|
|
124
125
|
VALUE idle_sampling_helper_instance;
|
|
126
|
+
idle_sampling_loop_state *idle_sampling_helper_state;
|
|
125
127
|
VALUE owner_thread;
|
|
126
128
|
dynamic_sampling_rate_state cpu_dynamic_sampling_rate;
|
|
127
129
|
discrete_dynamic_sampler allocation_sampler;
|
|
@@ -145,6 +147,7 @@ typedef struct {
|
|
|
145
147
|
//
|
|
146
148
|
// It's not an actual lock because we rely on the GVL for correct synchronization
|
|
147
149
|
// (and thus this flag is only valid when we know we have the GVL).
|
|
150
|
+
// Exception: `on_gvl_event` sets it for RUBY_INTERNAL_THREAD_EVENT_RESUMED without the GVL -- see the notes there.
|
|
148
151
|
//
|
|
149
152
|
// Similar to a lock, it should not be held across long-running operations,
|
|
150
153
|
// **in particular it MUST NEVER be held during operations where we might lose the GVL**
|
|
@@ -157,6 +160,10 @@ typedef struct {
|
|
|
157
160
|
// @ivoanjo: Right now we always sample inside `safely_call`; if that ever changes, this flag may need to become
|
|
158
161
|
// volatile/atomic/have some barriers to ensure it's visible during e.g. signal handlers.
|
|
159
162
|
bool during_sample;
|
|
163
|
+
// Used from the signal handler to ask for a sample at the end of GC.
|
|
164
|
+
// The GC_EXIT event is emitted by the thread that had the GVL + was doing GC, e.g. Ruby won't switch threads between
|
|
165
|
+
// this flag getting set in the signal handler and the GC_EXIT event.
|
|
166
|
+
bool prepare_sample_on_gc_finish;
|
|
160
167
|
|
|
161
168
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
162
169
|
// Only set when sampling is active (gets created at start and cleaned on stop)
|
|
@@ -229,6 +236,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
229
236
|
static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr);
|
|
230
237
|
static VALUE _native_sampling_loop(VALUE self, VALUE instance);
|
|
231
238
|
static VALUE _native_profiler_internal_thread_done(VALUE self_instance);
|
|
239
|
+
static VALUE _native_prepare_serialize(DDTRACE_UNUSED VALUE self, VALUE self_instance);
|
|
232
240
|
static VALUE _native_stop(DDTRACE_UNUSED VALUE _self, VALUE self_instance, VALUE worker_thread);
|
|
233
241
|
static VALUE stop(VALUE self_instance, VALUE optional_exception, const char *optional_exception_during_operation);
|
|
234
242
|
static void stop_state(cpu_and_wall_time_worker_state *state, VALUE optional_exception, const char *optional_operation_name);
|
|
@@ -244,10 +252,12 @@ static VALUE _native_failure_exception_during_operation(DDTRACE_UNUSED VALUE sel
|
|
|
244
252
|
static void testing_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
|
|
245
253
|
static VALUE _native_install_testing_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
246
254
|
static VALUE _native_remove_testing_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
247
|
-
static VALUE _native_install_sigprof_handler_on_altstack(DDTRACE_UNUSED VALUE self);
|
|
248
255
|
static VALUE _native_trigger_sample(DDTRACE_UNUSED VALUE self);
|
|
249
256
|
static VALUE _native_gc_tracepoint(DDTRACE_UNUSED VALUE self, VALUE instance);
|
|
250
257
|
static void on_gc_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused);
|
|
258
|
+
static void handle_gc_event(cpu_and_wall_time_worker_state *state, rb_event_flag_t event);
|
|
259
|
+
static VALUE _native_on_gc_event(VALUE self, VALUE instance, VALUE event, VALUE during_sample);
|
|
260
|
+
static VALUE _native_request_prepare_on_gc_finish(VALUE self, VALUE instance);
|
|
251
261
|
static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused);
|
|
252
262
|
static VALUE safely_call(
|
|
253
263
|
VALUE (*function_to_call_safely)(VALUE),
|
|
@@ -272,7 +282,6 @@ static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self);
|
|
|
272
282
|
static VALUE rescued_sample_allocation(VALUE tracepoint_data);
|
|
273
283
|
static VALUE rescued_commit_heap_recordings_may_lose_gvl(VALUE self_instance);
|
|
274
284
|
static void delayed_error(cpu_and_wall_time_worker_state *state, const char *error);
|
|
275
|
-
static void delayed_error_clock_failure(cpu_and_wall_time_worker_state *state);
|
|
276
285
|
static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg);
|
|
277
286
|
static VALUE _native_hold_signals(DDTRACE_UNUSED VALUE self);
|
|
278
287
|
static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self);
|
|
@@ -290,7 +299,6 @@ static VALUE handle_sampling_failure_rescued_sample_allocation(VALUE self_instan
|
|
|
290
299
|
static VALUE handle_sampling_failure_rescued_commit_heap_recordings(VALUE self_instance, VALUE exception);
|
|
291
300
|
static inline void during_sample_enter(cpu_and_wall_time_worker_state* state);
|
|
292
301
|
static inline void during_sample_exit(cpu_and_wall_time_worker_state* state);
|
|
293
|
-
static VALUE during_sample_exit_rescue(VALUE state_ptr);
|
|
294
302
|
static void commit_heap_recordings_from_postponed_job_may_lose_gvl(DDTRACE_UNUSED void *_unused);
|
|
295
303
|
|
|
296
304
|
// We're using `on_newobj_event` function with `rb_add_event_hook2`, which requires in its public signature a function
|
|
@@ -317,16 +325,6 @@ static void commit_heap_recordings_from_postponed_job_may_lose_gvl(DDTRACE_UNUSE
|
|
|
317
325
|
// (e.g. signal handler) where it's impossible or just awkward to pass it as an argument.
|
|
318
326
|
static VALUE active_sampler_instance = Qnil;
|
|
319
327
|
static cpu_and_wall_time_worker_state *active_sampler_instance_state = NULL;
|
|
320
|
-
static VALUE clock_failure_exception_class = Qnil;
|
|
321
|
-
|
|
322
|
-
// Stats that live outside of any particular `cpu_and_wall_time_worker_state`, so that they can always be safely
|
|
323
|
-
// touched, even when we're not sure it's safe to touch the state (e.g. signal handler, no GVL, etc).
|
|
324
|
-
typedef struct {
|
|
325
|
-
// How many times we skipped sampling because we were running on the alternate signal stack (e.g. nested inside
|
|
326
|
-
// another signal handler, presumably GC compaction).
|
|
327
|
-
unsigned int signal_handler_skipped_sample_on_altstack;
|
|
328
|
-
} global_stats_t;
|
|
329
|
-
static global_stats_t global_stats;
|
|
330
328
|
|
|
331
329
|
// See handle_sampling_signal for details on what this does
|
|
332
330
|
#ifdef NO_POSTPONED_TRIGGER
|
|
@@ -367,8 +365,6 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
|
|
|
367
365
|
VALUE collectors_cpu_and_wall_time_worker_class = rb_define_class_under(collectors_module, "CpuAndWallTimeWorker", rb_cObject);
|
|
368
366
|
// Hosts methods used for testing the native code using RSpec
|
|
369
367
|
VALUE testing_module = rb_define_module_under(collectors_cpu_and_wall_time_worker_class, "Testing");
|
|
370
|
-
clock_failure_exception_class = rb_define_class_under(collectors_cpu_and_wall_time_worker_class, "ClockFailure", rb_eRuntimeError);
|
|
371
|
-
rb_gc_register_mark_object(clock_failure_exception_class);
|
|
372
368
|
|
|
373
369
|
// Instances of the CpuAndWallTimeWorker class are "TypedData" objects.
|
|
374
370
|
// "TypedData" objects are special objects in the Ruby VM that can wrap C structs.
|
|
@@ -383,6 +379,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
|
|
|
383
379
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_initialize", _native_initialize, -1);
|
|
384
380
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_sampling_loop", _native_sampling_loop, 1);
|
|
385
381
|
rb_define_method(collectors_cpu_and_wall_time_worker_class, "_native_profiler_internal_thread_done", _native_profiler_internal_thread_done, 0);
|
|
382
|
+
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_prepare_serialize", _native_prepare_serialize, 1);
|
|
386
383
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_stop", _native_stop, 2);
|
|
387
384
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
|
388
385
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_stats", _native_stats, 1);
|
|
@@ -395,9 +392,10 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
|
|
|
395
392
|
rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_resume_signals", _native_resume_signals, 0);
|
|
396
393
|
rb_define_singleton_method(testing_module, "_native_install_testing_signal_handler", _native_install_testing_signal_handler, 0);
|
|
397
394
|
rb_define_singleton_method(testing_module, "_native_remove_testing_signal_handler", _native_remove_testing_signal_handler, 0);
|
|
398
|
-
rb_define_singleton_method(testing_module, "_native_install_sigprof_handler_on_altstack", _native_install_sigprof_handler_on_altstack, 0);
|
|
399
395
|
rb_define_singleton_method(testing_module, "_native_trigger_sample", _native_trigger_sample, 0);
|
|
400
396
|
rb_define_singleton_method(testing_module, "_native_gc_tracepoint", _native_gc_tracepoint, 1);
|
|
397
|
+
rb_define_singleton_method(testing_module, "_native_on_gc_event", _native_on_gc_event, 3);
|
|
398
|
+
rb_define_singleton_method(testing_module, "_native_request_prepare_on_gc_finish", _native_request_prepare_on_gc_finish, 1);
|
|
401
399
|
rb_define_singleton_method(testing_module, "_native_simulate_handle_sampling_signal", _native_simulate_handle_sampling_signal, 0);
|
|
402
400
|
rb_define_singleton_method(testing_module, "_native_simulate_sample_from_postponed_job", _native_simulate_sample_from_postponed_job, 0);
|
|
403
401
|
rb_define_singleton_method(testing_module, "_native_is_sigprof_blocked_in_current_thread", _native_is_sigprof_blocked_in_current_thread, 0);
|
|
@@ -420,7 +418,7 @@ static const rb_data_type_t cpu_and_wall_time_worker_typed_data = {
|
|
|
420
418
|
};
|
|
421
419
|
|
|
422
420
|
static VALUE _native_new(VALUE klass) {
|
|
423
|
-
long now = monotonic_wall_time_now_ns(
|
|
421
|
+
long now = monotonic_wall_time_now_ns();
|
|
424
422
|
|
|
425
423
|
cpu_and_wall_time_worker_state *state = ruby_xcalloc(1, sizeof(cpu_and_wall_time_worker_state));
|
|
426
424
|
|
|
@@ -439,6 +437,7 @@ static VALUE _native_new(VALUE klass) {
|
|
|
439
437
|
state->waiting_for_gvl_threshold_ns = 10 * 1000 * 1000;
|
|
440
438
|
state->thread_context_collector_instance = Qnil;
|
|
441
439
|
state->idle_sampling_helper_instance = Qnil;
|
|
440
|
+
state->idle_sampling_helper_state = NULL;
|
|
442
441
|
state->owner_thread = Qnil;
|
|
443
442
|
dynamic_sampling_rate_init(&state->cpu_dynamic_sampling_rate);
|
|
444
443
|
state->gc_tracepoint = Qnil;
|
|
@@ -517,12 +516,14 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
517
516
|
// TODO: May be nice to offer customization here? Distribute available "overhead" margin with a bias towards one or the other
|
|
518
517
|
// sampler.
|
|
519
518
|
dynamic_sampling_rate_set_overhead_target_percentage(&state->cpu_dynamic_sampling_rate, total_overhead_target_percentage / 2);
|
|
520
|
-
|
|
521
|
-
|
|
519
|
+
discrete_dynamic_sampler_set_overhead_target_percentage(
|
|
520
|
+
&state->allocation_sampler, total_overhead_target_percentage / 2, monotonic_wall_time_now_ns()
|
|
521
|
+
);
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
state->thread_context_collector_instance = enforce_thread_context_collector_instance(thread_context_collector_instance);
|
|
525
525
|
state->idle_sampling_helper_instance = idle_sampling_helper_instance;
|
|
526
|
+
state->idle_sampling_helper_state = idle_sampling_helper_get_state(idle_sampling_helper_instance);
|
|
526
527
|
state->gc_tracepoint = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_GC_ENTER | RUBY_INTERNAL_EVENT_GC_EXIT, on_gc_event, NULL /* unused */);
|
|
527
528
|
|
|
528
529
|
return Qtrue;
|
|
@@ -575,8 +576,7 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
|
|
|
575
576
|
|
|
576
577
|
// Reset the dynamic sampling rate state, if any (reminder: the monotonic clock reference may change after a fork)
|
|
577
578
|
dynamic_sampling_rate_reset(&state->cpu_dynamic_sampling_rate);
|
|
578
|
-
|
|
579
|
-
discrete_dynamic_sampler_reset(&state->allocation_sampler, now);
|
|
579
|
+
discrete_dynamic_sampler_reset(&state->allocation_sampler, monotonic_wall_time_now_ns());
|
|
580
580
|
|
|
581
581
|
// Reset per-thread state, if any. This ensures there's no leftover state from a previous profiler run that would
|
|
582
582
|
// affect or be included in samples taken by this profiler about to run.
|
|
@@ -653,6 +653,11 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
|
|
|
653
653
|
return Qnil;
|
|
654
654
|
}
|
|
655
655
|
|
|
656
|
+
static VALUE during_sample_exit_ensure(VALUE state_ptr) {
|
|
657
|
+
during_sample_exit((cpu_and_wall_time_worker_state *) state_ptr);
|
|
658
|
+
return Qnil;
|
|
659
|
+
}
|
|
660
|
+
|
|
656
661
|
static VALUE _native_profiler_internal_thread_done(VALUE self_instance) {
|
|
657
662
|
cpu_and_wall_time_worker_state *state;
|
|
658
663
|
TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
@@ -660,7 +665,23 @@ static VALUE _native_profiler_internal_thread_done(VALUE self_instance) {
|
|
|
660
665
|
during_sample_enter(state);
|
|
661
666
|
return rb_ensure(
|
|
662
667
|
thread_context_collector_profiler_internal_thread_done, state->thread_context_collector_instance,
|
|
663
|
-
|
|
668
|
+
during_sample_exit_ensure, (VALUE) state
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// This method exists so that we run `thread_context_prepare_serialize` protected with
|
|
673
|
+
// `during_sample_enter`/`during_sample_exit` before we actually serialize.
|
|
674
|
+
//
|
|
675
|
+
// That's why in normal operation we go through the `CpuAndWallTimeWorker` and not directly to the `ThreadContext` to
|
|
676
|
+
// serialize.
|
|
677
|
+
static VALUE _native_prepare_serialize(DDTRACE_UNUSED VALUE self, VALUE self_instance) {
|
|
678
|
+
cpu_and_wall_time_worker_state *state;
|
|
679
|
+
TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
680
|
+
|
|
681
|
+
during_sample_enter(state);
|
|
682
|
+
return rb_ensure(
|
|
683
|
+
thread_context_prepare_serialize, state->thread_context_collector_instance,
|
|
684
|
+
during_sample_exit_ensure, (VALUE) state
|
|
664
685
|
);
|
|
665
686
|
}
|
|
666
687
|
|
|
@@ -692,57 +713,19 @@ static VALUE stop(VALUE self_instance, VALUE optional_exception, const char *opt
|
|
|
692
713
|
return Qtrue;
|
|
693
714
|
}
|
|
694
715
|
|
|
695
|
-
// Our SIGPROF handler is not installed with SA_ONSTACK (see `install_sigprof_signal_handler`).
|
|
696
|
-
// If we detect our signal handler is running on the alt stack, it means we interrupted another signal handler
|
|
697
|
-
// that IS running on the alt stack -- in practice, Ruby's GC compaction read-barrier handler.
|
|
698
|
-
#ifdef __APPLE__
|
|
699
|
-
// On macOS, `ucontext->uc_stack.ss_sp` reports the (live) stack pointer near the top of the alt stack rather than the
|
|
700
|
-
// alt-stack base like Linux does, and `ss_size` is the full alt-stack size. Thus the linux version of the check
|
|
701
|
-
// didn't work for macOS.
|
|
702
|
-
// As an alternative, we query `sigaltstack()`; we save/restore errno since `sigaltstack()` may clobber it.
|
|
703
|
-
static bool is_running_on_alternate_signal_stack(DDTRACE_UNUSED void *ucontext) {
|
|
704
|
-
int old_errno = errno;
|
|
705
|
-
stack_t current_alt_stack;
|
|
706
|
-
bool on_alt_stack = sigaltstack(NULL, ¤t_alt_stack) == 0 && (current_alt_stack.ss_flags & SS_ONSTACK) != 0;
|
|
707
|
-
errno = old_errno;
|
|
708
|
-
return on_alt_stack;
|
|
709
|
-
}
|
|
710
|
-
#else
|
|
711
|
-
// NOTE: On Linux we use the alt-stack bounds from the `ucontext` rather than `sigaltstack()`, allowing us to avoid one syscall.
|
|
712
|
-
static bool is_running_on_alternate_signal_stack(void *ucontext) {
|
|
713
|
-
if (ucontext == NULL) return false;
|
|
714
|
-
|
|
715
|
-
stack_t alt_stack = ((ucontext_t *) ucontext)->uc_stack;
|
|
716
|
-
if (alt_stack.ss_size == 0) return false; // No alternate signal stack registered on this thread
|
|
717
|
-
|
|
718
|
-
char stack_local;
|
|
719
|
-
uintptr_t current_sp = (uintptr_t) &stack_local;
|
|
720
|
-
uintptr_t alt_stack_start = (uintptr_t) alt_stack.ss_sp;
|
|
721
|
-
|
|
722
|
-
return current_sp >= alt_stack_start && current_sp < alt_stack_start + alt_stack.ss_size;
|
|
723
|
-
}
|
|
724
|
-
#endif
|
|
725
|
-
|
|
726
716
|
// NOTE: Remember that this will run in the thread and within the scope of user code, including user C code.
|
|
727
717
|
// We need to be careful not to change any state that may be observed OR to restore it if we do. For instance, if anything
|
|
728
718
|
// we do here can set `errno`, then we must be careful to restore the old `errno` after the fact.
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
// since they can touch those pages, and thus we just bail out here in this situation to avoid crashes.
|
|
734
|
-
if (is_running_on_alternate_signal_stack(ucontext)) {
|
|
735
|
-
global_stats.signal_handler_skipped_sample_on_altstack++;
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
|
|
719
|
+
//
|
|
720
|
+
// Neither this handler nor its callees may dereference Ruby objects before confirming GVL ownership or during GC.
|
|
721
|
+
// Holding the GVL is not enough: we may have interrupted GC itself, with Ruby heap pages protected by compaction.
|
|
722
|
+
static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext) {
|
|
739
723
|
// We must first check that we landed on the correct thread and can proceed.
|
|
740
724
|
// We must never touch the state before we confirm that we have landed on the thread that is holding the GVL on the main
|
|
741
725
|
// ractor as otherwise we may be concurrent with the profiler shutting down and removing its state.
|
|
742
726
|
if (
|
|
743
727
|
!ruby_native_thread_p() || // Not a Ruby thread
|
|
744
|
-
!
|
|
745
|
-
!ddtrace_rb_ractor_main_p() // We're not on the main Ractor; we currently don't support profiling non-main Ractors
|
|
728
|
+
!is_current_thread_in_main_ractor_and_holding_the_gvl() // Not safe to enqueue a sample from this thread
|
|
746
729
|
) return;
|
|
747
730
|
|
|
748
731
|
cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
|
|
@@ -767,11 +750,11 @@ static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED si
|
|
|
767
750
|
if (sample_from_signal_handler) {
|
|
768
751
|
if (rb_during_gc()) {
|
|
769
752
|
// During GC Ruby might be marking our buffer or compacting (moving) where iseqs live so we skip preparing the
|
|
770
|
-
// sample immediately, and instead
|
|
753
|
+
// sample immediately, and instead defer preparation until GC_EXIT.
|
|
771
754
|
//
|
|
772
755
|
// When GC profiling is disabled, we just fall back to sampling the stack after GC finishes, e.g. it's the
|
|
773
756
|
// equivalent of disabling `sample_from_signal_handler` for samples that happen during GC.
|
|
774
|
-
|
|
757
|
+
state->prepare_sample_on_gc_finish = true;
|
|
775
758
|
} else {
|
|
776
759
|
// Buffer current stack trace. Note that this will not actually record the sample, for that we still need to wait
|
|
777
760
|
// until the postponed job below gets run.
|
|
@@ -812,7 +795,8 @@ static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED si
|
|
|
812
795
|
#endif
|
|
813
796
|
}
|
|
814
797
|
|
|
815
|
-
//
|
|
798
|
+
// Runs without the GVL: neither this loop nor its callees may dereference Ruby objects until they reacquire it.
|
|
799
|
+
// GC compaction can protect Ruby heap pages while this loop runs, so even reading an object's header is unsafe.
|
|
816
800
|
static void *run_sampling_trigger_loop(void *state_ptr) {
|
|
817
801
|
cpu_and_wall_time_worker_state *state = (cpu_and_wall_time_worker_state *) state_ptr;
|
|
818
802
|
|
|
@@ -832,7 +816,7 @@ static void *run_sampling_trigger_loop(void *state_ptr) {
|
|
|
832
816
|
state->stats.trigger_simulated_signal_delivery_attempts++;
|
|
833
817
|
grab_gvl_and_sample(); // Note: Can raise exceptions
|
|
834
818
|
} else {
|
|
835
|
-
current_gvl_owner owner =
|
|
819
|
+
current_gvl_owner owner = main_ractor_gvl_owner();
|
|
836
820
|
if (owner.valid) {
|
|
837
821
|
// Note that reading the GVL owner and sending them a signal is a race -- the Ruby VM keeps on executing while
|
|
838
822
|
// we're doing this, so we may still not signal the correct thread from time to time, but our signal handler
|
|
@@ -856,7 +840,7 @@ static void *run_sampling_trigger_loop(void *state_ptr) {
|
|
|
856
840
|
// for an uncontrolled amount of time. (This can still happen to the IdleSamplingHelper, but the
|
|
857
841
|
// CpuAndWallTimeWorker will still be free to interrupt the Ruby VM and keep sampling for the entire blocking period).
|
|
858
842
|
state->stats.trigger_simulated_signal_delivery_attempts++;
|
|
859
|
-
idle_sampling_helper_request_action(state->
|
|
843
|
+
idle_sampling_helper_request_action(state->idle_sampling_helper_state, grab_gvl_and_sample);
|
|
860
844
|
}
|
|
861
845
|
}
|
|
862
846
|
}
|
|
@@ -868,7 +852,7 @@ static void *run_sampling_trigger_loop(void *state_ptr) {
|
|
|
868
852
|
// Note that we deliberately should NOT combine this sleep_for with the one above because the result of
|
|
869
853
|
// `dynamic_sampling_rate_get_sleep` may have changed while the above sleep was ongoing.
|
|
870
854
|
uint64_t extra_sleep =
|
|
871
|
-
dynamic_sampling_rate_get_sleep(&state->cpu_dynamic_sampling_rate, monotonic_wall_time_now_ns(
|
|
855
|
+
dynamic_sampling_rate_get_sleep(&state->cpu_dynamic_sampling_rate, monotonic_wall_time_now_ns());
|
|
872
856
|
if (state->dynamic_sampling_rate_enabled && extra_sleep > 0) {
|
|
873
857
|
state->stats.trigger_sample_extra_sleep++;
|
|
874
858
|
sleep_for(extra_sleep);
|
|
@@ -922,7 +906,7 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
|
|
|
922
906
|
cpu_and_wall_time_worker_state *state;
|
|
923
907
|
TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
924
908
|
|
|
925
|
-
long wall_time_ns_before_sample = monotonic_wall_time_now_ns(
|
|
909
|
+
long wall_time_ns_before_sample = monotonic_wall_time_now_ns();
|
|
926
910
|
|
|
927
911
|
if (state->dynamic_sampling_rate_enabled && !dynamic_sampling_rate_should_sample(&state->cpu_dynamic_sampling_rate, wall_time_ns_before_sample)) {
|
|
928
912
|
state->stats.cpu_skipped++;
|
|
@@ -934,11 +918,13 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
|
|
|
934
918
|
bool needs_otel_span_key =
|
|
935
919
|
thread_context_collector_sample(state->thread_context_collector_instance, wall_time_ns_before_sample);
|
|
936
920
|
|
|
937
|
-
long wall_time_ns_after_sample = monotonic_wall_time_now_ns(
|
|
938
|
-
long delta_ns = wall_time_ns_after_sample - wall_time_ns_before_sample;
|
|
921
|
+
long wall_time_ns_after_sample = monotonic_wall_time_now_ns();
|
|
939
922
|
|
|
940
|
-
|
|
941
|
-
|
|
923
|
+
if (wall_time_ns_after_sample < wall_time_ns_before_sample) {
|
|
924
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected wall time going backwards when tracking overhead");
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
uint64_t sampling_time_ns = wall_time_ns_after_sample - wall_time_ns_before_sample;
|
|
942
928
|
|
|
943
929
|
state->stats.cpu_sampling_time_ns_min = uint64_min_of(sampling_time_ns, state->stats.cpu_sampling_time_ns_min);
|
|
944
930
|
state->stats.cpu_sampling_time_ns_max = uint64_max_of(sampling_time_ns, state->stats.cpu_sampling_time_ns_max);
|
|
@@ -1011,7 +997,7 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
|
|
|
1011
997
|
// If we stopped sampling due to an exception, re-raise it (now in the worker thread)
|
|
1012
998
|
if (state->failure_exception != Qnil) rb_exc_raise(state->failure_exception);
|
|
1013
999
|
|
|
1014
|
-
|
|
1000
|
+
_native_profiler_internal_thread_done(instance);
|
|
1015
1001
|
|
|
1016
1002
|
return Qnil;
|
|
1017
1003
|
}
|
|
@@ -1051,28 +1037,6 @@ static VALUE _native_remove_testing_signal_handler(DDTRACE_UNUSED VALUE self) {
|
|
|
1051
1037
|
return Qtrue;
|
|
1052
1038
|
}
|
|
1053
1039
|
|
|
1054
|
-
// Reproduces a profiler sample being taken in the middle of GC compaction: during compaction a SIGPROF can be
|
|
1055
|
-
// delivered while nested inside Ruby's read-barrier signal handler, which runs on the alternate signal stack. We
|
|
1056
|
-
// simulate that here by re-installing the production `handle_sampling_signal` with the `SA_ONSTACK` flag, so it runs
|
|
1057
|
-
// on the alternate signal stack just like it would in that scenario.
|
|
1058
|
-
//
|
|
1059
|
-
// The `SA_ONSTACK` will be undone by any next `replace_sigprof_signal_handler_with_empty_handler` /
|
|
1060
|
-
// `install_sigprof_signal_handler_internal` / `remove_sigprof_signal_handle` so this doesn't need a specific, symmetric
|
|
1061
|
-
// `uninstall` to undo this.
|
|
1062
|
-
static VALUE _native_install_sigprof_handler_on_altstack(DDTRACE_UNUSED VALUE self) {
|
|
1063
|
-
struct sigaction signal_handler_config = {
|
|
1064
|
-
.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK,
|
|
1065
|
-
.sa_sigaction = handle_sampling_signal,
|
|
1066
|
-
};
|
|
1067
|
-
sigemptyset(&signal_handler_config.sa_mask);
|
|
1068
|
-
|
|
1069
|
-
if (sigaction(SIGPROF, &signal_handler_config, NULL) != 0) {
|
|
1070
|
-
rb_sys_fail("Failed to install SA_ONSTACK SIGPROF signal handler for testing");
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
return Qtrue;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
1040
|
// This method exists only to enable testing Datadog::Profiling::Collectors::CpuAndWallTimeWorker behavior using RSpec.
|
|
1077
1041
|
// It SHOULD NOT be used for other purposes.
|
|
1078
1042
|
static VALUE _native_trigger_sample(DDTRACE_UNUSED VALUE self) {
|
|
@@ -1116,13 +1080,21 @@ static void on_gc_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
|
|
|
1116
1080
|
// and disabled before it is cleared, but just in case...
|
|
1117
1081
|
if (state == NULL) return;
|
|
1118
1082
|
|
|
1083
|
+
handle_gc_event(state, event);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
static void handle_gc_event(cpu_and_wall_time_worker_state *state, rb_event_flag_t event) {
|
|
1119
1087
|
if (event == RUBY_INTERNAL_EVENT_GC_ENTER) {
|
|
1088
|
+
// A signal can arrive after GC_EXIT while `rb_during_gc` is still true; discard requests from the previous GC step.
|
|
1089
|
+
state->prepare_sample_on_gc_finish = false;
|
|
1120
1090
|
thread_context_collector_on_gc_start(state->thread_context_collector_instance);
|
|
1121
1091
|
} else if (event == RUBY_INTERNAL_EVENT_GC_EXIT) {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1092
|
+
if (state->prepare_sample_on_gc_finish && !state->during_sample) {
|
|
1093
|
+
during_sample_enter(state);
|
|
1094
|
+
thread_context_collector_prepare_sample_on_gc_finish();
|
|
1095
|
+
during_sample_exit(state);
|
|
1096
|
+
}
|
|
1097
|
+
bool should_flush = thread_context_collector_on_gc_finish(state->thread_context_collector_instance);
|
|
1126
1098
|
|
|
1127
1099
|
// We use rb_postponed_job_register_one to ask Ruby to run thread_context_collector_sample_after_gc when the
|
|
1128
1100
|
// thread collector flags it's time to flush.
|
|
@@ -1136,6 +1108,31 @@ static void on_gc_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
|
|
|
1136
1108
|
}
|
|
1137
1109
|
}
|
|
1138
1110
|
|
|
1111
|
+
static VALUE _native_request_prepare_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
1112
|
+
cpu_and_wall_time_worker_state *state;
|
|
1113
|
+
TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
1114
|
+
state->prepare_sample_on_gc_finish = true;
|
|
1115
|
+
return Qnil;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
static VALUE _native_on_gc_event(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE event, VALUE during_sample) {
|
|
1119
|
+
cpu_and_wall_time_worker_state *state;
|
|
1120
|
+
TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
1121
|
+
ENFORCE_BOOLEAN(during_sample);
|
|
1122
|
+
rb_event_flag_t event_flag;
|
|
1123
|
+
if (event == ID2SYM(rb_intern("enter"))) event_flag = RUBY_INTERNAL_EVENT_GC_ENTER;
|
|
1124
|
+
else if (event == ID2SYM(rb_intern("exit"))) event_flag = RUBY_INTERNAL_EVENT_GC_EXIT;
|
|
1125
|
+
else rb_raise(rb_eArgError, "Expected :enter or :exit");
|
|
1126
|
+
|
|
1127
|
+
bool was_during_sample = state->during_sample;
|
|
1128
|
+
if (during_sample == Qtrue) during_sample_enter(state);
|
|
1129
|
+
debug_enter_unsafe_context();
|
|
1130
|
+
handle_gc_event(state, event_flag);
|
|
1131
|
+
debug_leave_unsafe_context();
|
|
1132
|
+
if (!was_during_sample) during_sample_exit(state);
|
|
1133
|
+
return Qnil;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1139
1136
|
static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
1140
1137
|
cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
|
|
1141
1138
|
|
|
@@ -1220,6 +1217,8 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE instance)
|
|
|
1220
1217
|
// Disable all hooks, so that there are no more attempts to mutate the profile
|
|
1221
1218
|
disable_hooks(state);
|
|
1222
1219
|
|
|
1220
|
+
private_vm_api_access_self_test();
|
|
1221
|
+
|
|
1223
1222
|
reset_stats_not_thread_safe(state);
|
|
1224
1223
|
|
|
1225
1224
|
// Remove all state from the `Collectors::ThreadState` and connected downstream components
|
|
@@ -1254,7 +1253,6 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
|
1254
1253
|
ID2SYM(rb_intern("simulated_signal_delivery")), /* => */ UINT2NUM(state->stats.simulated_signal_delivery),
|
|
1255
1254
|
ID2SYM(rb_intern("signal_handler_enqueued_sample")), /* => */ UINT2NUM(state->stats.signal_handler_enqueued_sample),
|
|
1256
1255
|
ID2SYM(rb_intern("signal_handler_prepared_sample")), /* => */ UINT2NUM(state->stats.signal_handler_prepared_sample),
|
|
1257
|
-
ID2SYM(rb_intern("signal_handler_skipped_sample_on_altstack")), /* => */ UINT2NUM(global_stats.signal_handler_skipped_sample_on_altstack),
|
|
1258
1256
|
ID2SYM(rb_intern("interrupt_thread_attempts")), /* => */ UINT2NUM(state->stats.interrupt_thread_attempts),
|
|
1259
1257
|
|
|
1260
1258
|
// CPU Stats
|
|
@@ -1340,7 +1338,6 @@ static void reset_stats_not_thread_safe(cpu_and_wall_time_worker_state *state) {
|
|
|
1340
1338
|
.gvl_sampling_time_ns_min = UINT64_MAX,
|
|
1341
1339
|
// Other fields are reset to 0
|
|
1342
1340
|
};
|
|
1343
|
-
global_stats = (global_stats_t) {0};
|
|
1344
1341
|
}
|
|
1345
1342
|
|
|
1346
1343
|
static void sleep_for(uint64_t time_ns) {
|
|
@@ -1369,15 +1366,6 @@ static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self) {
|
|
|
1369
1366
|
return are_allocations_being_tracked ? ULL2NUM(allocation_count) : Qnil;
|
|
1370
1367
|
}
|
|
1371
1368
|
|
|
1372
|
-
#define HANDLE_CLOCK_FAILURE(call) ({ \
|
|
1373
|
-
long _result = (call); \
|
|
1374
|
-
if (_result == 0) { \
|
|
1375
|
-
delayed_error_clock_failure(state); \
|
|
1376
|
-
return; \
|
|
1377
|
-
} \
|
|
1378
|
-
_result; \
|
|
1379
|
-
})
|
|
1380
|
-
|
|
1381
1369
|
// Implements memory-related profiling events. This function is called by Ruby via the `rb_add_event_hook2`
|
|
1382
1370
|
// when the RUBY_INTERNAL_EVENT_NEWOBJ event is triggered.
|
|
1383
1371
|
//
|
|
@@ -1443,16 +1431,13 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
|
|
|
1443
1431
|
if (RB_LIKELY(state->dynamic_sampling_rate_enabled && !discrete_dynamic_sampler_should_sample(&state->allocation_sampler))) {
|
|
1444
1432
|
state->stats.allocation_skipped++;
|
|
1445
1433
|
|
|
1446
|
-
|
|
1447
|
-
HANDLE_CLOCK_FAILURE(now.timestamp_ns);
|
|
1448
|
-
|
|
1449
|
-
bool needs_readjust = discrete_dynamic_sampler_skipped_sample(&state->allocation_sampler, now);
|
|
1434
|
+
bool needs_readjust = discrete_dynamic_sampler_skipped_sample(&state->allocation_sampler, monotonic_coarse_wall_time_now_ns());
|
|
1450
1435
|
if (RB_UNLIKELY(needs_readjust)) {
|
|
1451
1436
|
// We rarely readjust, so this is a cold path
|
|
1452
1437
|
// Also, while above we used the cheaper monotonic_coarse, for this call we want the regular monotonic call,
|
|
1453
1438
|
// which is why we end up getting time "again".
|
|
1454
1439
|
discrete_dynamic_sampler_readjust(
|
|
1455
|
-
&state->allocation_sampler,
|
|
1440
|
+
&state->allocation_sampler, monotonic_wall_time_now_ns()
|
|
1456
1441
|
);
|
|
1457
1442
|
}
|
|
1458
1443
|
|
|
@@ -1462,7 +1447,7 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
|
|
|
1462
1447
|
// From here on, we've decided to go ahead with the sample, which is way less common than skipping it
|
|
1463
1448
|
|
|
1464
1449
|
discrete_dynamic_sampler_before_sample(
|
|
1465
|
-
&state->allocation_sampler,
|
|
1450
|
+
&state->allocation_sampler, monotonic_wall_time_now_ns()
|
|
1466
1451
|
);
|
|
1467
1452
|
|
|
1468
1453
|
during_sample_enter(state);
|
|
@@ -1476,12 +1461,7 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
|
|
|
1476
1461
|
);
|
|
1477
1462
|
|
|
1478
1463
|
if (state->dynamic_sampling_rate_enabled) {
|
|
1479
|
-
|
|
1480
|
-
if (now == 0) {
|
|
1481
|
-
delayed_error_clock_failure(state);
|
|
1482
|
-
// NOTE: Not short-circuiting here to make sure cleanup happens
|
|
1483
|
-
}
|
|
1484
|
-
uint64_t sampling_time_ns = discrete_dynamic_sampler_after_sample(&state->allocation_sampler, now);
|
|
1464
|
+
uint64_t sampling_time_ns = discrete_dynamic_sampler_after_sample(&state->allocation_sampler, monotonic_wall_time_now_ns());
|
|
1485
1465
|
// NOTE: To keep things lean when dynamic sampling rate is disabled we skip clock interactions which is
|
|
1486
1466
|
// why we're fine with having this inside this conditional.
|
|
1487
1467
|
state->stats.allocation_sampling_time_ns_min = uint64_min_of(sampling_time_ns, state->stats.allocation_sampling_time_ns_min);
|
|
@@ -1566,11 +1546,6 @@ static void delayed_error(cpu_and_wall_time_worker_state *state, const char *err
|
|
|
1566
1546
|
stop_state(state, rb_exc_new_cstr(rb_eRuntimeError, error), "delayed_error");
|
|
1567
1547
|
}
|
|
1568
1548
|
|
|
1569
|
-
static void delayed_error_clock_failure(cpu_and_wall_time_worker_state *state) {
|
|
1570
|
-
// If we can't raise an immediate exception at the calling site, use the asynchronous flow through the main worker loop.
|
|
1571
|
-
stop_state(state, rb_exc_new_cstr(clock_failure_exception_class, "failed to get clock time"), "delayed_error_clock_failure");
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
1549
|
static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg) {
|
|
1575
1550
|
ENFORCE_TYPE(error_msg, T_STRING);
|
|
1576
1551
|
|
|
@@ -1619,6 +1594,8 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1619
1594
|
VALUE target_thread = rb_thread_current();
|
|
1620
1595
|
#endif
|
|
1621
1596
|
|
|
1597
|
+
// TODO: `rb_internal_thread_specific_get` is documented as "async and native thread safe.",
|
|
1598
|
+
// unclear if that holds during GC compaction.
|
|
1622
1599
|
per_thread_context* thread_context = get_per_thread_context(target_thread);
|
|
1623
1600
|
if (!thread_context) return;
|
|
1624
1601
|
// If non-NULL the thread is profiled and from the main Ractor
|
|
@@ -1690,14 +1667,15 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1690
1667
|
cpu_and_wall_time_worker_state *state;
|
|
1691
1668
|
TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
1692
1669
|
|
|
1693
|
-
long wall_time_ns_before_sample = monotonic_wall_time_now_ns(
|
|
1670
|
+
long wall_time_ns_before_sample = monotonic_wall_time_now_ns();
|
|
1694
1671
|
thread_context_collector_sample_after_gvl_running(state->thread_context_collector_instance, rb_thread_current(), wall_time_ns_before_sample);
|
|
1695
|
-
long wall_time_ns_after_sample = monotonic_wall_time_now_ns(
|
|
1672
|
+
long wall_time_ns_after_sample = monotonic_wall_time_now_ns();
|
|
1696
1673
|
|
|
1697
|
-
|
|
1674
|
+
if (wall_time_ns_after_sample < wall_time_ns_before_sample) {
|
|
1675
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected wall time going backwards when tracking overhead");
|
|
1676
|
+
}
|
|
1698
1677
|
|
|
1699
|
-
|
|
1700
|
-
uint64_t sampling_time_ns = delta_ns < 0 ? 0 : delta_ns;
|
|
1678
|
+
uint64_t sampling_time_ns = wall_time_ns_after_sample - wall_time_ns_before_sample;
|
|
1701
1679
|
|
|
1702
1680
|
state->stats.gvl_sampling_time_ns_min = uint64_min_of(sampling_time_ns, state->stats.gvl_sampling_time_ns_min);
|
|
1703
1681
|
state->stats.gvl_sampling_time_ns_max = uint64_max_of(sampling_time_ns, state->stats.gvl_sampling_time_ns_max);
|
|
@@ -1801,8 +1779,3 @@ static inline void during_sample_exit(cpu_and_wall_time_worker_state* state) {
|
|
|
1801
1779
|
atomic_signal_fence(memory_order_seq_cst);
|
|
1802
1780
|
state->during_sample = false;
|
|
1803
1781
|
}
|
|
1804
|
-
|
|
1805
|
-
static VALUE during_sample_exit_rescue(VALUE state_ptr) {
|
|
1806
|
-
during_sample_exit((cpu_and_wall_time_worker_state *) state_ptr);
|
|
1807
|
-
return Qnil;
|
|
1808
|
-
}
|
|
@@ -367,11 +367,7 @@ static const rb_data_type_t sampler_typed_data = {
|
|
|
367
367
|
static VALUE _native_new(VALUE klass) {
|
|
368
368
|
sampler_state *state = ruby_xcalloc(1, sizeof(sampler_state));
|
|
369
369
|
|
|
370
|
-
|
|
371
|
-
if (now_ns == 0) {
|
|
372
|
-
raise_error(rb_eRuntimeError, "failed to get clock time");
|
|
373
|
-
}
|
|
374
|
-
discrete_dynamic_sampler_init(&state->sampler, "test sampler", now_ns);
|
|
370
|
+
discrete_dynamic_sampler_init(&state->sampler, "test sampler", monotonic_wall_time_now_ns());
|
|
375
371
|
|
|
376
372
|
// Note: As of this writing, no new Ruby objects get created and stored in the state. If that ever changes, remember
|
|
377
373
|
// to keep them on the stack and mark them with RB_GC_GUARD -- otherwise it's possible for a GC to run and
|