datadog 2.37.0 → 2.38.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 +29 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +23 -15
- data/ext/datadog_profiling_native_extension/collectors_stack.c +19 -24
- data/ext/datadog_profiling_native_extension/collectors_stack.h +0 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +155 -141
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +0 -3
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +5 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +2 -0
- data/ext/datadog_profiling_native_extension/time_helpers.h +12 -7
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +5 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +5 -0
- data/lib/datadog/core/configuration/agentless_settings_resolver.rb +2 -2
- data/lib/datadog/core/configuration/settings.rb +4 -4
- data/lib/datadog/core/configuration/supported_configurations.rb +1 -0
- data/lib/datadog/core/knuth_sampler.rb +3 -1
- data/lib/datadog/core/metrics/client.rb +1 -1
- data/lib/datadog/core/transport/http/builder.rb +1 -1
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
- data/lib/datadog/core/utils/time.rb +5 -1
- data/lib/datadog/di/boot.rb +3 -0
- data/lib/datadog/di/capture_expression.rb +21 -0
- data/lib/datadog/di/capture_expression_evaluator.rb +71 -0
- data/lib/datadog/di/capture_limits.rb +41 -0
- data/lib/datadog/di/component.rb +2 -2
- data/lib/datadog/di/configuration.rb +6 -0
- data/lib/datadog/di/context.rb +9 -3
- data/lib/datadog/di/el/compiler.rb +1 -1
- data/lib/datadog/di/instrumenter.rb +34 -3
- data/lib/datadog/di/probe.rb +47 -2
- data/lib/datadog/di/probe_builder.rb +83 -1
- data/lib/datadog/di/probe_notification_builder.rb +48 -7
- data/lib/datadog/di/remote.rb +1 -1
- data/lib/datadog/di/serializer.rb +22 -10
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +5 -0
- data/lib/datadog/profiling/collectors/thread_context.rb +9 -1
- data/lib/datadog/profiling/component.rb +17 -0
- data/lib/datadog/tracing/contrib/active_job/data_streams.rb +62 -0
- data/lib/datadog/tracing/contrib/active_job/patcher.rb +12 -0
- data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +2 -3
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +12 -12
- data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/sequel/utils.rb +2 -2
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +1 -1
- data/lib/datadog/tracing/contrib/sinatra/framework.rb +1 -1
- data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- metadata +9 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a10640edadba480a4014cb7861ed0eaa21dac0718709266f59fc1307b8fa64bd
|
|
4
|
+
data.tar.gz: cb0b0bcc5423c0e0398277c3535ea52b485967879cf0ede20eedcfb513f1389f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c77d4b07287c8ee87feb82d541269faa5213861509b99d9e66948022f8c98c005d5b8a8205a263d1d020b8fe8b12306e89fff032d1076f652232133db5c985f2
|
|
7
|
+
data.tar.gz: 5799211f54f368537792d441af52dbd41403da2699f2c503d56b46a533df4b9bef6ef9abf56476f330067c8c555db79a3b1163090cc2267199d937576d210e98
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.38.0] - 2026-07-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
* Dynamic Instrumentation: Log probes now support capture expressions as an alternative to capturing the full local/argument scope ([#5845][])
|
|
10
|
+
* SSI: Add support for Ruby 4.0 runtime ([#5981][])
|
|
11
|
+
* SSI: Add support for Bundler 4.0 and RubyGems 4.0 ([#5981][])
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
* AppSec: Fix Rack-based and Rails request body analysis when body parsing fails ([#5993][])
|
|
16
|
+
* Profiling: Add workaround for crash when collecting frame information ([#6014][])
|
|
17
|
+
* Profiling: Fix left-over state impacting samples when profiler gets stopped and started again ([#5960][])
|
|
18
|
+
* Profiling: Fix Ruby 4.0.6 support by bumping minimum version of `datadog-ruby_core_source` to 3.5.3 ([#6047][])
|
|
19
|
+
* Profiling: Disable live heap size profiling on Ruby 4.0 due to incompatibility ([#6022][])
|
|
20
|
+
* Profiling: Disallow heap profiling on Ruby 4.1+ ([#6023][])
|
|
21
|
+
* Tracing: Fix `pg` instrumentation which raised `ArgumentError` when calling `exec_params`, `exec_prepared` without a `params` argument, or their `async_`/`sync_` variants ([#6020][])
|
|
22
|
+
|
|
5
23
|
## [2.37.0] - 2026-07-06
|
|
6
24
|
|
|
7
25
|
### Added
|
|
@@ -3685,7 +3703,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
|
|
|
3685
3703
|
Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
3686
3704
|
|
|
3687
3705
|
|
|
3688
|
-
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.
|
|
3706
|
+
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.38.0...master
|
|
3707
|
+
[2.38.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.37.0...v2.38.0
|
|
3689
3708
|
[2.37.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.36.0...v2.37.0
|
|
3690
3709
|
[2.36.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.35.0...v2.36.0
|
|
3691
3710
|
[2.35.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.34.0...v2.35.0
|
|
@@ -5454,6 +5473,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5454
5473
|
[#5834]: https://github.com/DataDog/dd-trace-rb/issues/5834
|
|
5455
5474
|
[#5836]: https://github.com/DataDog/dd-trace-rb/issues/5836
|
|
5456
5475
|
[#5844]: https://github.com/DataDog/dd-trace-rb/issues/5844
|
|
5476
|
+
[#5845]: https://github.com/DataDog/dd-trace-rb/issues/5845
|
|
5457
5477
|
[#5857]: https://github.com/DataDog/dd-trace-rb/issues/5857
|
|
5458
5478
|
[#5872]: https://github.com/DataDog/dd-trace-rb/issues/5872
|
|
5459
5479
|
[#5877]: https://github.com/DataDog/dd-trace-rb/issues/5877
|
|
@@ -5470,12 +5490,20 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5470
5490
|
[#5945]: https://github.com/DataDog/dd-trace-rb/issues/5945
|
|
5471
5491
|
[#5954]: https://github.com/DataDog/dd-trace-rb/issues/5954
|
|
5472
5492
|
[#5955]: https://github.com/DataDog/dd-trace-rb/issues/5955
|
|
5493
|
+
[#5960]: https://github.com/DataDog/dd-trace-rb/issues/5960
|
|
5473
5494
|
[#5970]: https://github.com/DataDog/dd-trace-rb/issues/5970
|
|
5474
5495
|
[#5973]: https://github.com/DataDog/dd-trace-rb/issues/5973
|
|
5475
5496
|
[#5975]: https://github.com/DataDog/dd-trace-rb/issues/5975
|
|
5497
|
+
[#5981]: https://github.com/DataDog/dd-trace-rb/issues/5981
|
|
5476
5498
|
[#5987]: https://github.com/DataDog/dd-trace-rb/issues/5987
|
|
5477
5499
|
[#5990]: https://github.com/DataDog/dd-trace-rb/issues/5990
|
|
5478
5500
|
[#5991]: https://github.com/DataDog/dd-trace-rb/issues/5991
|
|
5501
|
+
[#5993]: https://github.com/DataDog/dd-trace-rb/issues/5993
|
|
5502
|
+
[#6014]: https://github.com/DataDog/dd-trace-rb/issues/6014
|
|
5503
|
+
[#6020]: https://github.com/DataDog/dd-trace-rb/issues/6020
|
|
5504
|
+
[#6022]: https://github.com/DataDog/dd-trace-rb/issues/6022
|
|
5505
|
+
[#6023]: https://github.com/DataDog/dd-trace-rb/issues/6023
|
|
5506
|
+
[#6047]: https://github.com/DataDog/dd-trace-rb/issues/6047
|
|
5479
5507
|
[@AdrianLC]: https://github.com/AdrianLC
|
|
5480
5508
|
[@Azure7111]: https://github.com/Azure7111
|
|
5481
5509
|
[@BabyGroot]: https://github.com/BabyGroot
|
|
@@ -154,8 +154,6 @@ typedef struct {
|
|
|
154
154
|
unsigned int signal_handler_enqueued_sample;
|
|
155
155
|
// How many times we prepared a sample (sampled directly) from the signal handler
|
|
156
156
|
unsigned int signal_handler_prepared_sample;
|
|
157
|
-
// How many times the signal handler was called from the wrong thread
|
|
158
|
-
unsigned int signal_handler_wrong_thread;
|
|
159
157
|
// How many times we actually tried to interrupt a thread for sampling
|
|
160
158
|
unsigned int interrupt_thread_attempts;
|
|
161
159
|
|
|
@@ -537,6 +535,13 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
|
|
|
537
535
|
long now = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
538
536
|
discrete_dynamic_sampler_reset(&state->allocation_sampler, now);
|
|
539
537
|
|
|
538
|
+
// Reset per-thread state, if any. This ensures there's no leftover state from a previous profiler run that would
|
|
539
|
+
// affect or be included in samples taken by this profiler about to run.
|
|
540
|
+
//
|
|
541
|
+
// NOTE: This needs to be called before we enable any tracepoints or anything that could trigger samples (e.g.
|
|
542
|
+
// reset cannot be concurrent with any sampling activity)
|
|
543
|
+
thread_context_collector_reset_all_per_thread_contexts(state->thread_context_collector_instance);
|
|
544
|
+
|
|
540
545
|
// This write to a global is thread-safe BECAUSE we're still holding on to the global VM lock at this point
|
|
541
546
|
active_sampler_instance_state = state;
|
|
542
547
|
active_sampler_instance = instance;
|
|
@@ -637,19 +642,19 @@ static VALUE stop(VALUE self_instance, VALUE optional_exception, const char *opt
|
|
|
637
642
|
// We need to be careful not to change any state that may be observed OR to restore it if we do. For instance, if anything
|
|
638
643
|
// we do here can set `errno`, then we must be careful to restore the old `errno` after the fact.
|
|
639
644
|
static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext) {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
//
|
|
643
|
-
if (state == NULL) return;
|
|
644
|
-
|
|
645
|
+
// We must first check that we landed on the correct thread and can proceed.
|
|
646
|
+
// We must never touch the state before we confirm that we have landed on the thread that is holding the GVL on the main
|
|
647
|
+
// ractor as otherwise we may be concurrent with the profiler shutting down and removing its state.
|
|
645
648
|
if (
|
|
646
649
|
!ruby_native_thread_p() || // Not a Ruby thread
|
|
647
650
|
!is_current_thread_holding_the_gvl() || // Not safe to enqueue a sample from this thread
|
|
648
651
|
!ddtrace_rb_ractor_main_p() // We're not on the main Ractor; we currently don't support profiling non-main Ractors
|
|
649
|
-
)
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
652
|
+
) return;
|
|
653
|
+
|
|
654
|
+
cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
|
|
655
|
+
|
|
656
|
+
// This can potentially happen if the CpuAndWallTimeWorker was stopped while the signal delivery was happening; nothing to do
|
|
657
|
+
if (state == NULL) return;
|
|
653
658
|
|
|
654
659
|
// We assume there can be no concurrent nor nested calls to handle_sampling_signal because
|
|
655
660
|
// 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)
|
|
@@ -1104,7 +1109,6 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
|
|
|
1104
1109
|
ID2SYM(rb_intern("simulated_signal_delivery")), /* => */ UINT2NUM(state->stats.simulated_signal_delivery),
|
|
1105
1110
|
ID2SYM(rb_intern("signal_handler_enqueued_sample")), /* => */ UINT2NUM(state->stats.signal_handler_enqueued_sample),
|
|
1106
1111
|
ID2SYM(rb_intern("signal_handler_prepared_sample")), /* => */ UINT2NUM(state->stats.signal_handler_prepared_sample),
|
|
1107
|
-
ID2SYM(rb_intern("signal_handler_wrong_thread")), /* => */ UINT2NUM(state->stats.signal_handler_wrong_thread),
|
|
1108
1112
|
ID2SYM(rb_intern("interrupt_thread_attempts")), /* => */ UINT2NUM(state->stats.interrupt_thread_attempts),
|
|
1109
1113
|
|
|
1110
1114
|
// CPU Stats
|
|
@@ -1437,17 +1441,21 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
|
|
|
1437
1441
|
static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused) {
|
|
1438
1442
|
// Be very careful about touching the `state` here or doing anything at all:
|
|
1439
1443
|
// This function gets called without the GVL, and potentially from non-main Ractors!
|
|
1444
|
+
//
|
|
1445
|
+
// Note, even though these events can get called without the GVL, they synchronize-with enabling/disabling of
|
|
1446
|
+
// the hook that calls us using a read-write lock. Thus, disabling the hook cannot be concurrent with calling this function,
|
|
1447
|
+
// and once the disable finishes there can't be "late" calls into this function.
|
|
1440
1448
|
|
|
1441
1449
|
// The thread that this event is about may not be the current thread
|
|
1442
1450
|
// (as documented on rb_internal_thread_add_event_hook(), and this is notably the case for READY on Ruby 4.0),
|
|
1443
1451
|
// so be careful with native thread locals that are not directly tied to the thread object and the like.
|
|
1444
1452
|
|
|
1445
|
-
// On Ruby 3.2 the event does not carry the thread, but all events always fire on the event thread on Ruby 3.2.
|
|
1446
|
-
// However, during early thread startup rb_thread_current() can crash because the execution context (Fiber) isn't
|
|
1447
|
-
// stored in TLS yet; ruby_native_thread_p() guards against this.
|
|
1448
1453
|
#ifdef HAVE_RUBY_THREAD_STORAGE_API
|
|
1449
1454
|
VALUE target_thread = event_data->thread;
|
|
1450
1455
|
#else
|
|
1456
|
+
// On Ruby 3.2 the event does not carry the thread, but all events fire on the thread itself.
|
|
1457
|
+
// However, during early thread startup rb_thread_current() can crash because the execution context (Fiber) isn't
|
|
1458
|
+
// stored in TLS yet; ruby_native_thread_p() guards against this.
|
|
1451
1459
|
if (!ruby_native_thread_p()) return;
|
|
1452
1460
|
VALUE target_thread = rb_thread_current();
|
|
1453
1461
|
#endif
|
|
@@ -251,22 +251,26 @@ void sample_thread(
|
|
|
251
251
|
bool native_filenames_enabled,
|
|
252
252
|
st_table *native_filenames_cache
|
|
253
253
|
) {
|
|
254
|
+
if (buffer->max_frames != locations.len) {
|
|
255
|
+
// This shouldn't happen as thread_context_collector_reset_all_per_thread_contexts (which resizes every
|
|
256
|
+
// per-thread sampling buffer to match the collector's max_frames) must always be called before starting or
|
|
257
|
+
// restarting profiling.
|
|
258
|
+
raise_error(
|
|
259
|
+
rb_eRuntimeError,
|
|
260
|
+
"Unexpected: sampling buffer max_frames (%d) doesn't match locations len (%d)",
|
|
261
|
+
(int) buffer->max_frames,
|
|
262
|
+
(int) locations.len
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
254
266
|
// If we already prepared a sample, we use it below; if not, we prepare it now.
|
|
255
267
|
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
268
|
prepare_sample_thread(thread, buffer);
|
|
261
269
|
}
|
|
262
270
|
|
|
263
271
|
buffer->pending_sample = false;
|
|
264
272
|
int captured_frames = buffer->pending_sample_result;
|
|
265
273
|
|
|
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
|
-
|
|
270
274
|
if (captured_frames == PLACEHOLDER_STACK_IN_NATIVE_CODE) {
|
|
271
275
|
record_placeholder_stack_in_native_code(recorder_instance, values, labels);
|
|
272
276
|
return;
|
|
@@ -312,8 +316,13 @@ void sample_thread(
|
|
|
312
316
|
bool top_of_the_stack = i == top_of_stack_position;
|
|
313
317
|
|
|
314
318
|
if (buffer->stack_buffer[i].is_ruby_frame) {
|
|
315
|
-
VALUE
|
|
316
|
-
VALUE
|
|
319
|
+
VALUE iseq = buffer->stack_buffer[i].as.ruby_frame.iseq;
|
|
320
|
+
VALUE name = rb_iseq_base_label(iseq);
|
|
321
|
+
VALUE filename =
|
|
322
|
+
// Note: We saw crash reports from a customer from dereferencing a null pointer inside `rb_iseq_path`.
|
|
323
|
+
// It's not clear in what situation would the pathobj ever be NULL, yet it seems harmless from our side to
|
|
324
|
+
// skip this information if we're ever in such a situation.
|
|
325
|
+
pathobj_is_null(iseq) ? Qnil : rb_iseq_path(iseq);
|
|
317
326
|
|
|
318
327
|
name_slice = NIL_P(name) ? DDOG_CHARSLICE_C("") : char_slice_from_ruby_string(name);
|
|
319
328
|
filename_slice = NIL_P(filename) ? DDOG_CHARSLICE_C("") : char_slice_from_ruby_string(filename);
|
|
@@ -420,11 +429,6 @@ void sample_thread(
|
|
|
420
429
|
values,
|
|
421
430
|
labels
|
|
422
431
|
);
|
|
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
|
-
}
|
|
428
432
|
}
|
|
429
433
|
|
|
430
434
|
#if (defined(HAVE_DLADDR1) && HAVE_DLADDR1) || (defined(HAVE_DLADDR) && HAVE_DLADDR)
|
|
@@ -645,16 +649,7 @@ void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames) {
|
|
|
645
649
|
buffer->pending_sample_result = 0;
|
|
646
650
|
}
|
|
647
651
|
|
|
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
|
-
|
|
653
652
|
void sampling_buffer_free(sampling_buffer *buffer) {
|
|
654
|
-
if (buffer->max_frames == 0 || buffer->stack_buffer == NULL) {
|
|
655
|
-
raise_error(rb_eArgError, "sampling_buffer_free called with invalid buffer");
|
|
656
|
-
}
|
|
657
|
-
|
|
658
653
|
ruby_xfree(buffer->stack_buffer);
|
|
659
654
|
|
|
660
655
|
buffer->max_frames = 0;
|
|
@@ -42,7 +42,6 @@ bool prepare_sample_thread(VALUE thread, sampling_buffer *buffer);
|
|
|
42
42
|
|
|
43
43
|
uint16_t sampling_buffer_check_max_frames(int max_frames);
|
|
44
44
|
void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames);
|
|
45
|
-
void sampling_buffer_reinitialize(sampling_buffer *buffer, uint16_t max_frames);
|
|
46
45
|
void sampling_buffer_free(sampling_buffer *buffer);
|
|
47
46
|
void sampling_buffer_mark(sampling_buffer *buffer);
|
|
48
47
|
static inline bool sampling_buffer_needs_marking(sampling_buffer *buffer) {
|