datadog 2.35.0 → 2.36.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 +40 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +68 -31
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +37 -18
- data/ext/datadog_profiling_native_extension/collectors_stack.h +8 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +434 -300
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +9 -7
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +7 -8
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -12
- data/ext/datadog_profiling_native_extension/extconf.rb +2 -2
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +4 -43
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -47
- data/ext/datadog_profiling_native_extension/heap_recorder.c +44 -26
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +14 -35
- data/ext/datadog_profiling_native_extension/profiling.c +41 -4
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +33 -34
- data/ext/datadog_profiling_native_extension/stack_recorder.c +24 -3
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +4 -2
- data/ext/libdatadog_api/datadog_ruby_common.c +7 -8
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -12
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/api_security/route_extractor.rb +6 -0
- data/lib/datadog/appsec/component.rb +1 -1
- data/lib/datadog/appsec/configuration.rb +7 -0
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +37 -4
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +64 -19
- data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -0
- data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +41 -3
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +20 -7
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +92 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +33 -0
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +17 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +20 -3
- data/lib/datadog/appsec/default_header_tags.rb +10 -6
- data/lib/datadog/core/configuration/components.rb +1 -0
- data/lib/datadog/core/configuration/settings.rb +1 -2
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
- data/lib/datadog/core/remote/component.rb +1 -1
- data/lib/datadog/core/telemetry/event/app_started.rb +0 -21
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
- data/lib/datadog/core/utils/forking.rb +3 -1
- data/lib/datadog/core/utils/spawn_monkey_patch.rb +3 -1
- data/lib/datadog/core.rb +3 -0
- data/lib/datadog/di/base.rb +4 -1
- data/lib/datadog/di/component.rb +1 -1
- data/lib/datadog/error_tracking/collector.rb +2 -1
- data/lib/datadog/error_tracking/component.rb +2 -2
- data/lib/datadog/kit/tracing/method_tracer.rb +4 -1
- data/lib/datadog/opentelemetry/sdk/propagator.rb +9 -3
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +4 -1
- data/lib/datadog/profiling/collectors/thread_context.rb +1 -0
- data/lib/datadog/profiling/component.rb +13 -15
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
- data/lib/datadog/ruby_version.rb +25 -0
- data/lib/datadog/symbol_database/component.rb +306 -98
- data/lib/datadog/symbol_database/extractor.rb +223 -84
- data/lib/datadog/tracing/configuration/ext.rb +13 -0
- data/lib/datadog/tracing/configuration/settings.rb +17 -0
- data/lib/datadog/tracing/contrib/configuration/resolver.rb +7 -0
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/grpc.rb +1 -0
- data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/http.rb +1 -0
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/karafka.rb +1 -0
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +3 -1
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +3 -1
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq.rb +1 -0
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/waterdrop.rb +1 -0
- data/lib/datadog/tracing/distributed/propagation.rb +33 -1
- data/lib/datadog/tracing/distributed/trace_context.rb +11 -2
- data/lib/datadog/tracing/trace_digest.rb +7 -0
- data/lib/datadog/tracing/trace_operation.rb +4 -1
- data/lib/datadog/tracing/tracer.rb +1 -0
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +4 -1
- metadata +8 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fd8fcd94fa20eb591676abd0f96d6ef69f475f10c08e53d7e4dad1c0cab35e7c
|
|
4
|
+
data.tar.gz: 6e3036aedb4b47ccb9d8c7ec74d070f52e755796e729d99f83c171ca1879ba59
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8de4af357d5807e1855eb4dc078d291af8b601bda3fac1d44d442634dd1b241c7a6b27397095f54a9d931110b503398a43de70878e62c9525629c2b17bcbd97a
|
|
7
|
+
data.tar.gz: 6f8acf814a862cfe261b6a3e97f40c37d0cb86b36deeadf253b14c0b17647e570eca9fca2a4d3dc86126a63d9fa5c0d83f750d495d0f6df0e1d23f43493dad6b
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.36.0] - 2026-06-24
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
* Tracing: Add `DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT` to control trace extraction behavior with `continue`, `restart`, and `ignore` modes ([#5844][])
|
|
10
|
+
* AppSec: Add `DD_APPSEC_BODY_PARSING_SIZE_LIMIT` to control processing of request and response body size; set to 0 to disable ([#5877][])
|
|
11
|
+
* AppSec: Detect attacks from inline fragments in GraphQL queries ([#5916][])
|
|
12
|
+
* Dynamic Instrumentation: Show lazily loaded classes in UI ([#5697][])
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
* Dynamic Instrumentation: Reduce peak memory usage during Symbol Database extraction ([#5883][])
|
|
17
|
+
* Profiling: Reduce profiler overhead by up to 50% by skipping redundant samples for threads without the GVL ([#5777][])
|
|
18
|
+
* Profiling: Remove overhead when cleaning up dead threads ([#5816][])
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
* Tracing: Workaround Ruby VM bug causing segmentation faults inside CachingResolver ([#5719][], [#5890][])
|
|
23
|
+
* Dynamic Instrumentation: Prevent uploading stale class definitions for apps using `remove_const`-then-redefine patterns ([#5872][])
|
|
24
|
+
* Profiling: Fix GC profiling being incorrectly disabled on Ruby 3.2.10 and 3.2.11 ([#5894][])
|
|
25
|
+
* Profiling: Fix over-counting of the first allocation sample at profiler startup ([#5881][])
|
|
26
|
+
* Profiling: Fix rare profiler crash during shutdown in heap profiling cleanup ([#5920][])
|
|
27
|
+
* Core: Fix exception message formatting from native extensions ([#5857][])
|
|
28
|
+
|
|
5
29
|
## [2.35.0] - 2026-06-03
|
|
6
30
|
|
|
7
31
|
### Added
|
|
@@ -3634,7 +3658,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
|
|
|
3634
3658
|
Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
3635
3659
|
|
|
3636
3660
|
|
|
3637
|
-
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.
|
|
3661
|
+
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.36.0...master
|
|
3662
|
+
[2.36.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.35.0...v2.36.0
|
|
3638
3663
|
[2.35.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.34.0...v2.35.0
|
|
3639
3664
|
[2.34.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.33.0...v2.34.0
|
|
3640
3665
|
[2.33.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.32.0...v2.33.0
|
|
@@ -5379,8 +5404,10 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5379
5404
|
[#5681]: https://github.com/DataDog/dd-trace-rb/issues/5681
|
|
5380
5405
|
[#5687]: https://github.com/DataDog/dd-trace-rb/issues/5687
|
|
5381
5406
|
[#5689]: https://github.com/DataDog/dd-trace-rb/issues/5689
|
|
5407
|
+
[#5697]: https://github.com/DataDog/dd-trace-rb/issues/5697
|
|
5382
5408
|
[#5705]: https://github.com/DataDog/dd-trace-rb/issues/5705
|
|
5383
5409
|
[#5717]: https://github.com/DataDog/dd-trace-rb/issues/5717
|
|
5410
|
+
[#5719]: https://github.com/DataDog/dd-trace-rb/issues/5719
|
|
5384
5411
|
[#5723]: https://github.com/DataDog/dd-trace-rb/issues/5723
|
|
5385
5412
|
[#5724]: https://github.com/DataDog/dd-trace-rb/issues/5724
|
|
5386
5413
|
[#5750]: https://github.com/DataDog/dd-trace-rb/issues/5750
|
|
@@ -5389,10 +5416,22 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5389
5416
|
[#5762]: https://github.com/DataDog/dd-trace-rb/issues/5762
|
|
5390
5417
|
[#5768]: https://github.com/DataDog/dd-trace-rb/issues/5768
|
|
5391
5418
|
[#5773]: https://github.com/DataDog/dd-trace-rb/issues/5773
|
|
5419
|
+
[#5777]: https://github.com/DataDog/dd-trace-rb/issues/5777
|
|
5392
5420
|
[#5811]: https://github.com/DataDog/dd-trace-rb/issues/5811
|
|
5393
5421
|
[#5812]: https://github.com/DataDog/dd-trace-rb/issues/5812
|
|
5422
|
+
[#5816]: https://github.com/DataDog/dd-trace-rb/issues/5816
|
|
5394
5423
|
[#5830]: https://github.com/DataDog/dd-trace-rb/issues/5830
|
|
5395
5424
|
[#5836]: https://github.com/DataDog/dd-trace-rb/issues/5836
|
|
5425
|
+
[#5844]: https://github.com/DataDog/dd-trace-rb/issues/5844
|
|
5426
|
+
[#5857]: https://github.com/DataDog/dd-trace-rb/issues/5857
|
|
5427
|
+
[#5872]: https://github.com/DataDog/dd-trace-rb/issues/5872
|
|
5428
|
+
[#5877]: https://github.com/DataDog/dd-trace-rb/issues/5877
|
|
5429
|
+
[#5881]: https://github.com/DataDog/dd-trace-rb/issues/5881
|
|
5430
|
+
[#5883]: https://github.com/DataDog/dd-trace-rb/issues/5883
|
|
5431
|
+
[#5890]: https://github.com/DataDog/dd-trace-rb/issues/5890
|
|
5432
|
+
[#5894]: https://github.com/DataDog/dd-trace-rb/issues/5894
|
|
5433
|
+
[#5916]: https://github.com/DataDog/dd-trace-rb/issues/5916
|
|
5434
|
+
[#5920]: https://github.com/DataDog/dd-trace-rb/issues/5920
|
|
5396
5435
|
[@AdrianLC]: https://github.com/AdrianLC
|
|
5397
5436
|
[@Azure7111]: https://github.com/Azure7111
|
|
5398
5437
|
[@BabyGroot]: https://github.com/BabyGroot
|
|
@@ -182,6 +182,11 @@ typedef struct {
|
|
|
182
182
|
unsigned int allocations_during_sample;
|
|
183
183
|
|
|
184
184
|
// # GVL profiling stats
|
|
185
|
+
// Note that this tracks two kind of samples from RESUMED:
|
|
186
|
+
// * samples when Waiting for GVL for more than the threshold
|
|
187
|
+
// * samples for the skip-samples-while-without-GVL optimization,
|
|
188
|
+
// which are needed to attribute the time without GVL to the correct Ruby stack
|
|
189
|
+
|
|
185
190
|
// How many times we triggered the after_gvl_running sampling
|
|
186
191
|
unsigned int after_gvl_running;
|
|
187
192
|
// How many times we skipped the after_gvl_running sampling
|
|
@@ -659,7 +664,7 @@ static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED si
|
|
|
659
664
|
if (sample_from_signal_handler) {
|
|
660
665
|
// Buffer current stack trace. Note that this will not actually record the sample, for that we still need to wait
|
|
661
666
|
// until the postponed job below gets run.
|
|
662
|
-
bool prepared = thread_context_collector_prepare_sample_inside_signal_handler(
|
|
667
|
+
bool prepared = thread_context_collector_prepare_sample_inside_signal_handler();
|
|
663
668
|
|
|
664
669
|
if (prepared) state->stats.signal_handler_prepared_sample++;
|
|
665
670
|
}
|
|
@@ -808,8 +813,7 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
|
|
|
808
813
|
|
|
809
814
|
state->stats.cpu_sampled++;
|
|
810
815
|
|
|
811
|
-
|
|
812
|
-
thread_context_collector_sample(state->thread_context_collector_instance, wall_time_ns_before_sample, profiler_overhead_stack_thread);
|
|
816
|
+
thread_context_collector_sample(state->thread_context_collector_instance, wall_time_ns_before_sample);
|
|
813
817
|
|
|
814
818
|
long wall_time_ns_after_sample = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
815
819
|
long delta_ns = wall_time_ns_after_sample - wall_time_ns_before_sample;
|
|
@@ -865,15 +869,12 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
|
|
|
865
869
|
|
|
866
870
|
if (state->gvl_profiling_enabled) {
|
|
867
871
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
868
|
-
#ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS
|
|
869
|
-
gvl_profiling_state_thread_tracking_workaround();
|
|
870
|
-
#endif
|
|
871
|
-
|
|
872
872
|
state->gvl_profiling_hook = rb_internal_thread_add_event_hook(
|
|
873
873
|
on_gvl_event,
|
|
874
874
|
(
|
|
875
875
|
// For now we're only asking for these events, even though there's more
|
|
876
876
|
// (e.g. check docs or gvl-tracing gem)
|
|
877
|
+
RUBY_INTERNAL_THREAD_EVENT_SUSPENDED | /* released gvl */
|
|
877
878
|
RUBY_INTERNAL_THREAD_EVENT_READY /* waiting for gvl */ |
|
|
878
879
|
RUBY_INTERNAL_THREAD_EVENT_RESUMED /* running/runnable */
|
|
879
880
|
),
|
|
@@ -1055,6 +1056,9 @@ static VALUE _native_simulate_sample_from_postponed_job(DDTRACE_UNUSED VALUE sel
|
|
|
1055
1056
|
//
|
|
1056
1057
|
// In the future, if we add more other components with tracepoints, we will need to coordinate stopping all such
|
|
1057
1058
|
// tracepoints before doing the other cleaning steps.
|
|
1059
|
+
//
|
|
1060
|
+
// Note that tests call this method directly in the same process without forking,
|
|
1061
|
+
// and in such a case non-current Threads keep running.
|
|
1058
1062
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
1059
1063
|
cpu_and_wall_time_worker_state *state;
|
|
1060
1064
|
TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
@@ -1129,6 +1133,9 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
|
1129
1133
|
ID2SYM(rb_intern("gvl_waiting_time_ns_total")), /* => */ state->gvl_profiling_enabled ? ULL2NUM(state->stats.vm_metrics.gvl_waiting_time_ns_total) : Qnil,
|
|
1130
1134
|
};
|
|
1131
1135
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
|
|
1136
|
+
|
|
1137
|
+
thread_context_collector_stats(state->thread_context_collector_instance, stats_as_hash);
|
|
1138
|
+
|
|
1132
1139
|
return stats_as_hash;
|
|
1133
1140
|
}
|
|
1134
1141
|
|
|
@@ -1136,6 +1143,7 @@ static VALUE _native_stats_reset_not_thread_safe(DDTRACE_UNUSED VALUE self, VALU
|
|
|
1136
1143
|
cpu_and_wall_time_worker_state *state;
|
|
1137
1144
|
TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
|
|
1138
1145
|
reset_stats_not_thread_safe(state);
|
|
1146
|
+
thread_context_collector_stats_reset_not_thread_safe(state->thread_context_collector_instance);
|
|
1139
1147
|
return Qnil;
|
|
1140
1148
|
}
|
|
1141
1149
|
|
|
@@ -1245,13 +1253,23 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
|
|
|
1245
1253
|
return;
|
|
1246
1254
|
}
|
|
1247
1255
|
|
|
1256
|
+
VALUE current_thread = rb_thread_current();
|
|
1257
|
+
|
|
1248
1258
|
// If Ruby is in the middle of raising an exception, we don't want to try to sample. This is because if we accidentally
|
|
1249
1259
|
// trigger an exception inside the profiler code, bad things will happen (specifically, Ruby will try to kill off the
|
|
1250
1260
|
// thread even though we may try to catch the exception).
|
|
1251
1261
|
//
|
|
1252
1262
|
// Note that "in the middle of raising an exception" means the exception itself has already been allocated.
|
|
1253
1263
|
// What's getting allocated now is probably the backtrace objects (@ivoanjo or at least that's what I've observed)
|
|
1254
|
-
if (is_raised_flag_set(
|
|
1264
|
+
if (is_raised_flag_set(current_thread)) {
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
per_thread_context *thread_context = get_per_thread_context(current_thread);
|
|
1269
|
+
if (!thread_context) {
|
|
1270
|
+
// No per_thread_context yet on this Thread, we can't use get_or_create_context_for() as that allocates,
|
|
1271
|
+
// and we are inside on_newobj_event where we MUST NOT allocate.
|
|
1272
|
+
// So we don't sample allocations until another hook allocates the per_thread_context.
|
|
1255
1273
|
return;
|
|
1256
1274
|
}
|
|
1257
1275
|
|
|
@@ -1286,7 +1304,7 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
|
|
|
1286
1304
|
// Rescue against any exceptions that happen during sampling
|
|
1287
1305
|
safely_call(
|
|
1288
1306
|
rescued_sample_allocation,
|
|
1289
|
-
|
|
1307
|
+
(VALUE) thread_context,
|
|
1290
1308
|
state->self_instance,
|
|
1291
1309
|
handle_sampling_failure_rescued_sample_allocation
|
|
1292
1310
|
);
|
|
@@ -1338,7 +1356,8 @@ static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self) {
|
|
|
1338
1356
|
}
|
|
1339
1357
|
}
|
|
1340
1358
|
|
|
1341
|
-
static VALUE rescued_sample_allocation(
|
|
1359
|
+
static VALUE rescued_sample_allocation(VALUE arg) {
|
|
1360
|
+
per_thread_context *thread_context = (per_thread_context*) arg;
|
|
1342
1361
|
cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
|
|
1343
1362
|
|
|
1344
1363
|
// This should not happen in a normal situation because on_newobj_event already checked for this, but just in case...
|
|
@@ -1357,7 +1376,7 @@ static VALUE rescued_sample_allocation(DDTRACE_UNUSED VALUE unused) {
|
|
|
1357
1376
|
// To control bias from sampling, we clamp the maximum weight attributed to a single allocation sample. This avoids
|
|
1358
1377
|
// assigning a very large number to a sample, if for instance the dynamic sampling mechanism chose a really big interval.
|
|
1359
1378
|
unsigned int weight = allocations_since_last_sample > MAX_ALLOC_WEIGHT ? MAX_ALLOC_WEIGHT : (unsigned int) allocations_since_last_sample;
|
|
1360
|
-
bool needs_after_allocation = thread_context_collector_sample_allocation(state->thread_context_collector_instance, weight, new_object);
|
|
1379
|
+
bool needs_after_allocation = thread_context_collector_sample_allocation(state->thread_context_collector_instance, thread_context, weight, new_object);
|
|
1361
1380
|
// ...but we still represent the skipped samples in the profile, thus the data will account for all allocations.
|
|
1362
1381
|
if (weight < allocations_since_last_sample) {
|
|
1363
1382
|
uint32_t skipped_samples = (uint32_t) uint64_min_of(allocations_since_last_sample - weight, UINT32_MAX);
|
|
@@ -1414,30 +1433,49 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1414
1433
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
1415
1434
|
static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused) {
|
|
1416
1435
|
// Be very careful about touching the `state` here or doing anything at all:
|
|
1417
|
-
// This function gets called without the GVL, and potentially from
|
|
1418
|
-
//
|
|
1419
|
-
// In fact, the `target_thread` that this event is about may not even be the current thread. (So be careful with thread locals that
|
|
1420
|
-
// are not directly tied to the `target_thread` object and the like)
|
|
1421
|
-
gvl_profiling_thread target_thread = thread_from_event(event_data);
|
|
1436
|
+
// This function gets called without the GVL, and potentially from non-main Ractors!
|
|
1422
1437
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
// Interesting note: A RUBY_INTERNAL_THREAD_EVENT_RESUMED is guaranteed to be called with the GVL being acquired.
|
|
1427
|
-
// (And... I think target_thread will be == rb_thread_current()?)
|
|
1428
|
-
//
|
|
1429
|
-
// But we're not sure if we're on the main Ractor yet. The thread context collector can actually help here:
|
|
1430
|
-
// it tags threads it's tracking, so if a thread is tagged then by definition we know that thread belongs to the main
|
|
1431
|
-
// Ractor. Thus, if we get a ON_GVL_RUNNING_UNKNOWN result we shouldn't touch any state, but otherwise we're good to go.
|
|
1438
|
+
// The thread that this event is about may not be the current thread
|
|
1439
|
+
// (as documented on rb_internal_thread_add_event_hook(), and this is notably the case for READY on Ruby 4.0),
|
|
1440
|
+
// so be careful with native thread locals that are not directly tied to the thread object and the like.
|
|
1432
1441
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1442
|
+
// On Ruby 3.2 the event does not carry the thread, but all events always fire on the event thread on Ruby 3.2.
|
|
1443
|
+
// However, during early thread startup rb_thread_current() can crash because the execution context (Fiber) isn't
|
|
1444
|
+
// stored in TLS yet; ruby_native_thread_p() guards against this.
|
|
1445
|
+
#ifdef HAVE_RUBY_THREAD_STORAGE_API
|
|
1446
|
+
VALUE target_thread = event_data->thread;
|
|
1447
|
+
#else
|
|
1448
|
+
if (!ruby_native_thread_p()) return;
|
|
1449
|
+
VALUE target_thread = rb_thread_current();
|
|
1450
|
+
#endif
|
|
1436
1451
|
|
|
1452
|
+
per_thread_context* thread_context = get_per_thread_context(target_thread);
|
|
1453
|
+
if (!thread_context) return;
|
|
1454
|
+
// If non-NULL the thread is profiled and from the main Ractor
|
|
1455
|
+
|
|
1456
|
+
if (event_id == RUBY_INTERNAL_THREAD_EVENT_SUSPENDED) { /* released gvl */
|
|
1457
|
+
thread_context_collector_on_gvl_released(thread_context);
|
|
1458
|
+
} else if (event_id == RUBY_INTERNAL_THREAD_EVENT_READY) { /* waiting for gvl */
|
|
1459
|
+
thread_context_collector_on_gvl_waiting(thread_context);
|
|
1460
|
+
} else if (event_id == RUBY_INTERNAL_THREAD_EVENT_RESUMED) { /* running/runnable */
|
|
1461
|
+
// Interesting note: A RUBY_INTERNAL_THREAD_EVENT_RESUMED is guaranteed to be called with the GVL being acquired
|
|
1462
|
+
// and on the event thread.
|
|
1463
|
+
// However, on_gvl_event() is called while holding the scheduler lock, so we do as little work as possible here,
|
|
1464
|
+
// and perform the sample in a postponed_job.
|
|
1437
1465
|
cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
|
|
1438
1466
|
if (state == NULL) return; // This should not happen, but just in case...
|
|
1439
1467
|
|
|
1440
|
-
|
|
1468
|
+
// on_gvl_running prepares a stack sample so we need to gate it with `during_sample_enter` to avoid a
|
|
1469
|
+
// SIGPROF signal coming in during that function and potentially causing a corrupted final state.
|
|
1470
|
+
//
|
|
1471
|
+
// TODO: Currently, the sample prepared in on_gvl_running can still be clobbered if the signal handler runs
|
|
1472
|
+
// after `during_sample_exit` but before `after_gvl_running_from_postponed_job` gets to run. We'll need to fix
|
|
1473
|
+
// that next.
|
|
1474
|
+
during_sample_enter(state);
|
|
1475
|
+
|
|
1476
|
+
on_gvl_running_result result = thread_context_collector_on_gvl_running(state->thread_context_collector_instance, target_thread, thread_context);
|
|
1477
|
+
|
|
1478
|
+
during_sample_exit(state);
|
|
1441
1479
|
|
|
1442
1480
|
if (result.waiting_for_gvl_duration_ns > 0) {
|
|
1443
1481
|
state->stats.vm_metrics.gvl_waiting_time_ns_total += (uint64_t) result.waiting_for_gvl_duration_ns;
|
|
@@ -1453,8 +1491,7 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1453
1491
|
state->stats.gvl_dont_sample++;
|
|
1454
1492
|
}
|
|
1455
1493
|
} else {
|
|
1456
|
-
|
|
1457
|
-
fprintf(stderr, "[ddtrace] Unexpected value in on_gvl_event (%d)\n", event_id);
|
|
1494
|
+
rb_bug("[ddtrace] Unexpected value in on_gvl_event (%d)\n", event_id);
|
|
1458
1495
|
}
|
|
1459
1496
|
}
|
|
1460
1497
|
|
|
@@ -208,7 +208,7 @@ void idle_sampling_helper_request_action(VALUE self_instance, void (*run_action_
|
|
|
208
208
|
if (!rb_typeddata_is_kind_of(self_instance, &idle_sampling_helper_typed_data)) {
|
|
209
209
|
grab_gvl_and_raise(rb_eTypeError, "Wrong argument for idle_sampling_helper_request_action");
|
|
210
210
|
}
|
|
211
|
-
// This should never fail
|
|
211
|
+
// This should never fail when the above check passes
|
|
212
212
|
TypedData_Get_Struct(self_instance, idle_sampling_loop_state, &idle_sampling_helper_typed_data, state);
|
|
213
213
|
|
|
214
214
|
ENFORCE_SUCCESS_NO_GVL(pthread_mutex_lock(&state->wakeup_mutex));
|
|
@@ -40,7 +40,7 @@ static void set_file_info_for_cfunc(
|
|
|
40
40
|
st_table *native_filenames_cache
|
|
41
41
|
);
|
|
42
42
|
static const char *get_or_compute_native_filename(void *function, st_table *native_filenames_cache);
|
|
43
|
-
static void add_truncated_frames_placeholder(
|
|
43
|
+
static void add_truncated_frames_placeholder(ddog_prof_Location *locations);
|
|
44
44
|
static void record_placeholder_stack_in_native_code(VALUE recorder_instance, sample_values values, sample_labels labels);
|
|
45
45
|
static void maybe_trim_template_random_ids(ddog_CharSlice *name_slice, ddog_CharSlice *filename_slice);
|
|
46
46
|
|
|
@@ -98,7 +98,7 @@ typedef struct {
|
|
|
98
98
|
sample_values values;
|
|
99
99
|
sample_labels labels;
|
|
100
100
|
VALUE thread;
|
|
101
|
-
|
|
101
|
+
sample_locations locations;
|
|
102
102
|
sampling_buffer *buffer;
|
|
103
103
|
bool native_filenames_enabled;
|
|
104
104
|
st_table *native_filenames_cache;
|
|
@@ -175,7 +175,7 @@ static VALUE _native_sample(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
|
|
175
175
|
|
|
176
176
|
ddog_prof_Location *locations = ruby_xcalloc(max_frames_requested, sizeof(ddog_prof_Location));
|
|
177
177
|
sampling_buffer buffer;
|
|
178
|
-
sampling_buffer_initialize(&buffer, max_frames_requested
|
|
178
|
+
sampling_buffer_initialize(&buffer, max_frames_requested);
|
|
179
179
|
|
|
180
180
|
ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = labels_count};
|
|
181
181
|
|
|
@@ -185,7 +185,7 @@ static VALUE _native_sample(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
|
|
185
185
|
.values = values,
|
|
186
186
|
.labels = (sample_labels) {.labels = slice_labels, .state_label = state_label, .is_gvl_waiting_state = is_gvl_waiting_state == Qtrue},
|
|
187
187
|
.thread = thread,
|
|
188
|
-
.locations = locations,
|
|
188
|
+
.locations = (sample_locations) {.ptr = locations, .len = max_frames_requested},
|
|
189
189
|
.buffer = &buffer,
|
|
190
190
|
.native_filenames_enabled = native_filenames_enabled == Qtrue,
|
|
191
191
|
.native_filenames_cache = st_init_numtable(),
|
|
@@ -208,6 +208,7 @@ static VALUE native_sample_do(VALUE args) {
|
|
|
208
208
|
sample_thread(
|
|
209
209
|
args_struct->thread,
|
|
210
210
|
args_struct->buffer,
|
|
211
|
+
args_struct->locations,
|
|
211
212
|
args_struct->recorder_instance,
|
|
212
213
|
args_struct->values,
|
|
213
214
|
args_struct->labels,
|
|
@@ -222,7 +223,7 @@ static VALUE native_sample_do(VALUE args) {
|
|
|
222
223
|
static VALUE native_sample_ensure(VALUE args) {
|
|
223
224
|
native_sample_args *args_struct = (native_sample_args *) args;
|
|
224
225
|
|
|
225
|
-
ruby_xfree(args_struct->locations);
|
|
226
|
+
ruby_xfree(args_struct->locations.ptr);
|
|
226
227
|
sampling_buffer_free(args_struct->buffer);
|
|
227
228
|
st_free_table(args_struct->native_filenames_cache);
|
|
228
229
|
|
|
@@ -243,6 +244,7 @@ static VALUE native_sample_ensure(VALUE args) {
|
|
|
243
244
|
void sample_thread(
|
|
244
245
|
VALUE thread,
|
|
245
246
|
sampling_buffer* buffer,
|
|
247
|
+
sample_locations locations,
|
|
246
248
|
VALUE recorder_instance,
|
|
247
249
|
sample_values values,
|
|
248
250
|
sample_labels labels,
|
|
@@ -250,11 +252,21 @@ void sample_thread(
|
|
|
250
252
|
st_table *native_filenames_cache
|
|
251
253
|
) {
|
|
252
254
|
// If we already prepared a sample, we use it below; if not, we prepare it now.
|
|
253
|
-
if (!buffer->pending_sample)
|
|
255
|
+
if (!buffer->pending_sample) {
|
|
256
|
+
// Reconcile the sampling_buffer's max_frames with the locations size
|
|
257
|
+
if (buffer->max_frames != locations.len) {
|
|
258
|
+
sampling_buffer_reinitialize(buffer, locations.len);
|
|
259
|
+
}
|
|
260
|
+
prepare_sample_thread(thread, buffer);
|
|
261
|
+
}
|
|
254
262
|
|
|
255
263
|
buffer->pending_sample = false;
|
|
256
264
|
int captured_frames = buffer->pending_sample_result;
|
|
257
265
|
|
|
266
|
+
// The per_thread_context's sampling_buffer may have been created by a previous collector with a
|
|
267
|
+
// different (larger) max_frames. Cap to the locations array size to prevent out-of-bounds writes.
|
|
268
|
+
if (captured_frames > (int) locations.len) captured_frames = (int) locations.len;
|
|
269
|
+
|
|
258
270
|
if (captured_frames == PLACEHOLDER_STACK_IN_NATIVE_CODE) {
|
|
259
271
|
record_placeholder_stack_in_native_code(recorder_instance, values, labels);
|
|
260
272
|
return;
|
|
@@ -389,25 +401,30 @@ void sample_thread(
|
|
|
389
401
|
|
|
390
402
|
int libdatadog_stores_stacks_flipped_from_rb_profile_frames_index = top_of_stack_position - i;
|
|
391
403
|
|
|
392
|
-
|
|
404
|
+
locations.ptr[libdatadog_stores_stacks_flipped_from_rb_profile_frames_index] = (ddog_prof_Location) {
|
|
393
405
|
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
|
394
406
|
.function = (ddog_prof_Function) {.name = name_slice, .filename = filename_slice},
|
|
395
407
|
.line = line,
|
|
396
408
|
};
|
|
397
409
|
}
|
|
398
410
|
|
|
399
|
-
// If we filled up the
|
|
411
|
+
// If we filled up the locations, some frames may have been omitted. In that case, we'll add a placeholder frame
|
|
400
412
|
// with that info.
|
|
401
|
-
if (captured_frames == (long)
|
|
402
|
-
add_truncated_frames_placeholder(
|
|
413
|
+
if (captured_frames == (long) locations.len) {
|
|
414
|
+
add_truncated_frames_placeholder(locations.ptr);
|
|
403
415
|
}
|
|
404
416
|
|
|
405
417
|
record_sample(
|
|
406
418
|
recorder_instance,
|
|
407
|
-
(ddog_prof_Slice_Location) {.ptr =
|
|
419
|
+
(ddog_prof_Slice_Location) {.ptr = locations.ptr, .len = captured_frames},
|
|
408
420
|
values,
|
|
409
421
|
labels
|
|
410
422
|
);
|
|
423
|
+
|
|
424
|
+
// Reconcile the sampling_buffer's max_frames with the locations size for future samples
|
|
425
|
+
if (buffer->max_frames != locations.len) {
|
|
426
|
+
sampling_buffer_reinitialize(buffer, locations.len);
|
|
427
|
+
}
|
|
411
428
|
}
|
|
412
429
|
|
|
413
430
|
#if (defined(HAVE_DLADDR1) && HAVE_DLADDR1) || (defined(HAVE_DLADDR) && HAVE_DLADDR)
|
|
@@ -538,10 +555,10 @@ static void maybe_trim_template_random_ids(ddog_CharSlice *name_slice, ddog_Char
|
|
|
538
555
|
name_slice->len = pos;
|
|
539
556
|
}
|
|
540
557
|
|
|
541
|
-
static void add_truncated_frames_placeholder(
|
|
558
|
+
static void add_truncated_frames_placeholder(ddog_prof_Location *locations) {
|
|
542
559
|
// Important note: The strings below are static so we don't need to worry about their lifetime. If we ever want to change
|
|
543
560
|
// this to non-static strings, don't forget to check that lifetimes are properly respected.
|
|
544
|
-
|
|
561
|
+
locations[0] = (ddog_prof_Location) {
|
|
545
562
|
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
|
546
563
|
.function = {.name = DDOG_CHARSLICE_C("Truncated Frames"), .filename = DDOG_CHARSLICE_C(""), .filename_id = {}},
|
|
547
564
|
.line = 0,
|
|
@@ -618,27 +635,29 @@ uint16_t sampling_buffer_check_max_frames(int max_frames) {
|
|
|
618
635
|
return max_frames;
|
|
619
636
|
}
|
|
620
637
|
|
|
621
|
-
void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames
|
|
638
|
+
void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames) {
|
|
622
639
|
sampling_buffer_check_max_frames(max_frames);
|
|
623
640
|
|
|
624
641
|
buffer->max_frames = max_frames;
|
|
625
|
-
buffer->locations = locations;
|
|
626
642
|
buffer->stack_buffer = ruby_xcalloc(max_frames, sizeof(frame_info));
|
|
627
643
|
buffer->pending_sample = false;
|
|
628
644
|
buffer->is_marking = false;
|
|
629
645
|
buffer->pending_sample_result = 0;
|
|
630
646
|
}
|
|
631
647
|
|
|
648
|
+
void sampling_buffer_reinitialize(sampling_buffer *buffer, uint16_t max_frames) {
|
|
649
|
+
sampling_buffer_free(buffer);
|
|
650
|
+
sampling_buffer_initialize(buffer, max_frames);
|
|
651
|
+
}
|
|
652
|
+
|
|
632
653
|
void sampling_buffer_free(sampling_buffer *buffer) {
|
|
633
|
-
if (buffer->max_frames == 0 || buffer->
|
|
654
|
+
if (buffer->max_frames == 0 || buffer->stack_buffer == NULL) {
|
|
634
655
|
raise_error(rb_eArgError, "sampling_buffer_free called with invalid buffer");
|
|
635
656
|
}
|
|
636
657
|
|
|
637
658
|
ruby_xfree(buffer->stack_buffer);
|
|
638
|
-
// Note: buffer->locations are owned by whoever called sampling_buffer_initialize, not by the buffer itself
|
|
639
659
|
|
|
640
660
|
buffer->max_frames = 0;
|
|
641
|
-
buffer->locations = NULL;
|
|
642
661
|
buffer->stack_buffer = NULL;
|
|
643
662
|
buffer->pending_sample = false;
|
|
644
663
|
buffer->is_marking = false;
|
|
@@ -11,16 +11,21 @@
|
|
|
11
11
|
// Used as scratch space during sampling
|
|
12
12
|
typedef struct {
|
|
13
13
|
uint16_t max_frames;
|
|
14
|
-
ddog_prof_Location *locations;
|
|
15
14
|
frame_info *stack_buffer;
|
|
16
15
|
bool pending_sample;
|
|
17
16
|
bool is_marking; // Used to avoid recording a sample when marking
|
|
18
17
|
int pending_sample_result;
|
|
19
18
|
} sampling_buffer;
|
|
20
19
|
|
|
20
|
+
typedef struct {
|
|
21
|
+
ddog_prof_Location *ptr;
|
|
22
|
+
uint16_t len;
|
|
23
|
+
} sample_locations;
|
|
24
|
+
|
|
21
25
|
void sample_thread(
|
|
22
26
|
VALUE thread,
|
|
23
27
|
sampling_buffer* buffer,
|
|
28
|
+
sample_locations locations,
|
|
24
29
|
VALUE recorder_instance,
|
|
25
30
|
sample_values values,
|
|
26
31
|
sample_labels labels,
|
|
@@ -36,7 +41,8 @@ void record_placeholder_stack(
|
|
|
36
41
|
bool prepare_sample_thread(VALUE thread, sampling_buffer *buffer);
|
|
37
42
|
|
|
38
43
|
uint16_t sampling_buffer_check_max_frames(int max_frames);
|
|
39
|
-
void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames
|
|
44
|
+
void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames);
|
|
45
|
+
void sampling_buffer_reinitialize(sampling_buffer *buffer, uint16_t max_frames);
|
|
40
46
|
void sampling_buffer_free(sampling_buffer *buffer);
|
|
41
47
|
void sampling_buffer_mark(sampling_buffer *buffer);
|
|
42
48
|
static inline bool sampling_buffer_needs_marking(sampling_buffer *buffer) {
|