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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +23 -15
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +19 -24
  5. data/ext/datadog_profiling_native_extension/collectors_stack.h +0 -1
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +155 -141
  7. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
  8. data/ext/datadog_profiling_native_extension/extconf.rb +0 -3
  9. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +5 -0
  10. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +2 -0
  11. data/ext/datadog_profiling_native_extension/time_helpers.h +12 -7
  12. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +5 -0
  13. data/lib/datadog/appsec/contrib/rails/gateway/request.rb +5 -0
  14. data/lib/datadog/core/configuration/agentless_settings_resolver.rb +2 -2
  15. data/lib/datadog/core/configuration/settings.rb +4 -4
  16. data/lib/datadog/core/configuration/supported_configurations.rb +1 -0
  17. data/lib/datadog/core/knuth_sampler.rb +3 -1
  18. data/lib/datadog/core/metrics/client.rb +1 -1
  19. data/lib/datadog/core/transport/http/builder.rb +1 -1
  20. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
  21. data/lib/datadog/core/utils/time.rb +5 -1
  22. data/lib/datadog/di/boot.rb +3 -0
  23. data/lib/datadog/di/capture_expression.rb +21 -0
  24. data/lib/datadog/di/capture_expression_evaluator.rb +71 -0
  25. data/lib/datadog/di/capture_limits.rb +41 -0
  26. data/lib/datadog/di/component.rb +2 -2
  27. data/lib/datadog/di/configuration.rb +6 -0
  28. data/lib/datadog/di/context.rb +9 -3
  29. data/lib/datadog/di/el/compiler.rb +1 -1
  30. data/lib/datadog/di/instrumenter.rb +34 -3
  31. data/lib/datadog/di/probe.rb +47 -2
  32. data/lib/datadog/di/probe_builder.rb +83 -1
  33. data/lib/datadog/di/probe_notification_builder.rb +48 -7
  34. data/lib/datadog/di/remote.rb +1 -1
  35. data/lib/datadog/di/serializer.rb +22 -10
  36. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +5 -0
  37. data/lib/datadog/profiling/collectors/thread_context.rb +9 -1
  38. data/lib/datadog/profiling/component.rb +17 -0
  39. data/lib/datadog/tracing/contrib/active_job/data_streams.rb +62 -0
  40. data/lib/datadog/tracing/contrib/active_job/patcher.rb +12 -0
  41. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +2 -3
  42. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +12 -12
  43. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  44. data/lib/datadog/tracing/contrib/sequel/utils.rb +2 -2
  45. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +1 -1
  46. data/lib/datadog/tracing/contrib/sinatra/framework.rb +1 -1
  47. data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
  48. data/lib/datadog/version.rb +1 -1
  49. metadata +9 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5435fe8fe48f8cd33010ad82ee5fe6f969077a86cdcca6a96cc9d8633c89eb0e
4
- data.tar.gz: 3681864939e4601b11bb94fa4b6647d1505efc621eae82ac9664fcc3819b34a3
3
+ metadata.gz: a10640edadba480a4014cb7861ed0eaa21dac0718709266f59fc1307b8fa64bd
4
+ data.tar.gz: cb0b0bcc5423c0e0398277c3535ea52b485967879cf0ede20eedcfb513f1389f
5
5
  SHA512:
6
- metadata.gz: de74ddeb7271f1ced1c21dde221b2bdd10fec9b7c9f8663cdf01f146c6a242d93dcde9ac7c63828e41045565f4870359469124a55ef999847f1d02416e36b919
7
- data.tar.gz: 2d7d44bb00879fa869c5ec630654632632fd74719c7cb276a9a5310606a952cf3eece0bd52c70b25a2a75cb6e69949a1f3ab9597badfb89e17175905b5e08f3e
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.37.0...master
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
- cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
641
-
642
- // This can potentially happen if the CpuAndWallTimeWorker was stopped while the signal delivery was happening; nothing to do
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
- state->stats.signal_handler_wrong_thread++;
651
- return;
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 name = rb_iseq_base_label(buffer->stack_buffer[i].as.ruby_frame.iseq);
316
- VALUE filename = rb_iseq_path(buffer->stack_buffer[i].as.ruby_frame.iseq);
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) {