datadog 2.42.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dfc982d67dbb768eb43d510dd6322ee484bc0a0fdacf20deb40196aa13821e51
4
- data.tar.gz: be416fd9f34cbdf7ead90fa03d273f1739e90008cb82b6a0d22d5f62f17e1660
3
+ metadata.gz: 154c126111f9b92cc59626264fc83b954b36317f96798bc33a0bcb7784516968
4
+ data.tar.gz: 4098225f4c88a0c3e31b069ca1a17231af4298ca149ce530d0dc4ccc9a8d8db5
5
5
  SHA512:
6
- metadata.gz: 80d39181feb3e02e4f022d06f0c6bab26e993f48521c2fb5e526d3b81a5b4a68e7069d92af90638700f2e0a0af24efbd0df63de311c7561cb02c1270d63cbaaa
7
- data.tar.gz: 44932c7f87c3a52e1790b44ae2c4f13ae63006360a0846c240ec0b6ac3ffe4b1c98ff33426b19f27f51ce5105770ab263870601ebe5b33788381debd85a04b37
6
+ metadata.gz: a566b1ab03efdf8c881ff7a6af93aff5fc946dc213ea1ccfabc554458ae733769ab98002ea14f039e3b2a19c150b2e208083e35fd0519f34b77ff239247412dd
7
+ data.tar.gz: 3a9f8574b7325c9f6e77da1a7f8ad65b8b6a74426b9dab829db84c8adbe8757c1f09d57d8232085c51d074c60e4a77e017cbdd9bfaa5d9f84efba0ad66869866
data/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
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
+
5
22
  ## [2.42.0] - 2026-08-31
6
23
 
7
24
  ### Added
@@ -3776,7 +3793,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
3776
3793
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3777
3794
 
3778
3795
 
