datadog 2.41.0 → 2.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +57 -1
- data/README.md +3 -0
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +120 -61
- data/ext/datadog_profiling_native_extension/collectors_stack.c +11 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +156 -108
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +7 -3
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -10
- data/ext/datadog_profiling_native_extension/extconf.rb +52 -93
- data/ext/datadog_profiling_native_extension/heap_recorder.c +351 -255
- data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -24
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +518 -442
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +15 -3
- data/ext/datadog_profiling_native_extension/profiling.c +2 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -79
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +0 -7
- data/ext/datadog_profiling_native_extension/stack_recorder.c +106 -61
- data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +2 -1
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -10
- data/ext/libdatadog_api/di.c +10 -0
- data/ext/libdatadog_api/extconf.rb +3 -0
- data/ext/libdatadog_api/init.c +2 -0
- data/ext/libdatadog_api/otel_thread_context.c +247 -0
- data/ext/libdatadog_api/otel_thread_context.h +5 -0
- data/ext/libdatadog_api/trace_exporter.c +642 -22
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/assets/blocked.html +1 -108
- data/lib/datadog/core/configuration/components.rb +1 -0
- data/lib/datadog/core/crashtracking/component.rb +5 -1
- data/lib/datadog/data_streams/pathway_context.rb +20 -22
- data/lib/datadog/data_streams/processor.rb +31 -0
- data/lib/datadog/di/instrumenter.rb +41 -1
- data/lib/datadog/di/logger.rb +2 -2
- data/lib/datadog/di/probe.rb +9 -1
- data/lib/datadog/di/probe_notification_builder.rb +1 -0
- data/lib/datadog/di/remote.rb +3 -3
- data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
- data/lib/datadog/open_feature/exposures/event.rb +10 -3
- data/lib/datadog/open_feature/ext.rb +19 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
- data/lib/datadog/open_feature/native_evaluator.rb +33 -6
- data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
- data/lib/datadog/open_feature/provider.rb +11 -2
- data/lib/datadog/open_feature/transport.rb +5 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
- data/lib/datadog/opentelemetry/trace.rb +3 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -0
- data/lib/datadog/profiling/collectors/info.rb +2 -6
- data/lib/datadog/profiling/collectors/thread_context.rb +0 -4
- data/lib/datadog/profiling/component.rb +8 -16
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
- data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
- data/lib/datadog/tracing/contrib/sequel/utils.rb +49 -62
- data/lib/datadog/tracing/distributed/baggage.rb +0 -1
- data/lib/datadog/tracing/distributed/datadog.rb +3 -3
- data/lib/datadog/tracing/distributed/propagation.rb +3 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
- data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
- data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
- data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
- data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
- data/lib/datadog/tracing/otel_thread_context.rb +34 -0
- data/lib/datadog/tracing/remote.rb +195 -27
- data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
- data/lib/datadog/tracing/trace_digest.rb +22 -4
- data/lib/datadog/tracing/trace_operation.rb +22 -10
- data/lib/datadog/tracing/tracer.rb +5 -5
- data/lib/datadog/tracing/transport/native.rb +17 -25
- data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
- data/lib/datadog/tracing/transport/traces.rb +2 -22
- data/lib/datadog/version.rb +1 -1
- metadata +16 -8
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 154c126111f9b92cc59626264fc83b954b36317f96798bc33a0bcb7784516968
|
|
4
|
+
data.tar.gz: 4098225f4c88a0c3e31b069ca1a17231af4298ca149ce530d0dc4ccc9a8d8db5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a566b1ab03efdf8c881ff7a6af93aff5fc946dc213ea1ccfabc554458ae733769ab98002ea14f039e3b2a19c150b2e208083e35fd0519f34b77ff239247412dd
|
|
7
|
+
data.tar.gz: 3a9f8574b7325c9f6e77da1a7f8ad65b8b6a74426b9dab829db84c8adbe8757c1f09d57d8232085c51d074c60e4a77e017cbdd9bfaa5d9f84efba0ad66869866
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.43.0] - 2026-09-22
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
* Core: Add an experimental `libdatadog`-backed native trace transport with full feature parity. Opt in with `DD_EXPERIMENTAL_NATIVE_TRANSPORT_ENABLED=true`. ([#5971][])
|
|
10
|
+
* SSI: Support `force_ruby_platform` during Ruby dependency resolution. ([#6316][])
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
* SSI: Respect transitive application dependencies during Ruby injection. ([#6316][])
|
|
15
|
+
* SSI: Preserve checksums in injected Ruby lockfiles. ([#6320][])
|
|
16
|
+
* SSI: Preserve Bundler selection and `RUBYOPT` options during injection. ([#6316][])
|
|
17
|
+
* SSI: Support versioned and symlinked Bundler launchers. ([#6316][])
|
|
18
|
+
* SSI: Restore Ruby injection for deployment and vendored bundles. ([#6316][])
|
|
19
|
+
* Profiling: Fix a potential sampling race when Ruby is idle. ([#6286][])
|
|
20
|
+
* Profiling: Fix profiler not starting on Ruby 4 due to `undefined symbol: rb_current_box`. ([#6350][])
|
|
21
|
+
|
|
22
|
+
## [2.42.0] - 2026-08-31
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
* Tracing: Add sampling probability compatibility with OTel-instrumented services ([#6111][])
|
|
27
|
+
* Dynamic Instrumentation: Honor organization- and environment-level remote enablement for Ruby services. ([#6234][])
|
|
28
|
+
* Dynamic Instrumentation: Add per-process runtime id for snapshots to distinguish process restarts within a container ([#6232][])
|
|
29
|
+
* Open Feature: OpenFeature provider sends the serial ID of the assigned split on exposure events ([#6220][])
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
* Profiling: Drop usage of MJIT headers for Ruby 2.6 to 3.1 ([#6135][])
|
|
34
|
+
* Profiling: Re-enable heap live size profiling for Ruby 4+ ([#6176][])
|
|
35
|
+
* Dynamic Instrumentation: Enforce process-wide rate limit across all probes ([#6043][])
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
* Core: Ignore `SignalException` from crashtracker as unhandled exception errors ([#6219][])
|
|
40
|
+
* Tracing: Fix missing peer tags for database queries traced through `ActiveRecord` ([#6213][])
|
|
41
|
+
* Profiling: Fix bug in `sample_after_gc` leading to profiler stopping ([#6242][])
|
|
42
|
+
* Profiling: Improve stability of heap profiling by using `ObjectSpace::WeakMap` instead of `_id2ref` ([#6176][])
|
|
43
|
+
* Profiling: VM-internal objects are no longer sampled for heap profiling ([#6176][])
|
|
44
|
+
|
|
5
45
|
## [2.41.0] - 2026-08-13
|
|
6
46
|
|
|
7
47
|
### Added
|
|
@@ -3753,7 +3793,9 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
|
|
|
3753
3793
|
Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
3754
3794
|
|
|
3755
3795
|
|
|
3756
|
-
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.
|
|
3796
|
+
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.43.0...master
|
|
3797
|
+
[2.43.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.42.0...v2.43.0
|
|
3798
|
+
[2.42.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.41.0...v2.42.0
|
|
3757
3799
|
[2.41.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.40.0...v2.41.0
|
|
3758
3800
|
[2.40.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.39.0...v2.40.0
|
|
3759
3801
|
[2.39.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.38.0...v2.39.0
|
|
@@ -5560,6 +5602,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5560
5602
|
[#6022]: https://github.com/DataDog/dd-trace-rb/issues/6022
|
|
5561
5603
|
[#6023]: https://github.com/DataDog/dd-trace-rb/issues/6023
|
|
5562
5604
|
[#6024]: https://github.com/DataDog/dd-trace-rb/issues/6024
|
|
5605
|
+
[#6043]: https://github.com/DataDog/dd-trace-rb/issues/6043
|
|
5563
5606
|
[#6047]: https://github.com/DataDog/dd-trace-rb/issues/6047
|
|
5564
5607
|
[#6050]: https://github.com/DataDog/dd-trace-rb/issues/6050
|
|
5565
5608
|
[#6056]: https://github.com/DataDog/dd-trace-rb/issues/6056
|
|
@@ -5568,14 +5611,27 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5568
5611
|
[#6072]: https://github.com/DataDog/dd-trace-rb/issues/6072
|
|
5569
5612
|
[#6097]: https://github.com/DataDog/dd-trace-rb/issues/6097
|
|
5570
5613
|
[#6104]: https://github.com/DataDog/dd-trace-rb/issues/6104
|
|
5614
|
+
[#6111]: https://github.com/DataDog/dd-trace-rb/issues/6111
|
|
5571
5615
|
[#6116]: https://github.com/DataDog/dd-trace-rb/issues/6116
|
|
5572
5616
|
[#6126]: https://github.com/DataDog/dd-trace-rb/issues/6126
|
|
5573
5617
|
[#6130]: https://github.com/DataDog/dd-trace-rb/issues/6130
|
|
5574
5618
|
[#6132]: https://github.com/DataDog/dd-trace-rb/issues/6132
|
|
5575
5619
|
[#6133]: https://github.com/DataDog/dd-trace-rb/issues/6133
|
|
5620
|
+
[#6135]: https://github.com/DataDog/dd-trace-rb/issues/6135
|
|
5576
5621
|
[#6138]: https://github.com/DataDog/dd-trace-rb/issues/6138
|
|
5577
5622
|
[#6142]: https://github.com/DataDog/dd-trace-rb/issues/6142
|
|
5578
5623
|
[#6173]: https://github.com/DataDog/dd-trace-rb/issues/6173
|
|
5624
|
+
[#6176]: https://github.com/DataDog/dd-trace-rb/issues/6176
|
|
5625
|
+
[#6213]: https://github.com/DataDog/dd-trace-rb/issues/6213
|
|
5626
|
+
[#6219]: https://github.com/DataDog/dd-trace-rb/issues/6219
|
|
5627
|
+
[#6220]: https://github.com/DataDog/dd-trace-rb/issues/6220
|
|
5628
|
+
[#6232]: https://github.com/DataDog/dd-trace-rb/issues/6232
|
|
5629
|
+
[#6234]: https://github.com/DataDog/dd-trace-rb/issues/6234
|
|
5630
|
+
[#6242]: https://github.com/DataDog/dd-trace-rb/issues/6242
|
|
5631
|
+
[#6286]: https://github.com/DataDog/dd-trace-rb/issues/6286
|
|
5632
|
+
[#6316]: https://github.com/DataDog/dd-trace-rb/issues/6316
|
|
5633
|
+
[#6320]: https://github.com/DataDog/dd-trace-rb/issues/6320
|
|
5634
|
+
[#6350]: https://github.com/DataDog/dd-trace-rb/issues/6350
|
|
5579
5635
|
[@AdrianLC]: https://github.com/AdrianLC
|
|
5580
5636
|
[@Azure7111]: https://github.com/Azure7111
|
|
5581
5637
|
[@BabyGroot]: https://github.com/BabyGroot
|
data/README.md
CHANGED
|
@@ -17,8 +17,11 @@ For descriptions of terminology used in APM, take a look at the [APM Terms and C
|
|
|
17
17
|
|
|
18
18
|
For contributing, checkout the [contribution guidelines][contribution docs] and [development guide][development docs].
|
|
19
19
|
|
|
20
|
+
To check that a downloaded gem file was published by this repository, see [how to verify gem provenance][provenance docs].
|
|
21
|
+
|
|
20
22
|
[public docs]: https://docs.datadoghq.com/tracing/setup/ruby/
|
|
21
23
|
[api docs]: https://datadog.github.io/dd-trace-rb/
|
|
22
24
|
[APM glossary]: https://docs.datadoghq.com/tracing/glossary/
|
|
23
25
|
[contribution docs]: https://github.com/DataDog/dd-trace-rb/blob/master/CONTRIBUTING.md
|
|
24
26
|
[development docs]: https://github.com/DataDog/dd-trace-rb/blob/master/docs/DevelopmentGuide.md
|
|
27
|
+
[provenance docs]: https://github.com/DataDog/dd-trace-rb/blob/master/docs/Provenance.md
|
|
@@ -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) {
|
|
@@ -711,6 +740,8 @@ static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED si
|
|
|
711
740
|
// a) we get triggered using SIGPROF, and the docs state a second SIGPROF will not interrupt an existing one (see sigaction docs on sa_mask)
|
|
712
741
|
// b) we validate we are in the thread that has the global VM lock; if a different thread gets a signal, it will return early
|
|
713
742
|
// because it will not have the global VM lock
|
|
743
|
+
// c) `simulate_sampling_signal_delivery` calls us directly instead of via a real SIGPROF, and blocks SIGPROF delivery on its
|
|
744
|
+
// thread for the duration of that call, so it can't be nested into by a real SIGPROF either
|
|
714
745
|
|
|
715
746
|
state->stats.signal_handler_enqueued_sample++;
|
|
716
747
|
|
|
@@ -848,7 +879,7 @@ static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
|
848
879
|
during_sample_enter(state);
|
|
849
880
|
|
|
850
881
|
// Rescue against any exceptions that happen during sampling
|
|
851
|
-
safely_call(
|
|
882
|
+
VALUE needs_otel_span_key = safely_call(
|
|
852
883
|
rescued_sample_from_postponed_job,
|
|
853
884
|
state->self_instance,
|
|
854
885
|
state->self_instance,
|
|
@@ -856,6 +887,12 @@ static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
|
856
887
|
);
|
|
857
888
|
|
|
858
889
|
during_sample_exit(state);
|
|
890
|
+
|
|
891
|
+
// Extracting the otel span key can lose the GVL, so we move it outside `during_sample`
|
|
892
|
+
// (It can't raise: it rescues its own exceptions)
|
|
893
|
+
if (needs_otel_span_key == Qtrue) {
|
|
894
|
+
thread_context_collector_resolve_otel_span_key_may_lose_gvl(state->thread_context_collector_instance);
|
|
895
|
+
}
|
|
859
896
|
}
|
|
860
897
|
|
|
861
898
|
static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
|
|
@@ -866,12 +903,13 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
|
|
|
866
903
|
|
|
867
904
|
if (state->dynamic_sampling_rate_enabled && !dynamic_sampling_rate_should_sample(&state->cpu_dynamic_sampling_rate, wall_time_ns_before_sample)) {
|
|
868
905
|
state->stats.cpu_skipped++;
|
|
869
|
-
return
|
|
906
|
+
return Qfalse;
|
|
870
907
|
}
|
|
871
908
|
|
|
872
909
|
state->stats.cpu_sampled++;
|
|
873
910
|
|
|
874
|
-
|
|
911
|
+
bool needs_otel_span_key =
|
|
912
|
+
thread_context_collector_sample(state->thread_context_collector_instance, wall_time_ns_before_sample);
|
|
875
913
|
|
|
876
914
|
long wall_time_ns_after_sample = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
877
915
|
long delta_ns = wall_time_ns_after_sample - wall_time_ns_before_sample;
|
|
@@ -885,8 +923,7 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
|
|
|
885
923
|
|
|
886
924
|
dynamic_sampling_rate_after_sample(&state->cpu_dynamic_sampling_rate, wall_time_ns_after_sample, sampling_time_ns);
|
|
887
925
|
|
|
888
|
-
|
|
889
|
-
return Qnil;
|
|
926
|
+
return needs_otel_span_key ? Qtrue : Qfalse;
|
|
890
927
|
}
|
|
891
928
|
|
|
892
929
|
// This method exists only to enable testing Datadog::Profiling::Collectors::CpuAndWallTimeWorker behavior using RSpec.
|
|
@@ -912,7 +949,7 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
|
|
|
912
949
|
cpu_and_wall_time_worker_state *state;
|
|
913
950
|
TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
914
951
|
|
|
915
|
-
// Final preparations: Setup signal handler and enable
|
|
952
|
+
// Final preparations: Setup signal handler and enable hooks. We run these here and not in `_native_sampling_loop`
|
|
916
953
|
// because they may raise exceptions.
|
|
917
954
|
install_sigprof_signal_handler(handle_sampling_signal, "handle_sampling_signal");
|
|
918
955
|
if (state->gc_profiling_enabled) rb_tracepoint_enable(state->gc_tracepoint);
|
|
@@ -1095,6 +1132,15 @@ static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused) {
|
|
|
1095
1132
|
);
|
|
1096
1133
|
|
|
1097
1134
|
during_sample_exit(state);
|
|
1135
|
+
|
|
1136
|
+
// This part runs separately from above because it may lose the GVL and we don't want `during_sample` to be set in
|
|
1137
|
+
// such a situation
|
|
1138
|
+
safely_call(
|
|
1139
|
+
thread_context_collector_heap_update_may_lose_gvl,
|
|
1140
|
+
state->thread_context_collector_instance,
|
|
1141
|
+
state->self_instance,
|
|
1142
|
+
handle_sampling_failure_thread_context_collector_heap_update
|
|
1143
|
+
);
|
|
1098
1144
|
}
|
|
1099
1145
|
|
|
1100
1146
|
// Equivalent to Ruby begin/rescue call, where we call a C function and jump to the exception handler if an
|
|
@@ -1132,12 +1178,12 @@ static VALUE _native_simulate_sample_from_postponed_job(DDTRACE_UNUSED VALUE sel
|
|
|
1132
1178
|
|
|
1133
1179
|
// After the Ruby VM forks, this method gets called in the child process to clean up any leftover state from the parent.
|
|
1134
1180
|
//
|
|
1135
|
-
// Assumption: This method gets called BEFORE restarting profiling. Note that profiling-related
|
|
1181
|
+
// Assumption: This method gets called BEFORE restarting profiling. Note that profiling-related hooks may still
|
|
1136
1182
|
// be active, so we make sure to disable them before calling into anything else, so that there are no components
|
|
1137
1183
|
// attempting to trigger samples at the same time as the reset is done.
|
|
1138
1184
|
//
|
|
1139
|
-
// In the future, if we add more other components with
|
|
1140
|
-
//
|
|
1185
|
+
// In the future, if we add more other components with hooks, we will need to coordinate stopping all such
|
|
1186
|
+
// hooks before doing the other cleaning steps.
|
|
1141
1187
|
//
|
|
1142
1188
|
// Note that tests call this method directly in the same process without forking,
|
|
1143
1189
|
// and in such a case non-current Threads keep running.
|
|
@@ -1145,8 +1191,8 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE instance)
|
|
|
1145
1191
|
cpu_and_wall_time_worker_state *state;
|
|
1146
1192
|
TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
1147
1193
|
|
|
1148
|
-
// Disable all
|
|
1149
|
-
|
|
1194
|
+
// Disable all hooks, so that there are no more attempts to mutate the profile
|
|
1195
|
+
disable_hooks(state);
|
|
1150
1196
|
|
|
1151
1197
|
reset_stats_not_thread_safe(state);
|
|
1152
1198
|
|
|
@@ -1235,11 +1281,19 @@ void *simulate_sampling_signal_delivery(DDTRACE_UNUSED void *_unused) {
|
|
|
1235
1281
|
// This can potentially happen if the CpuAndWallTimeWorker was stopped while the IdleSamplingHelper was trying to execute this action
|
|
1236
1282
|
if (state == NULL) return NULL;
|
|
1237
1283
|
|
|
1284
|
+
// Since this is not a real signal firing, we need to block SIGPROF delivery on this thread to avoid an actual SIGPROF
|
|
1285
|
+
// signal coming in nested and interrupting us on this thread. Thus we respect the invariant of "no nesting" for `handle_sampling_signal`.
|
|
1286
|
+
//
|
|
1287
|
+
// Not needed when `no_signals_workaround_enabled` is set: no SIGPROFs are ever sent in that mode, so there's nothing to mask.
|
|
1288
|
+
if (!state->no_signals_workaround_enabled) block_sigprof_signal_handler_from_running_in_current_thread();
|
|
1289
|
+
|
|
1238
1290
|
state->stats.simulated_signal_delivery++;
|
|
1239
1291
|
|
|
1240
1292
|
// `handle_sampling_signal` does a few things extra on top of `sample_from_postponed_job` so that's why we don't shortcut here
|
|
1241
1293
|
handle_sampling_signal(0, NULL, NULL);
|
|
1242
1294
|
|
|
1295
|
+
if (!state->no_signals_workaround_enabled) unblock_sigprof_signal_handler_from_running_in_current_thread();
|
|
1296
|
+
|
|
1243
1297
|
return NULL; // Unused
|
|
1244
1298
|
}
|
|
1245
1299
|
|
|
@@ -1301,6 +1355,9 @@ static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self) {
|
|
|
1301
1355
|
// Implements memory-related profiling events. This function is called by Ruby via the `rb_add_event_hook2`
|
|
1302
1356
|
// when the RUBY_INTERNAL_EVENT_NEWOBJ event is triggered.
|
|
1303
1357
|
//
|
|
1358
|
+
// This function is called from the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
|
|
1359
|
+
// Ruby heap nor release the GVL (https://github.com/DataDog/dd-trace-rb/pull/4240).
|
|
1360
|
+
//
|
|
1304
1361
|
// When allocation sampling is enabled, this function gets called for almost all* objects allocated by the Ruby VM.
|
|
1305
1362
|
// (*In some weird cases the VM may skip this tracepoint.)
|
|
1306
1363
|
//
|
|
@@ -1411,7 +1468,7 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
|
|
|
1411
1468
|
during_sample_exit(state);
|
|
1412
1469
|
}
|
|
1413
1470
|
|
|
1414
|
-
static void
|
|
1471
|
+
static void disable_hooks(cpu_and_wall_time_worker_state *state) {
|
|
1415
1472
|
if (state->gc_tracepoint != Qnil) {
|
|
1416
1473
|
rb_tracepoint_disable(state->gc_tracepoint);
|
|
1417
1474
|
}
|
|
@@ -1459,18 +1516,18 @@ static VALUE rescued_sample_allocation(VALUE arg) {
|
|
|
1459
1516
|
// To control bias from sampling, we clamp the maximum weight attributed to a single allocation sample. This avoids
|
|
1460
1517
|
// assigning a very large number to a sample, if for instance the dynamic sampling mechanism chose a really big interval.
|
|
1461
1518
|
unsigned int weight = allocations_since_last_sample > MAX_ALLOC_WEIGHT ? MAX_ALLOC_WEIGHT : (unsigned int) allocations_since_last_sample;
|
|
1462
|
-
bool
|
|
1519
|
+
bool needs_commit = thread_context_collector_sample_allocation(state->thread_context_collector_instance, thread_context, weight, new_object);
|
|
1463
1520
|
// ...but we still represent the skipped samples in the profile, thus the data will account for all allocations.
|
|
1464
1521
|
if (weight < allocations_since_last_sample) {
|
|
1465
1522
|
uint32_t skipped_samples = (uint32_t) uint64_min_of(allocations_since_last_sample - weight, UINT32_MAX);
|
|
1466
1523
|
thread_context_collector_sample_skipped_allocation_samples(state->thread_context_collector_instance, skipped_samples);
|
|
1467
1524
|
}
|
|
1468
1525
|
|
|
1469
|
-
if (
|
|
1526
|
+
if (needs_commit) {
|
|
1470
1527
|
#ifndef NO_POSTPONED_TRIGGER
|
|
1471
|
-
rb_postponed_job_trigger(
|
|
1528
|
+
rb_postponed_job_trigger(commit_heap_recordings_from_postponed_job_may_lose_gvl_handle);
|
|
1472
1529
|
#else
|
|
1473
|
-
|
|
1530
|
+
rb_postponed_job_register_one(0, commit_heap_recordings_from_postponed_job_may_lose_gvl, NULL);
|
|
1474
1531
|
#endif
|
|
1475
1532
|
}
|
|
1476
1533
|
|
|
@@ -1545,10 +1602,11 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1545
1602
|
} else if (event_id == RUBY_INTERNAL_THREAD_EVENT_READY) { /* waiting for gvl */
|
|
1546
1603
|
thread_context_collector_on_gvl_waiting(thread_context);
|
|
1547
1604
|
} else if (event_id == RUBY_INTERNAL_THREAD_EVENT_RESUMED) { /* running/runnable */
|
|
1548
|
-
//
|
|
1549
|
-
//
|
|
1550
|
-
// However,
|
|
1551
|
-
//
|
|
1605
|
+
// We must only use async-signal-safe functions here and not call arbitrary Ruby APIs and not allocate!
|
|
1606
|
+
// One might assume RUBY_INTERNAL_THREAD_EVENT_RESUMED means having the GVL and running that thread.
|
|
1607
|
+
// However, the reality is more complicated (https://bugs.ruby-lang.org/issues/22098),
|
|
1608
|
+
// it only "sort of" has the GVL but not fully, and it's called while holding the scheduler lock,
|
|
1609
|
+
// so we do as little work as possible here, and perform the sample in a postponed_job.
|
|
1552
1610
|
cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
|
|
1553
1611
|
if (state == NULL) return; // This should not happen, but just in case...
|
|
1554
1612
|
|
|
@@ -1560,7 +1618,8 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1560
1618
|
// that next.
|
|
1561
1619
|
during_sample_enter(state);
|
|
1562
1620
|
|
|
1563
|
-
on_gvl_running_result result =
|
|
1621
|
+
on_gvl_running_result result =
|
|
1622
|
+
thread_context_collector_on_gvl_running(target_thread, thread_context, state->waiting_for_gvl_threshold_ns);
|
|
1564
1623
|
|
|
1565
1624
|
during_sample_exit(state);
|
|
1566
1625
|
|
|
@@ -1650,54 +1709,54 @@ static VALUE handle_sampling_failure_thread_context_collector_sample_after_gc(VA
|
|
|
1650
1709
|
return Qnil;
|
|
1651
1710
|
}
|
|
1652
1711
|
|
|
1712
|
+
static VALUE handle_sampling_failure_thread_context_collector_heap_update(VALUE self_instance, VALUE exception) {
|
|
1713
|
+
stop(self_instance, exception, "thread_context_collector_heap_update_may_lose_gvl");
|
|
1714
|
+
return Qnil;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1653
1717
|
static VALUE handle_sampling_failure_rescued_sample_allocation(VALUE self_instance, VALUE exception) {
|
|
1654
1718
|
stop(self_instance, exception, "rescued_sample_allocation");
|
|
1655
1719
|
return Qnil;
|
|
1656
1720
|
}
|
|
1657
1721
|
|
|
1658
|
-
static VALUE
|
|
1659
|
-
stop(self_instance, exception, "
|
|
1722
|
+
static VALUE handle_sampling_failure_rescued_commit_heap_recordings(VALUE self_instance, VALUE exception) {
|
|
1723
|
+
stop(self_instance, exception, "rescued_commit_heap_recordings_may_lose_gvl");
|
|
1660
1724
|
return Qnil;
|
|
1661
1725
|
}
|
|
1662
1726
|
|
|
1663
|
-
static VALUE
|
|
1727
|
+
static VALUE rescued_commit_heap_recordings_may_lose_gvl(VALUE self_instance) {
|
|
1664
1728
|
cpu_and_wall_time_worker_state *state;
|
|
1665
1729
|
TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
1666
1730
|
|
|
1667
|
-
|
|
1731
|
+
thread_context_collector_commit_heap_recordings_may_lose_gvl(state->thread_context_collector_instance);
|
|
1668
1732
|
|
|
1669
1733
|
// Return a dummy VALUE because we're called from rb_rescue2 which requires it
|
|
1670
1734
|
return Qnil;
|
|
1671
1735
|
}
|
|
1672
1736
|
|
|
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) {
|
|
1737
|
+
// This postponed job callback is used to commit heap allocation recordings.
|
|
1738
|
+
// During on_newobj_event, we can't take the weak reference the heap recorder needs to track the object, so we defer
|
|
1739
|
+
// that until after the event completes.
|
|
1740
|
+
static void commit_heap_recordings_from_postponed_job_may_lose_gvl(DDTRACE_UNUSED void *_unused) {
|
|
1679
1741
|
cpu_and_wall_time_worker_state *state = active_sampler_instance_state;
|
|
1680
1742
|
|
|
1681
1743
|
if (state == NULL || !ddtrace_rb_ractor_main_p()) return;
|
|
1682
1744
|
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1745
|
+
if (state->during_sample) {
|
|
1746
|
+
delayed_error(state, "commit_heap_recordings_from_postponed_job_may_lose_gvl called during_sample");
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1687
1749
|
|
|
1688
1750
|
// NOTE: We're not updating the allocation_sampler here.
|
|
1689
1751
|
// This means work done in this function isn't accounted for as profiler overhead.
|
|
1690
1752
|
// This is acceptable as the amount of work done here is expected to be small.
|
|
1691
1753
|
safely_call(
|
|
1692
|
-
|
|
1754
|
+
rescued_commit_heap_recordings_may_lose_gvl,
|
|
1693
1755
|
state->self_instance,
|
|
1694
1756
|
state->self_instance,
|
|
1695
|
-
|
|
1757
|
+
handle_sampling_failure_rescued_commit_heap_recordings
|
|
1696
1758
|
);
|
|
1697
|
-
|
|
1698
|
-
during_sample_exit(state);
|
|
1699
1759
|
}
|
|
1700
|
-
#pragma GCC diagnostic pop
|
|
1701
1760
|
|
|
1702
1761
|
static inline void during_sample_enter(cpu_and_wall_time_worker_state* state) {
|
|
1703
1762
|
// Tell the compiler it's not allowed to reorder the `during_sample` flag with anything that happens after.
|