ddtrace 1.22.0 → 1.23.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55da7739191e23ba6bb188ade9979c9dbd0a965bddfd2faedd6bf2c41b3a6e97
4
- data.tar.gz: 98bfd95dc6dd156d3d57f2ac22deb081a8e6fb8d3221eadf6e682f45e358ef7c
3
+ metadata.gz: 9e52d825fdd7cb0391c1e529a979862f24f8ec0412cbd1952f0ae21f3129b83f
4
+ data.tar.gz: 27ab67ddd6bd0c21a0f7de60c022c143ae2367051b4b1a744f33f01453c1d8e5
5
5
  SHA512:
6
- metadata.gz: 526f2826818f960ca18703a03cc0bde6313203a4e2dd03a696b8bd08f5a12cd963c370a8a9278d7387d81cf4445b849693fdbb8f91989f27f20d4f5bf55ad1a2
7
- data.tar.gz: 9b8d42db31a997fd7f03e383e0e2671730577336f5bdb28bf36afd63e775639f584abb8d2005265835fc175c29ae275f5cdfea79ba9afe02eb16a3a826f47d70
6
+ metadata.gz: b0c94e18051c3fe9522493daf7321ae77246d9e5cf6d5888fd3b9c565668c882e684d3156d58867104743c6769f07437c4bc68f69f4884d0609c92279d1ea3bb
7
+ data.tar.gz: 83bab49e211de5749555906b2f59b0ee8cb8a501c2363ea625a52e6f2c318714be26dc0226e51aabfd8461b96f3036b15a856eb372729ce69bd29df7baa06ef8
data/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.23.3] - 2024-07-01
6
+
7
+ ### Added
8
+
9
+ * Add post install message about 2.x upgrade ([#3723][])
10
+
11
+ ### Fixed
12
+
13
+ * Fix telemetry events blocking main thread ([#3740][])
14
+ * Fix deadlock from telemetry threads ([#3745][])
15
+
16
+ ## [1.23.2] - 2024-06-13
17
+
18
+ ### Fixed
19
+
20
+ * Profiling: Fix rpath for linking to libdatadog when loading from extension dir ([#3683][])
21
+
22
+ ## [1.23.1] - 2024-06-06
23
+
24
+ ### Fixed
25
+
26
+ * AppSec: Fix undefined method error when Tracing disabled ([#3650][])
27
+
28
+ ## [1.23.0] - 2024-05-09
29
+
30
+ ### Added
31
+
32
+ * Profiling: Enable endpoint profiling for Sidekiq and similar background job processors ([#3619][])
33
+
34
+ ### Fixed
35
+
36
+ * Fix no such file or directory issue when using single step instrumentation ([#3623][])
37
+ * Fix error during telemetry debug logging attempt ([#3618][])
38
+
5
39
  ## [1.22.0] - 2024-04-16
6
40
 
7
41
  ### Added
@@ -2803,7 +2837,11 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
2803
2837
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
2804
2838
 
2805
2839
 
2806
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.22.0...master
2840
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.23.3...1.x-stable
2841
+ [1.23.3]: https://github.com/DataDog/dd-trace-rb/compare/v1.23.2...v1.23.3
2842
+ [1.23.2]: https://github.com/DataDog/dd-trace-rb/compare/v1.23.1...v1.23.2
2843
+ [1.23.1]: https://github.com/DataDog/dd-trace-rb/compare/v1.23.0...v1.23.1
2844
+ [1.23.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.22.0...v1.23.0
2807
2845
  [1.22.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.1...v1.22.0
2808
2846
  [2.0.0.beta1]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.1...v2.0.0.beta1
2809
2847
  [1.21.1]: https://github.com/DataDog/dd-trace-rb/compare/v1.21.0...v1.21.1
@@ -4111,6 +4149,12 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4111
4149
  [#3582]: https://github.com/DataDog/dd-trace-rb/issues/3582
4112
4150
  [#3585]: https://github.com/DataDog/dd-trace-rb/issues/3585
4113
4151
  [#3587]: https://github.com/DataDog/dd-trace-rb/issues/3587
4152
+ [#3618]: https://github.com/DataDog/dd-trace-rb/issues/3618
4153
+ [#3619]: https://github.com/DataDog/dd-trace-rb/issues/3619
4154
+ [#3623]: https://github.com/DataDog/dd-trace-rb/issues/3623
4155
+ [#3650]: https://github.com/DataDog/dd-trace-rb/issues/3650
4156
+ [#3683]: https://github.com/DataDog/dd-trace-rb/issues/3683
4157
+ [#3745]: https://github.com/DataDog/dd-trace-rb/issues/3745
4114
4158
  [@AdrianLC]: https://github.com/AdrianLC
4115
4159
  [@Azure7111]: https://github.com/Azure7111
4116
4160
  [@BabyGroot]: https://github.com/BabyGroot
@@ -4262,4 +4306,4 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4262
4306
  [@y-yagi]: https://github.com/y-yagi
4263
4307
  [@yujideveloper]: https://github.com/yujideveloper
4264
4308
  [@yukimurasawa]: https://github.com/yukimurasawa
4265
- [@zachmccormick]: https://github.com/zachmccormick
4309
+ [@zachmccormick]: https://github.com/zachmccormick
@@ -929,18 +929,6 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
929
929
  struct cpu_and_wall_time_worker_state *state;
930
930
  TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
931
931
 
932
- VALUE pretty_cpu_sampling_time_ns_min = state->stats.cpu_sampling_time_ns_min == UINT64_MAX ? Qnil : ULL2NUM(state->stats.cpu_sampling_time_ns_min);
933
- VALUE pretty_cpu_sampling_time_ns_max = state->stats.cpu_sampling_time_ns_max == 0 ? Qnil : ULL2NUM(state->stats.cpu_sampling_time_ns_max);
934
- VALUE pretty_cpu_sampling_time_ns_total = state->stats.cpu_sampling_time_ns_total == 0 ? Qnil : ULL2NUM(state->stats.cpu_sampling_time_ns_total);
935
- VALUE pretty_cpu_sampling_time_ns_avg =
936
- state->stats.cpu_sampled == 0 ? Qnil : DBL2NUM(((double) state->stats.cpu_sampling_time_ns_total) / state->stats.cpu_sampled);
937
-
938
- VALUE pretty_allocation_sampling_time_ns_min = state->stats.allocation_sampling_time_ns_min == UINT64_MAX ? Qnil : ULL2NUM(state->stats.allocation_sampling_time_ns_min);
939
- VALUE pretty_allocation_sampling_time_ns_max = state->stats.allocation_sampling_time_ns_max == 0 ? Qnil : ULL2NUM(state->stats.allocation_sampling_time_ns_max);
940
- VALUE pretty_allocation_sampling_time_ns_total = state->stats.allocation_sampling_time_ns_total == 0 ? Qnil : ULL2NUM(state->stats.allocation_sampling_time_ns_total);
941
- VALUE pretty_allocation_sampling_time_ns_avg =
942
- state->stats.allocation_sampled == 0 ? Qnil : DBL2NUM(((double) state->stats.allocation_sampling_time_ns_total) / state->stats.allocation_sampled);
943
-
944
932
  unsigned long total_cpu_samples_attempted = state->stats.cpu_sampled + state->stats.cpu_skipped;
945
933
  VALUE effective_cpu_sample_rate =
946
934
  total_cpu_samples_attempted == 0 ? Qnil : DBL2NUM(((double) state->stats.cpu_sampled) / total_cpu_samples_attempted);
@@ -968,19 +956,19 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
968
956
  ID2SYM(rb_intern("cpu_sampled")), /* => */ UINT2NUM(state->stats.cpu_sampled),
969
957
  ID2SYM(rb_intern("cpu_skipped")), /* => */ UINT2NUM(state->stats.cpu_skipped),
970
958
  ID2SYM(rb_intern("cpu_effective_sample_rate")), /* => */ effective_cpu_sample_rate,
971
- ID2SYM(rb_intern("cpu_sampling_time_ns_min")), /* => */ pretty_cpu_sampling_time_ns_min,
972
- ID2SYM(rb_intern("cpu_sampling_time_ns_max")), /* => */ pretty_cpu_sampling_time_ns_max,
973
- ID2SYM(rb_intern("cpu_sampling_time_ns_total")), /* => */ pretty_cpu_sampling_time_ns_total,
974
- ID2SYM(rb_intern("cpu_sampling_time_ns_avg")), /* => */ pretty_cpu_sampling_time_ns_avg,
959
+ ID2SYM(rb_intern("cpu_sampling_time_ns_min")), /* => */ RUBY_NUM_OR_NIL(state->stats.cpu_sampling_time_ns_min, != UINT64_MAX, ULL2NUM),
960
+ ID2SYM(rb_intern("cpu_sampling_time_ns_max")), /* => */ RUBY_NUM_OR_NIL(state->stats.cpu_sampling_time_ns_max, > 0, ULL2NUM),
961
+ ID2SYM(rb_intern("cpu_sampling_time_ns_total")), /* => */ RUBY_NUM_OR_NIL(state->stats.cpu_sampling_time_ns_total, > 0, ULL2NUM),
962
+ ID2SYM(rb_intern("cpu_sampling_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats.cpu_sampling_time_ns_total, state->stats.cpu_sampled),
975
963
 
976
964
  // Allocation stats
977
965
  ID2SYM(rb_intern("allocation_sampled")), /* => */ state->allocation_profiling_enabled ? ULONG2NUM(state->stats.allocation_sampled) : Qnil,
978
966
  ID2SYM(rb_intern("allocation_skipped")), /* => */ state->allocation_profiling_enabled ? ULONG2NUM(state->stats.allocation_skipped) : Qnil,
979
967
  ID2SYM(rb_intern("allocation_effective_sample_rate")), /* => */ effective_allocation_sample_rate,
980
- ID2SYM(rb_intern("allocation_sampling_time_ns_min")), /* => */ pretty_allocation_sampling_time_ns_min,
981
- ID2SYM(rb_intern("allocation_sampling_time_ns_max")), /* => */ pretty_allocation_sampling_time_ns_max,
982
- ID2SYM(rb_intern("allocation_sampling_time_ns_total")), /* => */ pretty_allocation_sampling_time_ns_total,
983
- ID2SYM(rb_intern("allocation_sampling_time_ns_avg")), /* => */ pretty_allocation_sampling_time_ns_avg,
968
+ ID2SYM(rb_intern("allocation_sampling_time_ns_min")), /* => */ RUBY_NUM_OR_NIL(state->stats.allocation_sampling_time_ns_min, != UINT64_MAX, ULL2NUM),
969
+ ID2SYM(rb_intern("allocation_sampling_time_ns_max")), /* => */ RUBY_NUM_OR_NIL(state->stats.allocation_sampling_time_ns_max, > 0, ULL2NUM),
970
+ ID2SYM(rb_intern("allocation_sampling_time_ns_total")), /* => */ RUBY_NUM_OR_NIL(state->stats.allocation_sampling_time_ns_total, > 0, ULL2NUM),
971
+ ID2SYM(rb_intern("allocation_sampling_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats.allocation_sampling_time_ns_total, state->stats.allocation_sampled),
984
972
  ID2SYM(rb_intern("allocation_sampler_snapshot")), /* => */ allocation_sampler_snapshot,
985
973
  ID2SYM(rb_intern("allocations_during_sample")), /* => */ state->allocation_profiling_enabled ? UINT2NUM(state->stats.allocations_during_sample) : Qnil,
986
974
  };
@@ -217,7 +217,7 @@ static long thread_id_for(VALUE thread);
217
217
  static VALUE _native_stats(VALUE self, VALUE collector_instance);
218
218
  static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
219
219
  static void trace_identifiers_for(struct thread_context_collector_state *state, VALUE thread, struct trace_identifiers *trace_identifiers_result);
220
- static bool should_collect_resource(VALUE root_span_type);
220
+ static bool should_collect_resource(VALUE root_span);
221
221
  static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
222
222
  static VALUE thread_list(struct thread_context_collector_state *state);
223
223
  static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object);
@@ -1146,10 +1146,7 @@ static void trace_identifiers_for(struct thread_context_collector_state *state,
1146
1146
 
1147
1147
  trace_identifiers_result->valid = true;
1148
1148
 
1149
- if (!state->endpoint_collection_enabled) return;
1150
-
1151
- VALUE root_span_type = rb_ivar_get(root_span, at_type_id /* @type */);
1152
- if (root_span_type == Qnil || !should_collect_resource(root_span_type)) return;
1149
+ if (!state->endpoint_collection_enabled || !should_collect_resource(root_span)) return;
1153
1150
 
1154
1151
  VALUE trace_resource = rb_ivar_get(active_trace, at_resource_id /* @resource */);
1155
1152
  if (RB_TYPE_P(trace_resource, T_STRING)) {
@@ -1160,21 +1157,32 @@ static void trace_identifiers_for(struct thread_context_collector_state *state,
1160
1157
  }
1161
1158
  }
1162
1159
 
1163
- // We only collect the resource for spans of types:
1160
+ // We opt-in to collecting the resource for spans of types:
1164
1161
  // * 'web', for web requests
1165
- // * proxy', used by the rack integration with request_queuing: true (e.g. also represents a web request)
1162
+ // * 'proxy', used by the rack integration with request_queuing: true (e.g. also represents a web request)
1163
+ // * 'worker', used for sidekiq and similar background job processors
1166
1164
  //
1167
- // NOTE: Currently we're only interested in HTTP service endpoints. Over time, this list may be expanded.
1165
+ // Over time, this list may be expanded.
1168
1166
  // Resources MUST NOT include personal identifiable information (PII); this should not be the case with
1169
1167
  // ddtrace integrations, but worth mentioning just in case :)
1170
- static bool should_collect_resource(VALUE root_span_type) {
1168
+ static bool should_collect_resource(VALUE root_span) {
1169
+ VALUE root_span_type = rb_ivar_get(root_span, at_type_id /* @type */);
1170
+ if (root_span_type == Qnil) return false;
1171
1171
  ENFORCE_TYPE(root_span_type, T_STRING);
1172
1172
 
1173
1173
  int root_span_type_length = RSTRING_LEN(root_span_type);
1174
1174
  const char *root_span_type_value = StringValuePtr(root_span_type);
1175
1175
 
1176
- return (root_span_type_length == strlen("web") && (memcmp("web", root_span_type_value, strlen("web")) == 0)) ||
1176
+ bool is_web_request =
1177
+ (root_span_type_length == strlen("web") && (memcmp("web", root_span_type_value, strlen("web")) == 0)) ||
1177
1178
  (root_span_type_length == strlen("proxy") && (memcmp("proxy", root_span_type_value, strlen("proxy")) == 0));
1179
+
1180
+ if (is_web_request) return true;
1181
+
1182
+ bool is_worker_request =
1183
+ (root_span_type_length == strlen("worker") && (memcmp("worker", root_span_type_value, strlen("worker")) == 0));
1184
+
1185
+ return is_worker_request;
1178
1186
  }
1179
1187
 
1180
1188
  // After the Ruby VM forks, this method gets called in the child process to clean up any leftover state from the parent.
@@ -225,13 +225,15 @@ unless have_type('atomic_int', ['stdatomic.h'])
225
225
  skip_building_extension!(Datadog::Profiling::NativeExtensionHelpers::Supported::COMPILER_ATOMIC_MISSING)
226
226
  end
227
227
 
228
- # See comments on the helper method being used for why we need to additionally set this.
228
+ # See comments on the helper methods being used for why we need to additionally set this.
229
229
  # The extremely excessive escaping around ORIGIN below seems to be correct and was determined after a lot of
230
230
  # experimentation. We need to get these special characters across a lot of tools untouched...
231
- $LDFLAGS += \
232
- ' -Wl,-rpath,$$$\\\\{ORIGIN\\}/' \
233
- "#{Datadog::Profiling::NativeExtensionHelpers.libdatadog_folder_relative_to_native_lib_folder}"
234
- Logging.message("[ddtrace] After pkg-config $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
231
+ extra_relative_rpaths = [
232
+ Datadog::Profiling::NativeExtensionHelpers.libdatadog_folder_relative_to_native_lib_folder,
233
+ *Datadog::Profiling::NativeExtensionHelpers.libdatadog_folder_relative_to_ruby_extensions_folders,
234
+ ]
235
+ extra_relative_rpaths.each { |folder| $LDFLAGS += " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{folder.to_str}" }
236
+ Logging.message("[datadog] After pkg-config $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
235
237
 
236
238
  # Tag the native extension library with the Ruby version and Ruby platform.
237
239
  # This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
@@ -158,6 +158,13 @@ struct heap_recorder {
158
158
 
159
159
  // Sampling state
160
160
  uint num_recordings_skipped;
161
+
162
+ struct stats_last_update {
163
+ size_t objects_alive;
164
+ size_t objects_dead;
165
+ size_t objects_skipped;
166
+ size_t objects_frozen;
167
+ } stats_last_update;
161
168
  };
162
169
  static heap_record* get_or_create_heap_record(heap_recorder*, ddog_prof_Slice_Location);
163
170
  static void cleanup_heap_record_if_unused(heap_recorder*, heap_record*);
@@ -372,6 +379,9 @@ void heap_recorder_prepare_iteration(heap_recorder *heap_recorder) {
372
379
  rb_raise(rb_eRuntimeError, "New heap recorder iteration prepared without the previous one having been finished.");
373
380
  }
374
381
 
382
+ // Reset last update stats, we'll be building them from scratch during the st_foreach call below
383
+ heap_recorder->stats_last_update = (struct stats_last_update) {};
384
+
375
385
  st_foreach(heap_recorder->object_records, st_object_record_update, (st_data_t) heap_recorder);
376
386
 
377
387
  heap_recorder->object_records_snapshot = st_copy(heap_recorder->object_records);
@@ -427,6 +437,22 @@ bool heap_recorder_for_each_live_object(
427
437
  return true;
428
438
  }
429
439
 
440
+ VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder) {
441
+ VALUE arguments[] = {
442
+ ID2SYM(rb_intern("num_object_records")), /* => */ LONG2NUM(heap_recorder->object_records->num_entries),
443
+ ID2SYM(rb_intern("num_heap_records")), /* => */ LONG2NUM(heap_recorder->heap_records->num_entries),
444
+
445
+ // Stats as of last update
446
+ ID2SYM(rb_intern("last_update_objects_alive")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_alive),
447
+ ID2SYM(rb_intern("last_update_objects_dead")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_dead),
448
+ ID2SYM(rb_intern("last_update_objects_skipped")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_skipped),
449
+ ID2SYM(rb_intern("last_update_objects_frozen")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_frozen),
450
+ };
451
+ VALUE hash = rb_hash_new();
452
+ for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
453
+ return hash;
454
+ }
455
+
430
456
  void heap_recorder_testonly_assert_hash_matches(ddog_prof_Slice_Location locations) {
431
457
  heap_stack *stack = heap_stack_new(locations);
432
458
  heap_record_key stack_based_key = (heap_record_key) {
@@ -497,12 +523,14 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
497
523
  // no point checking for liveness or updating its size, so exit early.
498
524
  // NOTE: This means that there should be an equivalent check during actual
499
525
  // iteration otherwise we'd iterate/expose stale object data.
526
+ recorder->stats_last_update.objects_skipped++;
500
527
  return ST_CONTINUE;
501
528
  }
502
529
 
503
530
  if (!ruby_ref_from_id(LONG2NUM(obj_id), &ref)) {
504
531
  // Id no longer associated with a valid ref. Need to delete this object record!
505
532
  on_committed_object_record_cleanup(recorder, record);
533
+ recorder->stats_last_update.objects_dead++;
506
534
  return ST_DELETE;
507
535
  }
508
536
 
@@ -537,6 +565,7 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
537
565
  RB_FL_SET(ref, RUBY_FL_SEEN_OBJ_ID);
538
566
 
539
567
  on_committed_object_record_cleanup(recorder, record);
568
+ recorder->stats_last_update.objects_dead++;
540
569
  return ST_DELETE;
541
570
  }
542
571
 
@@ -550,6 +579,11 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
550
579
  record->object_data.is_frozen = RB_OBJ_FROZEN(ref);
551
580
  }
552
581
 
582
+ recorder->stats_last_update.objects_alive++;
583
+ if (record->object_data.is_frozen) {
584
+ recorder->stats_last_update.objects_frozen++;
585
+ }
586
+
553
587
  return ST_CONTINUE;
554
588
  }
555
589
 
@@ -767,9 +801,10 @@ void object_record_free(object_record *record) {
767
801
 
768
802
  VALUE object_record_inspect(object_record *record) {
769
803
  heap_frame top_frame = record->heap_record->stack->frames[0];
770
- VALUE inspect = rb_sprintf("obj_id=%ld weight=%d size=%zu location=%s:%d alloc_gen=%zu gen_age=%zu ",
771
- record->obj_id, record->object_data.weight, record->object_data.size, top_frame.filename,
772
- (int) top_frame.line, record->object_data.alloc_gen, record->object_data.gen_age);
804
+ live_object_data object_data = record->object_data;
805
+ VALUE inspect = rb_sprintf("obj_id=%ld weight=%d size=%zu location=%s:%d alloc_gen=%zu gen_age=%zu frozen=%d ",
806
+ record->obj_id, object_data.weight, object_data.size, top_frame.filename,
807
+ (int) top_frame.line, object_data.alloc_gen, object_data.gen_age, object_data.is_frozen);
773
808
 
774
809
  const char *class = record->object_data.class;
775
810
  if (class != NULL) {
@@ -150,6 +150,11 @@ bool heap_recorder_for_each_live_object(
150
150
  bool (*for_each_callback)(heap_recorder_iteration_data data, void* extra_arg),
151
151
  void *for_each_callback_extra_arg);
152
152
 
153
+ // Return a Ruby hash containing a snapshot of this recorder's interesting state at calling time.
154
+ // WARN: This allocates in the Ruby VM and therefore should not be called without the
155
+ // VM lock or during GC.
156
+ VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder);
157
+
153
158
  // v--- TEST-ONLY APIs ---v
154
159
 
155
160
  // Assert internal hashing logic is valid for the provided locations and its
@@ -67,6 +67,52 @@ module Datadog
67
67
  Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(profiling_native_lib_folder)).to_s
68
68
  end
69
69
 
70
+ # In https://github.com/DataDog/dd-trace-rb/pull/3582 we got a report of a customer for which the native extension
71
+ # only got installed into the extensions folder.
72
+ #
73
+ # But then this fix was not enough to fully get them moving because then they started to see the issue from
74
+ # https://github.com/DataDog/dd-trace-rb/issues/2067 / https://github.com/DataDog/dd-trace-rb/pull/2125 :
75
+ #
76
+ # > Profiling was requested but is not supported, profiling disabled: There was an error loading the profiling
77
+ # > native extension due to 'RuntimeError Failure to load datadog_profiling_native_extension.3.2.2_x86_64-linux
78
+ # > due to libdatadog_profiling.so: cannot open shared object file: No such file or directory
79
+ #
80
+ # The problem is that when loading the native extension from the extensions directory, the relative rpath we add
81
+ # with the #libdatadog_folder_relative_to_native_lib_folder helper above is not correct, we need to add a relative
82
+ # rpath to the extensions directory.
83
+ #
84
+ # So how do we find the full path where the native extension is placed?
85
+ # * From https://github.com/ruby/ruby/blob/83f02d42e0a3c39661dc99c049ab9a70ff227d5b/lib/bundler/runtime.rb#L166
86
+ # `extension_dirs = Dir["#{Gem.dir}/extensions/*/*/*"] + Dir["#{Gem.dir}/bundler/gems/extensions/*/*/*"]`
87
+ # we get that's in one of two fixed subdirectories of `Gem.dir`
88
+ # * From https://github.com/ruby/ruby/blob/83f02d42e0a3c39661dc99c049ab9a70ff227d5b/lib/rubygems/basic_specification.rb#L111-L115
89
+ # we get the structure of the subdirectory (platform/extension_api_version/gem_and_version)
90
+ #
91
+ # Thus, `Gem.dir` of `/var/app/current/vendor/bundle/ruby/3.2.0` becomes (for instance)
92
+ # `/var/app/current/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/datadog-2.0.0/` or
93
+ # `/var/app/current/vendor/bundle/ruby/3.2.0/bundler/gems/extensions/x86_64-linux/3.2.0/datadog-2.0.0/`
94
+ #
95
+ # We then compute the relative path between these folders and the libdatadog folder, and use that as a relative path.
96
+ def self.libdatadog_folder_relative_to_ruby_extensions_folders(
97
+ gem_dir: Gem.dir,
98
+ libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder
99
+ )
100
+ return unless libdatadog_pkgconfig_folder
101
+
102
+ # For the purposes of calculating a folder relative to the other, we don't actually NEED to fill in the
103
+ # platform, extension_api_version and gem version. We're basically just after how many folders it is deep from
104
+ # the Gem.dir.
105
+ expected_ruby_extensions_folders = [
106
+ "#{gem_dir}/extensions/platform/extension_api_version/datadog_version/",
107
+ "#{gem_dir}/bundler/gems/extensions/platform/extension_api_version/datadog_version/",
108
+ ]
109
+ libdatadog_lib_folder = "#{libdatadog_pkgconfig_folder}/../"
110
+
111
+ expected_ruby_extensions_folders.map do |folder|
112
+ Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(folder)).to_s
113
+ end
114
+ end
115
+
70
116
  # Used to check if profiler is supported, including user-visible clear messages explaining why their
71
117
  # system may not be supported.
72
118
  module Supported
@@ -82,6 +82,9 @@ NORETURN(
82
82
  #define ENFORCE_SUCCESS_HELPER(expression, have_gvl) \
83
83
  { int result_syserr_errno = expression; if (RB_UNLIKELY(result_syserr_errno)) raise_syserr(result_syserr_errno, have_gvl, ADD_QUOTES(expression), __FILE__, __LINE__, __func__); }
84
84
 
85
+ #define RUBY_NUM_OR_NIL(val, condition, conv) ((val condition) ? conv(val) : Qnil)
86
+ #define RUBY_AVG_OR_NIL(total, count) ((count == 0) ? Qnil : DBL2NUM(((double) total) / count))
87
+
85
88
  // Called by ENFORCE_SUCCESS_HELPER; should not be used directly
86
89
  NORETURN(void raise_syserr(
87
90
  int syserr_errno,