3779
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.42.0...master
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
3780
3798
  [2.42.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.41.0...v2.42.0
3781
3799
  [2.41.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.40.0...v2.41.0
3782
3800
  [2.40.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.39.0...v2.40.0
@@ -5610,6 +5628,10 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
5610
5628
  [#6232]: https://github.com/DataDog/dd-trace-rb/issues/6232
5611
5629
  [#6234]: https://github.com/DataDog/dd-trace-rb/issues/6234
5612
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
5613
5635
  [@AdrianLC]: https://github.com/AdrianLC
5614
5636
  [@Azure7111]: https://github.com/Azure7111
5615
5637
  [@BabyGroot]: https://github.com/BabyGroot
@@ -5765,4 +5787,4 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
5765
5787
  [@y-yagi]: https://github.com/y-yagi
5766
5788
  [@yujideveloper]: https://github.com/yujideveloper
5767
5789
  [@yukimurasawa]: https://github.com/yukimurasawa
5768
- [@zachmccormick]: https://github.com/zachmccormick
5790
+ [@zachmccormick]: https://github.com/zachmccormick
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
@@ -740,6 +740,8 @@ static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED si
740
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)
741
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
742
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
743
745
 
744
746
  state->stats.signal_handler_enqueued_sample++;
745
747
 
@@ -1279,11 +1281,19 @@ void *simulate_sampling_signal_delivery(DDTRACE_UNUSED void *_unused) {
1279
1281
  // This can potentially happen if the CpuAndWallTimeWorker was stopped while the IdleSamplingHelper was trying to execute this action
1280
1282
  if (state == NULL) return NULL;
1281
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
+
1282
1290
  state->stats.simulated_signal_delivery++;
1283
1291
 
1284
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
1285
1293
  handle_sampling_signal(0, NULL, NULL);
1286
1294
 
1295
+ if (!state->no_signals_workaround_enabled) unblock_sigprof_signal_handler_from_running_in_current_thread();
1296
+
1287
1297
  return NULL; // Unused
1288
1298
  }
1289
1299
 
@@ -888,7 +888,12 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
888
888
  }
889
889
 
890
890
  // Here we record the wall-time first and in on_gc_finish we record it second to try to avoid having wall-time be slightly < cpu-time
891
- thread_context->gc_tracking.wall_time_at_start_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
891
+ long wall_time_at_start_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
892
+
893
+ // If our start timestamp is not OK, we skip tracking this GC as well
894
+ if (wall_time_at_start_ns == 0) return;
895
+
896
+ thread_context->gc_tracking.wall_time_at_start_ns = wall_time_at_start_ns;
892
897
  thread_context->gc_tracking.cpu_time_at_start_ns = cpu_time_now_ns(thread_context);
893
898
  }
894
899
 
@@ -917,7 +922,8 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
917
922
  long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
918
923
  long wall_time_at_start_ns = thread_context->gc_tracking.wall_time_at_start_ns;
919
924
 
920
- if (cpu_time_at_start_ns == INVALID_TIME && wall_time_at_start_ns == INVALID_TIME) {
925
+ // Both fields are always set and cleared together, so checking one is enough
926
+ if (cpu_time_at_start_ns == INVALID_TIME) {
921
927
  // If this happened, it means that on_gc_start was either never called for the thread OR it was called but no thread
922
928
  // context existed at the time. The former can be the result of a bug, but since we can't distinguish them, we just
923
929
  // do nothing.
@@ -1443,9 +1449,6 @@ static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collec
1443
1449
  }
1444
1450
 
1445
1451
  static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, per_thread_context *thread_context) {
1446
- // If we didn't have a time for the previous sample, we use the current one
1447
- if (*time_at_previous_sample_ns == INVALID_TIME) *time_at_previous_sample_ns = current_time_ns;
1448
-
1449
1452
  // We don't expect to be sampling a thread (and thus updating these counters) while Ruby is doing GC (between
1450
1453
  // `thread_context_collector_on_gc_start` and `thread_context_collector_on_gc_finish`)
1451
1454
  if (thread_context->gc_tracking.cpu_time_at_start_ns != INVALID_TIME) {
@@ -1467,6 +1470,13 @@ static long update_time_since_previous_sample(long *time_at_previous_sample_ns,
1467
1470
  }
1468
1471
 
1469
1472
  static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns) {
1473
+ // A previous `cpu_time_now_ns` may have failed to read the clock and thus invalidated our baseline; in that case we
1474
+ // use the current time, so this sample gets no cpu-time and the next one gets an accurate delta.
1475
+ // Note wall-time never needs this: it is always seeded with a real value in `initialize_context`.
1476
+ if (thread_context->cpu_time_at_previous_sample_ns == INVALID_TIME) {
1477
+ thread_context->cpu_time_at_previous_sample_ns = current_cpu_time_ns;
1478
+ }
1479
+
1470
1480
  long elapsed_time_ns = update_time_since_previous_sample(
1471
1481
  &thread_context->cpu_time_at_previous_sample_ns,
1472
1482
  current_cpu_time_ns,
@@ -1193,6 +1193,19 @@ VALUE heap_recorder_testonly_record_id_for(heap_recorder *heap_recorder, VALUE o
1193
1193
  return context.result;
1194
1194
  }
1195
1195
 
1196
+ void heap_recorder_testonly_set_object_alloc_gen(heap_recorder *heap_recorder, long record_id, size_t alloc_gen) {
1197
+ if (heap_recorder == NULL) {
1198
+ raise_error(rb_eArgError, "heap_recorder is NULL");
1199
+ }
1200
+
1201
+ object_record *record = NULL;
1202
+ if (!st_lookup(heap_recorder->object_records, (st_data_t) record_id, (st_data_t *) &record)) {
1203
+ raise_error(rb_eArgError, "record_id %ld is not being tracked", record_id);
1204
+ }
1205
+
1206
+ record->object_data.alloc_gen = alloc_gen;
1207
+ }
1208
+
1196
1209
  void heap_recorder_testonly_exhaust_record_ids(heap_recorder *heap_recorder) {
1197
1210
  if (heap_recorder == NULL) {
1198
1211
  raise_error(rb_eArgError, "heap_recorder is NULL");
@@ -190,6 +190,10 @@ VALUE heap_recorder_testonly_is_object_recorded(heap_recorder *heap_recorder, lo
190
190
  // NOTE: This is a linear scan over the tracked objects; fine for the small numbers tests track.
191
191
  VALUE heap_recorder_testonly_record_id_for(heap_recorder *heap_recorder, VALUE obj);
192
192
 
193
+ // Overrides the GC gen a tracked object is considered to have been allocated in. This lets tests give an object an
194
+ // exact age, instead of relying on an exact number of GCs having run.
195
+ void heap_recorder_testonly_set_object_alloc_gen(heap_recorder *heap_recorder, long record_id, size_t alloc_gen);
196
+
193
197
  // Used to ensure that a GC actually triggers an update of the objects
194
198
  void heap_recorder_testonly_reset_last_update(heap_recorder *heap_recorder);
195
199
 
@@ -982,8 +982,15 @@ VALUE ddtrace_alloc_free_rb_mod_name(VALUE mod) {
982
982
  // Checking for '#' in the name is equivalent on older Rubies:
983
983
  // non-permanent names contain '#' from `#<Module:0x...>` or `#<Class:0x...>` prefixes.
984
984
  static bool has_permanent_classpath(DDTRACE_UNUSED VALUE mod, DDTRACE_UNUSED VALUE mod_name) {
985
- #if defined(RCLASS_PERMANENT_CLASSPATH_P) // 4.0+
986
- return RCLASS_PERMANENT_CLASSPATH_P(mod);
985
+ #if defined(RCLASS_EXT_PRIME) // 4.0+
986
+ // We can't actually use RCLASS_PERMANENT_CLASSPATH_P(mod) because that uses
987
+ // RCLASS_EXT_READABLE() which uses rb_current_box(), which is a private symbol:
988
+ // https://github.com/DataDog/dd-trace-rb/issues/6338
989
+ // Instead we use `RCLASS_EXT_PRIME(mod)->permanent_classpath`, which is equivalent for our usage:
990
+ // the two can only differ for boxable (= core) modules with no permanent name, i.e. metaclasses like
991
+ // String.singleton_class, and is_metaclass() already replaced those with the attached module, so we display
992
+ // `String.foo` and not `#<Class:String>#foo`. Same for the name in ddtrace_alloc_free_rb_mod_name().
993
+ return RCLASS_EXT_PRIME(mod)->permanent_classpath;
987
994
  #elif defined(HAVE_PERMANENT_CLASSPATH) // 3.3 - 3.4
988
995
  return RCLASS_EXT(mod)->permanent_classpath;
989
996
  #else // 3.2 and older
@@ -275,6 +275,7 @@ static VALUE _native_is_object_recorded(DDTRACE_UNUSED VALUE _self, VALUE record
275
275
  static VALUE _native_record_id_for(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE obj);
276
276
  static VALUE _native_heap_recorder_reset_last_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
277
277
  static VALUE _native_heap_recorder_exhaust_record_ids(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
278
+ static VALUE _native_heap_recorder_set_object_alloc_gen(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE record_id, VALUE alloc_gen);
278
279
  static VALUE _native_recorder_heap_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
279
280
  static VALUE _native_benchmark_intern(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE string, VALUE times, VALUE use_all);
280
281
  static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE_UNUSED VALUE _self);
@@ -313,6 +314,7 @@ void stack_recorder_init(VALUE profiling_module) {
313
314
  rb_define_singleton_method(testing_module, "_native_record_id_for", _native_record_id_for, 2);
314
315
  rb_define_singleton_method(testing_module, "_native_heap_recorder_reset_last_update", _native_heap_recorder_reset_last_update, 1);
315
316
  rb_define_singleton_method(testing_module, "_native_heap_recorder_exhaust_record_ids", _native_heap_recorder_exhaust_record_ids, 1);
317
+ rb_define_singleton_method(testing_module, "_native_heap_recorder_set_object_alloc_gen", _native_heap_recorder_set_object_alloc_gen, 3);
316
318
  rb_define_singleton_method(testing_module, "_native_recorder_heap_update", _native_recorder_heap_update, 1);
317
319
  rb_define_singleton_method(testing_module, "_native_benchmark_intern", _native_benchmark_intern, 4);
318
320
  rb_define_singleton_method(testing_module, "_native_test_managed_string_storage_produces_valid_profiles", _native_test_managed_string_storage_produces_valid_profiles, 0);
@@ -1065,6 +1067,17 @@ static VALUE _native_heap_recorder_exhaust_record_ids(DDTRACE_UNUSED VALUE _self
1065
1067
  return Qtrue;
1066
1068
  }
1067
1069
 
1070
+ static VALUE _native_heap_recorder_set_object_alloc_gen(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE record_id, VALUE alloc_gen) {
1071
+ ENFORCE_TYPE(record_id, T_FIXNUM);
1072
+ ENFORCE_TYPE(alloc_gen, T_FIXNUM);
1073
+
1074
+ stack_recorder_state *state;
1075
+ TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
1076
+
1077
+ heap_recorder_testonly_set_object_alloc_gen(state->heap_recorder, FIX2LONG(record_id), NUM2SIZET(alloc_gen));
1078
+ return Qtrue;
1079
+ }
1080
+
1068
1081
  static VALUE _native_heap_recorder_reset_last_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance) {
1069
1082
  stack_recorder_state *state;
1070
1083
  TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
@@ -16,11 +16,12 @@ typedef enum { RAISE_ON_FAILURE, DO_NOT_RAISE_ON_FAILURE } raise_on_failure_sett
16
16
  #define INVALID_TIME -1
17
17
 
18
18
  typedef struct {
19
+ // INVALID_TIME until the first conversion, and the only field used to decide if the state needs to be (re)computed
19
20
  long system_epoch_ns_reference;
20
21
  long delta_to_epoch_ns;
21
22
  } monotonic_to_system_epoch_state;
22
23
 
23
- #define MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER {.system_epoch_ns_reference = INVALID_TIME, .delta_to_epoch_ns = INVALID_TIME}
24
+ #define MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER {.system_epoch_ns_reference = INVALID_TIME}
24
25
 
25
26
  static inline long retrieve_clock_as_ns(clockid_t clock_id, raise_on_failure_setting raise_on_failure) {
26
27
  struct timespec clock_value;
@@ -24,6 +24,7 @@ static ID fiber_context_slot;
24
24
  DDTRACE_UNUSED static bool otel_context_enabled = false;
25
25
 
26
26
  static VALUE native_set(VALUE _self, VALUE trace_id, VALUE span_id, VALUE local_root_span_id);
27
+ static VALUE native_clear(VALUE _self);
27
28
  static VALUE native_supported_p(VALUE _self);
28
29
  static VALUE native_enable(VALUE _self);
29
30
  static VALUE native_read(VALUE _self);
@@ -35,6 +36,7 @@ void otel_thread_context_init(VALUE tracing_module) {
35
36
 
36
37
  rb_define_singleton_method(otel_thread_context_module, "_native_enable", native_enable, 0);
37
38
  rb_define_singleton_method(otel_thread_context_module, "_native_set", native_set, 3);
39
+ rb_define_singleton_method(otel_thread_context_module, "_native_clear", native_clear, 0);
38
40
  rb_define_singleton_method(otel_thread_context_module, "_native_supported?", native_supported_p, 0);
39
41
 
40
42
  VALUE testing_module = rb_define_module_under(otel_thread_context_module, "Testing");
@@ -204,6 +206,19 @@ static VALUE native_set(
204
206
  #endif
205
207
  }
206
208
 
209
+ static VALUE native_clear(DDTRACE_UNUSED VALUE _self) {
210
+ #ifdef __linux__
211
+ if (!otel_context_enabled) return Qfalse;
212
+
213
+ ddog_ThreadContextHandle *detached = ddog_otel_thread_ctx_detach();
214
+ rb_thread_local_aset(rb_thread_current(), fiber_context_slot, Qnil);
215
+
216
+ return detached == NULL ? Qfalse : Qtrue;
217
+ #else
218
+ return Qfalse;
219
+ #endif
220
+ }
221
+
207
222
  static VALUE native_read(DDTRACE_UNUSED VALUE _self) {
208
223
  #ifdef __linux__
209
224
  const uint8_t *raw = otel_thread_ctx_v1;