datadog 2.41.0 → 2.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -2
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +110 -61
- data/ext/datadog_profiling_native_extension/collectors_stack.c +11 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +141 -103
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +7 -3
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -10
- data/ext/datadog_profiling_native_extension/extconf.rb +52 -93
- data/ext/datadog_profiling_native_extension/heap_recorder.c +338 -255
- data/ext/datadog_profiling_native_extension/heap_recorder.h +46 -31
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -24
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +509 -440
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +15 -3
- data/ext/datadog_profiling_native_extension/profiling.c +2 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -79
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +0 -7
- data/ext/datadog_profiling_native_extension/stack_recorder.c +93 -61
- data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -10
- data/ext/libdatadog_api/di.c +10 -0
- data/ext/libdatadog_api/extconf.rb +3 -0
- data/ext/libdatadog_api/init.c +2 -0
- data/ext/libdatadog_api/otel_thread_context.c +232 -0
- data/ext/libdatadog_api/otel_thread_context.h +5 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/assets/blocked.html +1 -108
- data/lib/datadog/core/configuration/components.rb +1 -0
- data/lib/datadog/core/crashtracking/component.rb +5 -1
- data/lib/datadog/data_streams/pathway_context.rb +20 -22
- data/lib/datadog/data_streams/processor.rb +31 -0
- data/lib/datadog/di/instrumenter.rb +41 -1
- data/lib/datadog/di/logger.rb +2 -2
- data/lib/datadog/di/probe.rb +9 -1
- data/lib/datadog/di/probe_notification_builder.rb +1 -0
- data/lib/datadog/di/remote.rb +3 -3
- data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
- data/lib/datadog/open_feature/exposures/event.rb +10 -3
- data/lib/datadog/open_feature/ext.rb +19 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
- data/lib/datadog/open_feature/native_evaluator.rb +33 -6
- data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
- data/lib/datadog/open_feature/provider.rb +11 -2
- data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
- data/lib/datadog/opentelemetry/trace.rb +3 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -0
- data/lib/datadog/profiling/collectors/thread_context.rb +0 -4
- data/lib/datadog/profiling/component.rb +8 -16
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
- data/lib/datadog/tracing/distributed/baggage.rb +0 -1
- data/lib/datadog/tracing/distributed/datadog.rb +3 -3
- data/lib/datadog/tracing/distributed/propagation.rb +3 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
- data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
- data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
- data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
- data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
- data/lib/datadog/tracing/otel_thread_context.rb +30 -0
- data/lib/datadog/tracing/remote.rb +195 -27
- data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
- data/lib/datadog/tracing/trace_digest.rb +22 -4
- data/lib/datadog/tracing/trace_operation.rb +22 -10
- data/lib/datadog/tracing/tracer.rb +5 -5
- data/lib/datadog/version.rb +1 -1
- metadata +14 -8
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dfc982d67dbb768eb43d510dd6322ee484bc0a0fdacf20deb40196aa13821e51
|
|
4
|
+
data.tar.gz: be416fd9f34cbdf7ead90fa03d273f1739e90008cb82b6a0d22d5f62f17e1660
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 80d39181feb3e02e4f022d06f0c6bab26e993f48521c2fb5e526d3b81a5b4a68e7069d92af90638700f2e0a0af24efbd0df63de311c7561cb02c1270d63cbaaa
|
|
7
|
+
data.tar.gz: 44932c7f87c3a52e1790b44ae2c4f13ae63006360a0846c240ec0b6ac3ffe4b1c98ff33426b19f27f51ce5105770ab263870601ebe5b33788381debd85a04b37
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.42.0] - 2026-08-31
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
* Tracing: Add sampling probability compatibility with OTel-instrumented services ([#6111][])
|
|
10
|
+
* Dynamic Instrumentation: Honor organization- and environment-level remote enablement for Ruby services. ([#6234][])
|
|
11
|
+
* Dynamic Instrumentation: Add per-process runtime id for snapshots to distinguish process restarts within a container ([#6232][])
|
|
12
|
+
* Open Feature: OpenFeature provider sends the serial ID of the assigned split on exposure events ([#6220][])
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
* Profiling: Drop usage of MJIT headers for Ruby 2.6 to 3.1 ([#6135][])
|
|
17
|
+
* Profiling: Re-enable heap live size profiling for Ruby 4+ ([#6176][])
|
|
18
|
+
* Dynamic Instrumentation: Enforce process-wide rate limit across all probes ([#6043][])
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
* Core: Ignore `SignalException` from crashtracker as unhandled exception errors ([#6219][])
|
|
23
|
+
* Tracing: Fix missing peer tags for database queries traced through `ActiveRecord` ([#6213][])
|
|
24
|
+
* Profiling: Fix bug in `sample_after_gc` leading to profiler stopping ([#6242][])
|
|
25
|
+
* Profiling: Improve stability of heap profiling by using `ObjectSpace::WeakMap` instead of `_id2ref` ([#6176][])
|
|
26
|
+
* Profiling: VM-internal objects are no longer sampled for heap profiling ([#6176][])
|
|
27
|
+
|
|
5
28
|
## [2.41.0] - 2026-08-13
|
|
6
29
|
|
|
7
30
|
### Added
|
|
@@ -3753,7 +3776,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
|
|
|
3753
3776
|
Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
3754
3777
|
|
|
3755
3778
|
|
|
3756
|
-
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.
|
|
3779
|
+
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.42.0...master
|
|
3780
|
+
[2.42.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.41.0...v2.42.0
|
|
3757
3781
|
[2.41.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.40.0...v2.41.0
|
|
3758
3782
|
[2.40.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.39.0...v2.40.0
|
|
3759
3783
|
[2.39.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.38.0...v2.39.0
|
|
@@ -5560,6 +5584,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5560
5584
|
[#6022]: https://github.com/DataDog/dd-trace-rb/issues/6022
|
|
5561
5585
|
[#6023]: https://github.com/DataDog/dd-trace-rb/issues/6023
|
|
5562
5586
|
[#6024]: https://github.com/DataDog/dd-trace-rb/issues/6024
|
|
5587
|
+
[#6043]: https://github.com/DataDog/dd-trace-rb/issues/6043
|
|
5563
5588
|
[#6047]: https://github.com/DataDog/dd-trace-rb/issues/6047
|
|
5564
5589
|
[#6050]: https://github.com/DataDog/dd-trace-rb/issues/6050
|
|
5565
5590
|
[#6056]: https://github.com/DataDog/dd-trace-rb/issues/6056
|
|
@@ -5568,14 +5593,23 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5568
5593
|
[#6072]: https://github.com/DataDog/dd-trace-rb/issues/6072
|
|
5569
5594
|
[#6097]: https://github.com/DataDog/dd-trace-rb/issues/6097
|
|
5570
5595
|
[#6104]: https://github.com/DataDog/dd-trace-rb/issues/6104
|
|
5596
|
+
[#6111]: https://github.com/DataDog/dd-trace-rb/issues/6111
|
|
5571
5597
|
[#6116]: https://github.com/DataDog/dd-trace-rb/issues/6116
|
|
5572
5598
|
[#6126]: https://github.com/DataDog/dd-trace-rb/issues/6126
|
|
5573
5599
|
[#6130]: https://github.com/DataDog/dd-trace-rb/issues/6130
|
|
5574
5600
|
[#6132]: https://github.com/DataDog/dd-trace-rb/issues/6132
|
|
5575
5601
|
[#6133]: https://github.com/DataDog/dd-trace-rb/issues/6133
|
|
5602
|
+
[#6135]: https://github.com/DataDog/dd-trace-rb/issues/6135
|
|
5576
5603
|
[#6138]: https://github.com/DataDog/dd-trace-rb/issues/6138
|
|
5577
5604
|
[#6142]: https://github.com/DataDog/dd-trace-rb/issues/6142
|
|
5578
5605
|
[#6173]: https://github.com/DataDog/dd-trace-rb/issues/6173
|
|
5606
|
+
[#6176]: https://github.com/DataDog/dd-trace-rb/issues/6176
|
|
5607
|
+
[#6213]: https://github.com/DataDog/dd-trace-rb/issues/6213
|
|
5608
|
+
[#6219]: https://github.com/DataDog/dd-trace-rb/issues/6219
|
|
5609
|
+
[#6220]: https://github.com/DataDog/dd-trace-rb/issues/6220
|
|
5610
|
+
[#6232]: https://github.com/DataDog/dd-trace-rb/issues/6232
|
|
5611
|
+
[#6234]: https://github.com/DataDog/dd-trace-rb/issues/6234
|
|
5612
|
+
[#6242]: https://github.com/DataDog/dd-trace-rb/issues/6242
|
|
5579
5613
|
[@AdrianLC]: https://github.com/AdrianLC
|
|
5580
5614
|
[@Azure7111]: https://github.com/Azure7111
|
|
5581
5615
|
[@BabyGroot]: https://github.com/BabyGroot
|
|
@@ -5731,4 +5765,4 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5731
5765
|
[@y-yagi]: https://github.com/y-yagi
|
|
5732
5766
|
[@yujideveloper]: https://github.com/yujideveloper
|
|
5733
5767
|
[@yukimurasawa]: https://github.com/yukimurasawa
|
|
5734
|
-
[@zachmccormick]: https://github.com/zachmccormick
|
|
5768
|
+
[@zachmccormick]: https://github.com/zachmccormick
|
|
@@ -65,38 +65,26 @@ Non-exhaustive list of APIs that cause exceptions to be raised:
|
|
|
65
65
|
To implement some of the features below, we sometimes require access to private Ruby header files (that describe VM
|
|
66
66
|
internal types, structures and functions).
|
|
67
67
|
|
|
68
|
-
Because these private header files are not included in regular Ruby installations, we
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
Because these private header files are not included in regular Ruby installations, we make use of the
|
|
69
|
+
[`datadog-ruby_core_source`](https://github.com/DataDog/datadog-ruby_core_source) gem, which contains almost no code
|
|
70
|
+
of its own; instead, it just contains per-Ruby-version folders with the private VM headers (`.h`) files for that
|
|
71
|
+
version. Thus, even though a regular Ruby installation does not include these files, we can access the copy inside
|
|
72
|
+
this gem.
|
|
72
73
|
|
|
73
74
|
Functions which make use of these headers are defined in the <private_vm_api_acccess.c> file.
|
|
74
75
|
|
|
75
|
-
There is currently no way for disabling usage of the private MJIT header for Ruby 2.6 to 3.2.
|
|
76
|
-
|
|
77
76
|
**Important Note**: Our medium/long-term plan is to stop relying on all private Ruby headers, and instead request and
|
|
78
77
|
contribute upstream changes so that they become official public VM APIs.
|
|
79
78
|
|
|
80
|
-
###
|
|
81
|
-
|
|
82
|
-
Ruby versions 2.6 to 3.2 shipped a JIT compiler called MJIT. This compiler does not directly generate machine code;
|
|
83
|
-
instead it generates C code and uses the system C compiler to turn it into machine code.
|
|
84
|
-
|
|
85
|
-
The generated C code `#include`s a private header -- which we call "the MJIT header".
|
|
86
|
-
The MJIT header gets shipped with all MJIT-enabled Rubies and includes the layout of many internal VM structures;
|
|
87
|
-
and of course the intention is that it is only used by the Ruby MJIT compiler.
|
|
88
|
-
|
|
89
|
-
This header is placed inside the `include/` directory in a Ruby installation, and is named for that specific Ruby
|
|
90
|
-
version. e.g. `rb_mjit_min_header-2.7.4.h`.
|
|
91
|
-
|
|
92
|
-
This header was removed in Ruby 3.3.
|
|
93
|
-
|
|
94
|
-
### Approach 2: Using the `datadog-ruby_core_source` gem
|
|
79
|
+
### Historical note: the Ruby private MJIT header
|
|
95
80
|
|
|
96
|
-
|
|
97
|
-
instead
|
|
81
|
+
Ruby versions 2.6 to 3.2 shipped a JIT compiler called MJIT. This compiler did not directly generate machine code;
|
|
82
|
+
instead it generated C code and used the system C compiler to turn it into machine code.
|
|
98
83
|
|
|
99
|
-
|
|
84
|
+
The generated C code `#include`d a private header -- which we called "the MJIT header". In the past we used to
|
|
85
|
+
rely on it for Ruby versions 2.6 to 3.2.
|
|
86
|
+
This header was removed in Ruby 3.3, and we have since moved all supported Ruby versions over to using the
|
|
87
|
+
`datadog-ruby_core_source` gem instead.
|
|
100
88
|
|
|
101
89
|
## Feature: Getting thread CPU-time clock_ids
|
|
102
90
|
|
|
@@ -68,14 +68,24 @@
|
|
|
68
68
|
// 4. The Ruby VM calls our `sample_from_postponed_job` from a thread holding the global VM lock. A sample is recorded by
|
|
69
69
|
// calling `thread_context_collector_sample`.
|
|
70
70
|
//
|
|
71
|
-
//
|
|
71
|
+
//
|
|
72
|
+
// ### Hooks and TracePoints
|
|
73
|
+
//
|
|
74
|
+
// This class uses various hooks:
|
|
75
|
+
// * A RUBY_INTERNAL_EVENT_GC_ENTER & RUBY_INTERNAL_EVENT_GC_EXIT TracePoint
|
|
76
|
+
// * A RUBY_INTERNAL_EVENT_NEWOBJ "event hook"/internal tracepoint
|
|
77
|
+
// * A GVL thread event hook
|
|
78
|
+
//
|
|
79
|
+
// We refer to those collectively as "hooks".
|
|
80
|
+
//
|
|
81
|
+
// ### Hooks and Forking
|
|
72
82
|
//
|
|
73
83
|
// When the Ruby VM forks, the CPU/Wall-time profiling stops naturally because it's triggered by a background thread
|
|
74
84
|
// that doesn't get automatically restarted by the VM on the child process. (The profiler does trigger its restart at
|
|
75
85
|
// some point -- see `Profiling::Tasks::Setup` for details).
|
|
76
86
|
//
|
|
77
|
-
// But this doesn't apply to any
|
|
78
|
-
// always remember consider this case of -- the worker thread may not be alive but the
|
|
87
|
+
// But this doesn't apply to any hooks this class may use, which will continue to be active. Thus, we need to
|
|
88
|
+
// always remember consider this case of -- the worker thread may not be alive but the hooks can continue to
|
|
79
89
|
// trigger samples.
|
|
80
90
|
//
|
|
81
91
|
// ---
|
|
@@ -91,7 +101,7 @@ unsigned int MAX_ALLOC_WEIGHT = 10000;
|
|
|
91
101
|
static rb_postponed_job_handle_t sample_from_postponed_job_handle;
|
|
92
102
|
static rb_postponed_job_handle_t after_gc_from_postponed_job_handle;
|
|
93
103
|
static rb_postponed_job_handle_t after_gvl_running_from_postponed_job_handle;
|
|
94
|
-
static rb_postponed_job_handle_t
|
|
104
|
+
static rb_postponed_job_handle_t commit_heap_recordings_from_postponed_job_may_lose_gvl_handle;
|
|
95
105
|
#endif
|
|
96
106
|
|
|
97
107
|
// Contains state for a single CpuAndWallTimeWorker instance
|
|
@@ -107,6 +117,8 @@ typedef struct {
|
|
|
107
117
|
bool skip_idle_samples_for_testing;
|
|
108
118
|
bool sighandler_sampling_enabled;
|
|
109
119
|
uint32_t cpu_sampling_interval_ms;
|
|
120
|
+
// Minimum duration of a "Waiting for GVL" period to trigger a sample
|
|
121
|
+
uint32_t waiting_for_gvl_threshold_ns;
|
|
110
122
|
VALUE self_instance;
|
|
111
123
|
VALUE thread_context_collector_instance;
|
|
112
124
|
VALUE idle_sampling_helper_instance;
|
|
@@ -127,9 +139,20 @@ typedef struct {
|
|
|
127
139
|
|
|
128
140
|
// Others
|
|
129
141
|
|
|
130
|
-
// Used to detect/avoid nested sampling,
|
|
131
|
-
//
|
|
132
|
-
// sampling.
|
|
142
|
+
// Used to detect/avoid nested sampling, and intended to behave as a lock to ensure the profiler doesn't recurse on itself,
|
|
143
|
+
// e.g. when on_newobj_event gets triggered by a memory allocation that happens during another sample, or when the
|
|
144
|
+
// signal handler gets triggered while we're already in the middle of sampling.
|
|
145
|
+
//
|
|
146
|
+
// It's not an actual lock because we rely on the GVL for correct synchronization
|
|
147
|
+
// (and thus this flag is only valid when we know we have the GVL).
|
|
148
|
+
//
|
|
149
|
+
// Similar to a lock, it should not be held across long-running operations,
|
|
150
|
+
// **in particular it MUST NEVER be held during operations where we might lose the GVL**
|
|
151
|
+
// because effectively that would stop the profiler from working until control
|
|
152
|
+
// goes back to that special thread, which on a contended Ruby app, can take hundreds of ms (or more).
|
|
153
|
+
//
|
|
154
|
+
// Because what we want is "profiler doesn't recurse on itself" we want this to behave as a non-reentrant lock
|
|
155
|
+
// (FIXME: We should have checks for this)
|
|
133
156
|
//
|
|
134
157
|
// @ivoanjo: Right now we always sample inside `safely_call`; if that ever changes, this flag may need to become
|
|
135
158
|
// volatile/atomic/have some barriers to ensure it's visible during e.g. signal handlers.
|
|
@@ -243,10 +266,10 @@ static void reset_stats_not_thread_safe(cpu_and_wall_time_worker_state *state);
|
|
|
243
266
|
static void sleep_for(uint64_t time_ns);
|
|
244
267
|
static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self);
|
|
245
268
|
static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *unused2);
|
|
246
|
-
static void
|
|
269
|
+
static void disable_hooks(cpu_and_wall_time_worker_state *state);
|
|
247
270
|
static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self);
|
|
248
271
|
static VALUE rescued_sample_allocation(VALUE tracepoint_data);
|
|
249
|
-
static VALUE
|
|
272
|
+
static VALUE rescued_commit_heap_recordings_may_lose_gvl(VALUE self_instance);
|
|
250
273
|
static void delayed_error(cpu_and_wall_time_worker_state *state, const char *error);
|
|
251
274
|
static void delayed_error_clock_failure(cpu_and_wall_time_worker_state *state);
|
|
252
275
|
static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg);
|
|
@@ -261,11 +284,12 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self);
|
|
|
261
284
|
static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE instance);
|
|
262
285
|
static VALUE handle_sampling_failure_rescued_sample_from_postponed_job(VALUE self_instance, VALUE exception);
|
|
263
286
|
static VALUE handle_sampling_failure_thread_context_collector_sample_after_gc(VALUE self_instance, VALUE exception);
|
|
287
|
+
static VALUE handle_sampling_failure_thread_context_collector_heap_update(VALUE self_instance, VALUE exception);
|
|
264
288
|
static VALUE handle_sampling_failure_rescued_sample_allocation(VALUE self_instance, VALUE exception);
|
|
265
|
-
static VALUE
|
|
289
|
+
static VALUE handle_sampling_failure_rescued_commit_heap_recordings(VALUE self_instance, VALUE exception);
|
|
266
290
|
static inline void during_sample_enter(cpu_and_wall_time_worker_state* state);
|
|
267
291
|
static inline void during_sample_exit(cpu_and_wall_time_worker_state* state);
|
|
268
|
-
static void
|
|
292
|
+
static void commit_heap_recordings_from_postponed_job_may_lose_gvl(DDTRACE_UNUSED void *_unused);
|
|
269
293
|
|
|
270
294
|
// We're using `on_newobj_event` function with `rb_add_event_hook2`, which requires in its public signature a function
|
|
271
295
|
// with signature `rb_event_hook_func_t` which doesn't match `on_newobj_event`.
|
|
@@ -322,13 +346,14 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
|
|
|
322
346
|
sample_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, sample_from_postponed_job, NULL);
|
|
323
347
|
after_gc_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_gc_from_postponed_job, NULL);
|
|
324
348
|
after_gvl_running_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_gvl_running_from_postponed_job, NULL);
|
|
325
|
-
|
|
349
|
+
commit_heap_recordings_from_postponed_job_may_lose_gvl_handle =
|
|
350
|
+
rb_postponed_job_preregister(unused_flags, commit_heap_recordings_from_postponed_job_may_lose_gvl, NULL);
|
|
326
351
|
|
|
327
352
|
if (
|
|
328
353
|
sample_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
|
|
329
354
|
after_gc_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
|
|
330
355
|
after_gvl_running_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
|
|
331
|
-
|
|
356
|
+
commit_heap_recordings_from_postponed_job_may_lose_gvl_handle == POSTPONED_JOB_HANDLE_INVALID
|
|
332
357
|
) {
|
|
333
358
|
raise_error(rb_eRuntimeError, "Failed to register profiler postponed jobs (got POSTPONED_JOB_HANDLE_INVALID)");
|
|
334
359
|
}
|
|
@@ -408,6 +433,7 @@ static VALUE _native_new(VALUE klass) {
|
|
|
408
433
|
state->skip_idle_samples_for_testing = false;
|
|
409
434
|
state->sighandler_sampling_enabled = false;
|
|
410
435
|
state->cpu_sampling_interval_ms = 10;
|
|
436
|
+
state->waiting_for_gvl_threshold_ns = 10 * 1000 * 1000;
|
|
411
437
|
state->thread_context_collector_instance = Qnil;
|
|
412
438
|
state->idle_sampling_helper_instance = Qnil;
|
|
413
439
|
state->owner_thread = Qnil;
|
|
@@ -453,6 +479,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
453
479
|
VALUE skip_idle_samples_for_testing = rb_hash_fetch(options, ID2SYM(rb_intern("skip_idle_samples_for_testing")));
|
|
454
480
|
VALUE sighandler_sampling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("sighandler_sampling_enabled")));
|
|
455
481
|
VALUE cpu_sampling_interval_ms = rb_hash_fetch(options, ID2SYM(rb_intern("cpu_sampling_interval_ms")));
|
|
482
|
+
VALUE waiting_for_gvl_threshold_ns = rb_hash_fetch(options, ID2SYM(rb_intern("waiting_for_gvl_threshold_ns")));
|
|
456
483
|
|
|
457
484
|
ENFORCE_BOOLEAN(gc_profiling_enabled);
|
|
458
485
|
ENFORCE_BOOLEAN(no_signals_workaround_enabled);
|
|
@@ -464,6 +491,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
464
491
|
ENFORCE_BOOLEAN(skip_idle_samples_for_testing)
|
|
465
492
|
ENFORCE_BOOLEAN(sighandler_sampling_enabled)
|
|
466
493
|
ENFORCE_TYPE(cpu_sampling_interval_ms, T_FIXNUM);
|
|
494
|
+
ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
|
|
467
495
|
|
|
468
496
|
cpu_and_wall_time_worker_state *state;
|
|
469
497
|
TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
@@ -477,6 +505,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
477
505
|
state->skip_idle_samples_for_testing = (skip_idle_samples_for_testing == Qtrue);
|
|
478
506
|
state->sighandler_sampling_enabled = (sighandler_sampling_enabled == Qtrue);
|
|
479
507
|
state->cpu_sampling_interval_ms = NUM2INT(cpu_sampling_interval_ms);
|
|
508
|
+
state->waiting_for_gvl_threshold_ns = NUM2UINT(waiting_for_gvl_threshold_ns);
|
|
480
509
|
|
|
481
510
|
double total_overhead_target_percentage = NUM2DBL(dynamic_sampling_rate_overhead_target_percentage);
|
|
482
511
|
if (!state->allocation_profiling_enabled) {
|
|
@@ -515,7 +544,7 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
|
|
|
515
544
|
|
|
516
545
|
// If we already got a delayed exception registered even before starting, raise before starting
|
|
517
546
|
if (state->failure_exception != Qnil) {
|
|
518
|
-
|
|
547
|
+
disable_hooks(state);
|
|
519
548
|
rb_exc_raise(state->failure_exception);
|
|
520
549
|
}
|
|
521
550
|
|
|
@@ -525,13 +554,13 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
|
|
|
525
554
|
raise_error(rb_eRuntimeError, "Could not start CpuAndWallTimeWorker: There's already another instance of CpuAndWallTimeWorker active in a different thread");
|
|
526
555
|
} else {
|
|
527
556
|
// The previously active thread seems to have died without cleaning up after itself.
|
|
528
|
-
// In this case, we can still go ahead and start the profiler BUT we make sure to disable any existing
|
|
557
|
+
// In this case, we can still go ahead and start the profiler BUT we make sure to disable any existing hooks
|
|
529
558
|
// first as:
|
|
530
|
-
// a) If this is a new instance of the CpuAndWallTimeWorker, we don't want the
|
|
559
|
+
// a) If this is a new instance of the CpuAndWallTimeWorker, we don't want the hooks from the old instance
|
|
531
560
|
// being kept around
|
|
532
561
|
// b) If this is the same instance of the CpuAndWallTimeWorker if we call enable on a tracepoint that is already
|
|
533
562
|
// enabled, it will start firing more than once, see https://bugs.ruby-lang.org/issues/19114 for details.
|
|
534
|
-
|
|
563
|
+
disable_hooks(old_state);
|
|
535
564
|
}
|
|
536
565
|
}
|
|
537
566
|
|
|
@@ -549,7 +578,7 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
|
|
|
549
578
|
// Reset per-thread state, if any. This ensures there's no leftover state from a previous profiler run that would
|
|
550
579
|
// affect or be included in samples taken by this profiler about to run.
|
|
551
580
|
//
|
|
552
|
-
// NOTE: This needs to be called before we enable any
|
|
581
|
+
// NOTE: This needs to be called before we enable any hooks or anything that could trigger samples (e.g.
|
|
553
582
|
// reset cannot be concurrent with any sampling activity)
|
|
554
583
|
thread_context_collector_reset_all_per_thread_contexts(state->thread_context_collector_instance);
|
|
555
584
|
|
|
@@ -576,7 +605,7 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
|
|
|
576
605
|
|
|
577
606
|
// The sample trigger loop finished (either cleanly or with an error); let's clean up
|
|
578
607
|
|
|
579
|
-
|
|
608
|
+
disable_hooks(state);
|
|
580
609
|
|
|
581
610
|
active_sampler_instance_state = NULL;
|
|
582
611
|
active_sampler_instance = Qnil;
|
|
@@ -636,8 +665,8 @@ static void stop_state(cpu_and_wall_time_worker_state *state, VALUE optional_exc
|
|
|
636
665
|
state->failure_exception = optional_exception;
|
|
637
666
|
state->failure_exception_during_operation = optional_exception_during_operation;
|
|
638
667
|
|
|
639
|
-
// Disable the
|
|
640
|
-
|
|
668
|
+
// Disable the hooks as soon as possible, so the VM doesn't keep on calling them
|
|
669
|
+
disable_hooks(state);
|
|
641
670
|
}
|
|
642
671
|
|
|
643
672
|
static VALUE stop(VALUE self_instance, VALUE optional_exception, const char *optional_exception_during_operation) {
|
|
@@ -848,7 +877,7 @@ static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
|
848
877
|
during_sample_enter(state);
|
|
849
878
|
|
|
850
879
|
// Rescue against any exceptions that happen during sampling
|
|
851
|
-
safely_call(
|
|
880
|
+
VALUE needs_otel_span_key = safely_call(
|
|
852
881
|
rescued_sample_from_postponed_job,
|
|
853
882
|
state->self_instance,
|
|
854
883
|
state->self_instance,
|
|
@@ -856,6 +885,12 @@ static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
|
856
885
|
);
|
|
857
886
|
|
|
858
887
|
during_sample_exit(state);
|
|
888
|
+
|
|
889
|
+
// Extracting the otel span key can lose the GVL, so we move it outside `during_sample`
|
|
890
|
+
// (It can't raise: it rescues its own exceptions)
|
|
891
|
+
if (needs_otel_span_key == Qtrue) {
|
|
892
|
+
thread_context_collector_resolve_otel_span_key_may_lose_gvl(state->thread_context_collector_instance);
|
|
893
|
+
}
|
|
859
894
|
}
|
|
860
895
|
|
|
861
896
|
static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
|
|
@@ -866,12 +901,13 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
|
|
|
866
901
|
|
|
867
902
|
if (state->dynamic_sampling_rate_enabled && !dynamic_sampling_rate_should_sample(&state->cpu_dynamic_sampling_rate, wall_time_ns_before_sample)) {
|
|
868
903
|
state->stats.cpu_skipped++;
|
|
869
|
-
return
|
|
904
|
+
return Qfalse;
|
|
870
905
|
}
|
|
871
906
|
|
|
872
907
|
state->stats.cpu_sampled++;
|
|
873
908
|
|
|
874
|
-
|
|
909
|
+
bool needs_otel_span_key =
|
|
910
|
+
thread_context_collector_sample(state->thread_context_collector_instance, wall_time_ns_before_sample);
|
|
875
911
|
|
|
876
912
|
long wall_time_ns_after_sample = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
877
913
|
long delta_ns = wall_time_ns_after_sample - wall_time_ns_before_sample;
|
|
@@ -885,8 +921,7 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
|
|
|
885
921
|
|
|
886
922
|
dynamic_sampling_rate_after_sample(&state->cpu_dynamic_sampling_rate, wall_time_ns_after_sample, sampling_time_ns);
|
|
887
923
|
|
|
888
|
-
|
|
889
|
-
return Qnil;
|
|
924
|
+
return needs_otel_span_key ? Qtrue : Qfalse;
|
|
890
925
|
}
|
|
891
926
|
|
|
892
927
|
// This method exists only to enable testing Datadog::Profiling::Collectors::CpuAndWallTimeWorker behavior using RSpec.
|
|
@@ -912,7 +947,7 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
|
|
|
912
947
|
cpu_and_wall_time_worker_state *state;
|
|
913
948
|
TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
914
949
|
|
|
915
|
-
// Final preparations: Setup signal handler and enable
|
|
950
|
+
// Final preparations: Setup signal handler and enable hooks. We run these here and not in `_native_sampling_loop`
|
|
916
951
|
// because they may raise exceptions.
|
|
917
952
|
install_sigprof_signal_handler(handle_sampling_signal, "handle_sampling_signal");
|
|
918
953
|
if (state->gc_profiling_enabled) rb_tracepoint_enable(state->gc_tracepoint);
|
|
@@ -1095,6 +1130,15 @@ static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
|
1095
1130
|
);
|
|
1096
1131
|
|
|
1097
1132
|
during_sample_exit(state);
|
|
1133
|
+
|
|
1134
|
+
// This part runs separately from above because it may lose the GVL and we don't want `during_sample` to be set in
|
|
1135
|
+
// such a situation
|
|
1136
|
+
safely_call(
|
|
1137
|
+
thread_context_collector_heap_update_may_lose_gvl,
|
|
1138
|
+
state->thread_context_collector_instance,
|
|
1139
|
+
state->self_instance,
|
|
1140
|
+
handle_sampling_failure_thread_context_collector_heap_update
|
|
1141
|
+
);
|
|
1098
1142
|
}
|
|
1099
1143
|
|
|
1100
1144
|
// Equivalent to Ruby begin/rescue call, where we call a C function and jump to the exception handler if an
|
|
@@ -1132,12 +1176,12 @@ static VALUE _native_simulate_sample_from_postponed_job(DDTRACE_UNUSED VALUE sel
|
|
|
1132
1176
|
|
|
1133
1177
|
// After the Ruby VM forks, this method gets called in the child process to clean up any leftover state from the parent.
|
|
1134
1178
|
//
|
|
1135
|
-
// Assumption: This method gets called BEFORE restarting profiling. Note that profiling-related
|
|
1179
|
+
// Assumption: This method gets called BEFORE restarting profiling. Note that profiling-related hooks may still
|
|
1136
1180
|
// be active, so we make sure to disable them before calling into anything else, so that there are no components
|
|
1137
1181
|
// attempting to trigger samples at the same time as the reset is done.
|
|
1138
1182
|
//
|
|
1139
|
-
// In the future, if we add more other components with
|
|
1140
|
-
//
|
|
1183
|
+
// In the future, if we add more other components with hooks, we will need to coordinate stopping all such
|
|
1184
|
+
// hooks before doing the other cleaning steps.
|
|
1141
1185
|
//
|
|
1142
1186
|
// Note that tests call this method directly in the same process without forking,
|
|
1143
1187
|
// and in such a case non-current Threads keep running.
|
|
@@ -1145,8 +1189,8 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE instance)
|
|
|
1145
1189
|
cpu_and_wall_time_worker_state *state;
|
|
1146
1190
|
TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
1147
1191
|
|
|
1148
|
-
// Disable all
|
|
1149
|
-
|
|
1192
|
+
// Disable all hooks, so that there are no more attempts to mutate the profile
|
|
1193
|
+
disable_hooks(state);
|
|
1150
1194
|
|
|
1151
1195
|
reset_stats_not_thread_safe(state);
|
|
1152
1196
|
|
|
@@ -1301,6 +1345,9 @@ static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self) {
|
|
|
1301
1345
|
// Implements memory-related profiling events. This function is called by Ruby via the `rb_add_event_hook2`
|
|
1302
1346
|
// when the RUBY_INTERNAL_EVENT_NEWOBJ event is triggered.
|
|
1303
1347
|
//
|
|
1348
|
+
// This function is called from the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
|
|
1349
|
+
// Ruby heap nor release the GVL (https://github.com/DataDog/dd-trace-rb/pull/4240).
|
|
1350
|
+
//
|
|
1304
1351
|
// When allocation sampling is enabled, this function gets called for almost all* objects allocated by the Ruby VM.
|
|
1305
1352
|
// (*In some weird cases the VM may skip this tracepoint.)
|
|
1306
1353
|
//
|
|
@@ -1411,7 +1458,7 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
|
|
|
1411
1458
|
during_sample_exit(state);
|
|
1412
1459
|
}
|
|
1413
1460
|
|
|
1414
|
-
static void
|
|
1461
|
+
static void disable_hooks(cpu_and_wall_time_worker_state *state) {
|
|
1415
1462
|
if (state->gc_tracepoint != Qnil) {
|
|
1416
1463
|
rb_tracepoint_disable(state->gc_tracepoint);
|
|
1417
1464
|
}
|
|
@@ -1459,18 +1506,18 @@ static VALUE rescued_sample_allocation(VALUE arg) {
|
|
|
1459
1506
|
// To control bias from sampling, we clamp the maximum weight attributed to a single allocation sample. This avoids
|
|
1460
1507
|
// assigning a very large number to a sample, if for instance the dynamic sampling mechanism chose a really big interval.
|
|
1461
1508
|
unsigned int weight = allocations_since_last_sample > MAX_ALLOC_WEIGHT ? MAX_ALLOC_WEIGHT : (unsigned int) allocations_since_last_sample;
|
|
1462
|
-
bool
|
|
1509
|
+
bool needs_commit = thread_context_collector_sample_allocation(state->thread_context_collector_instance, thread_context, weight, new_object);
|
|
1463
1510
|
// ...but we still represent the skipped samples in the profile, thus the data will account for all allocations.
|
|
1464
1511
|
if (weight < allocations_since_last_sample) {
|
|
1465
1512
|
uint32_t skipped_samples = (uint32_t) uint64_min_of(allocations_since_last_sample - weight, UINT32_MAX);
|
|
1466
1513
|
thread_context_collector_sample_skipped_allocation_samples(state->thread_context_collector_instance, skipped_samples);
|
|
1467
1514
|
}
|
|
1468
1515
|
|
|
1469
|
-
if (
|
|
1516
|
+
if (needs_commit) {
|
|
1470
1517
|
#ifndef NO_POSTPONED_TRIGGER
|
|
1471
|
-
rb_postponed_job_trigger(
|
|
1518
|
+
rb_postponed_job_trigger(commit_heap_recordings_from_postponed_job_may_lose_gvl_handle);
|
|
1472
1519
|
#else
|
|
1473
|
-
|
|
1520
|
+
rb_postponed_job_register_one(0, commit_heap_recordings_from_postponed_job_may_lose_gvl, NULL);
|
|
1474
1521
|
#endif
|
|
1475
1522
|
}
|
|
1476
1523
|
|
|
@@ -1545,10 +1592,11 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1545
1592
|
} else if (event_id == RUBY_INTERNAL_THREAD_EVENT_READY) { /* waiting for gvl */
|
|
1546
1593
|
thread_context_collector_on_gvl_waiting(thread_context);
|
|
1547
1594
|
} else if (event_id == RUBY_INTERNAL_THREAD_EVENT_RESUMED) { /* running/runnable */
|
|
1548
|
-
//
|
|
1549
|
-
//
|
|
1550
|
-
// However,
|
|
1551
|
-
//
|
|
1595
|
+
// We must only use async-signal-safe functions here and not call arbitrary Ruby APIs and not allocate!
|
|
1596
|
+
// One might assume RUBY_INTERNAL_THREAD_EVENT_RESUMED means having the GVL and running that thread.
|
|
1597
|
+
// However, the reality is more complicated (https://bugs.ruby-lang.org/issues/22098),
|
|
1598
|
+
// it only "sort of" has the GVL but not fully, and it's called while holding the scheduler lock,
|
|
1599
|
+
// so we do as little work as possible here, and perform the sample in a postponed_job.
|
|
1552
1600
|
cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
|
|
1553
1601
|
if (state == NULL) return; // This should not happen, but just in case...
|
|
1554
1602
|
|
|
@@ -1560,7 +1608,8 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1560
1608
|
// that next.
|
|
1561
1609
|
during_sample_enter(state);
|
|
1562
1610
|
|
|
1563
|
-
on_gvl_running_result result =
|
|
1611
|
+
on_gvl_running_result result =
|
|
1612
|
+
thread_context_collector_on_gvl_running(target_thread, thread_context, state->waiting_for_gvl_threshold_ns);
|
|
1564
1613
|
|
|
1565
1614
|
during_sample_exit(state);
|
|
1566
1615
|
|
|
@@ -1650,54 +1699,54 @@ static VALUE handle_sampling_failure_thread_context_collector_sample_after_gc(VA
|
|
|
1650
1699
|
return Qnil;
|
|
1651
1700
|
}
|
|
1652
1701
|
|
|
1702
|
+
static VALUE handle_sampling_failure_thread_context_collector_heap_update(VALUE self_instance, VALUE exception) {
|
|
1703
|
+
stop(self_instance, exception, "thread_context_collector_heap_update_may_lose_gvl");
|
|
1704
|
+
return Qnil;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1653
1707
|
static VALUE handle_sampling_failure_rescued_sample_allocation(VALUE self_instance, VALUE exception) {
|
|
1654
1708
|
stop(self_instance, exception, "rescued_sample_allocation");
|
|
1655
1709
|
return Qnil;
|
|
1656
1710
|
}
|
|
1657
1711
|
|
|
1658
|
-
static VALUE
|
|
1659
|
-
stop(self_instance, exception, "
|
|
1712
|
+
static VALUE handle_sampling_failure_rescued_commit_heap_recordings(VALUE self_instance, VALUE exception) {
|
|
1713
|
+
stop(self_instance, exception, "rescued_commit_heap_recordings_may_lose_gvl");
|
|
1660
1714
|
return Qnil;
|
|
1661
1715
|
}
|
|
1662
1716
|
|
|
1663
|
-
static VALUE
|
|
1717
|
+
static VALUE rescued_commit_heap_recordings_may_lose_gvl(VALUE self_instance) {
|
|
1664
1718
|
cpu_and_wall_time_worker_state *state;
|
|
1665
1719
|
TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
1666
1720
|
|
|
1667
|
-
|
|
1721
|
+
thread_context_collector_commit_heap_recordings_may_lose_gvl(state->thread_context_collector_instance);
|
|
1668
1722
|
|
|
1669
1723
|
// Return a dummy VALUE because we're called from rb_rescue2 which requires it
|
|
1670
1724
|
return Qnil;
|
|
1671
1725
|
}
|
|
1672
1726
|
|
|
1673
|
-
// This postponed job callback is used to
|
|
1674
|
-
// During on_newobj_event,
|
|
1675
|
-
//
|
|
1676
|
-
|
|
1677
|
-
#pragma GCC diagnostic ignored "-Wunused-function" // This is only used for some Rubies, but we want to build on all to make it easier to dev
|
|
1678
|
-
static void after_allocation_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
1727
|
+
// This postponed job callback is used to commit heap allocation recordings.
|
|
1728
|
+
// During on_newobj_event, we can't take the weak reference the heap recorder needs to track the object, so we defer
|
|
1729
|
+
// that until after the event completes.
|
|
1730
|
+
static void commit_heap_recordings_from_postponed_job_may_lose_gvl(DDTRACE_UNUSED void *_unused) {
|
|
1679
1731
|
cpu_and_wall_time_worker_state *state = active_sampler_instance_state;
|
|
1680
1732
|
|
|
1681
1733
|
if (state == NULL || !ddtrace_rb_ractor_main_p()) return;
|
|
1682
1734
|
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1735
|
+
if (state->during_sample) {
|
|
1736
|
+
delayed_error(state, "commit_heap_recordings_from_postponed_job_may_lose_gvl called during_sample");
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1687
1739
|
|
|
1688
1740
|
// NOTE: We're not updating the allocation_sampler here.
|
|
1689
1741
|
// This means work done in this function isn't accounted for as profiler overhead.
|
|
1690
1742
|
// This is acceptable as the amount of work done here is expected to be small.
|
|
1691
1743
|
safely_call(
|
|
1692
|
-
|
|
1744
|
+
rescued_commit_heap_recordings_may_lose_gvl,
|
|
1693
1745
|
state->self_instance,
|
|
1694
1746
|
state->self_instance,
|
|
1695
|
-
|
|
1747
|
+
handle_sampling_failure_rescued_commit_heap_recordings
|
|
1696
1748
|
);
|
|
1697
|
-
|
|
1698
|
-
during_sample_exit(state);
|
|
1699
1749
|
}
|
|
1700
|
-
#pragma GCC diagnostic pop
|
|
1701
1750
|
|
|
1702
1751
|
static inline void during_sample_enter(cpu_and_wall_time_worker_state* state) {
|
|
1703
1752
|
// Tell the compiler it's not allowed to reorder the `during_sample` flag with anything that happens after.
|
|
@@ -130,8 +130,16 @@ static VALUE _native_sample(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
|
|
130
130
|
ENFORCE_BOOLEAN(show_classes);
|
|
131
131
|
|
|
132
132
|
VALUE zero = INT2NUM(0);
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
// Pass `heap_sample: {new_object: ..., alloc_class: ...}` to also record this sample for heap profiling
|
|
134
|
+
VALUE heap_sample_options = rb_hash_lookup2(options, ID2SYM(rb_intern("heap_sample")), Qnil);
|
|
135
|
+
heap_sample_values heap_sample = {};
|
|
136
|
+
if (heap_sample_options != Qnil) {
|
|
137
|
+
ENFORCE_TYPE(heap_sample_options, T_HASH);
|
|
138
|
+
heap_sample.new_object = rb_hash_fetch(heap_sample_options, ID2SYM(rb_intern("new_object")));
|
|
139
|
+
heap_sample.alloc_class =
|
|
140
|
+
char_slice_from_ruby_string(rb_hash_fetch(heap_sample_options, ID2SYM(rb_intern("alloc_class"))));
|
|
141
|
+
}
|
|
142
|
+
|
|
135
143
|
sample_values values = {
|
|
136
144
|
.cpu_time_ns = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("cpu-time"), zero)),
|
|
137
145
|
.cpu_or_wall_samples = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("cpu-samples"), zero)),
|
|
@@ -139,7 +147,7 @@ static VALUE _native_sample(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
|
|
139
147
|
.alloc_samples = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("alloc-samples"), zero)),
|
|
140
148
|
.alloc_samples_unscaled = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("alloc-samples-unscaled"), zero)),
|
|
141
149
|
.timeline_wall_time_ns = NUM2UINT(rb_hash_lookup2(metric_values_hash, rb_str_new_cstr("timeline"), zero)),
|
|
142
|
-
.heap_sample = heap_sample
|
|
150
|
+
.heap_sample = heap_sample_options != Qnil ? &heap_sample : NULL,
|
|
143
151
|
};
|
|
144
152
|
|
|
145
153
|
long labels_count = RARRAY_LEN(labels_array) + RARRAY_LEN(numeric_labels_array);
|