datadog 2.17.0 → 2.19.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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +63 -56
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +263 -76
  5. data/ext/datadog_profiling_native_extension/collectors_stack.h +20 -3
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +62 -12
  7. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
  8. data/ext/datadog_profiling_native_extension/extconf.rb +7 -0
  9. data/ext/datadog_profiling_native_extension/heap_recorder.c +239 -363
  10. data/ext/datadog_profiling_native_extension/heap_recorder.h +4 -6
  11. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +22 -0
  12. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -5
  13. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +38 -26
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +6 -4
  15. data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -13
  16. data/ext/datadog_profiling_native_extension/ruby_helpers.h +3 -11
  17. data/ext/datadog_profiling_native_extension/stack_recorder.c +154 -57
  18. data/ext/libdatadog_api/extconf.rb +2 -2
  19. data/ext/libdatadog_api/library_config.c +54 -12
  20. data/ext/libdatadog_api/library_config.h +6 -0
  21. data/ext/libdatadog_api/process_discovery.c +2 -7
  22. data/ext/libdatadog_extconf_helpers.rb +1 -1
  23. data/lib/datadog/appsec/api_security/lru_cache.rb +9 -2
  24. data/lib/datadog/appsec/api_security/route_extractor.rb +71 -0
  25. data/lib/datadog/appsec/api_security/sampler.rb +59 -0
  26. data/lib/datadog/appsec/api_security.rb +14 -0
  27. data/lib/datadog/appsec/assets/waf_rules/recommended.json +257 -85
  28. data/lib/datadog/appsec/assets/waf_rules/strict.json +10 -78
  29. data/lib/datadog/appsec/component.rb +30 -54
  30. data/lib/datadog/appsec/configuration/settings.rb +60 -2
  31. data/lib/datadog/appsec/context.rb +6 -6
  32. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +1 -1
  33. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +27 -16
  34. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +1 -1
  35. data/lib/datadog/appsec/processor/rule_loader.rb +5 -6
  36. data/lib/datadog/appsec/remote.rb +15 -55
  37. data/lib/datadog/appsec/security_engine/engine.rb +194 -0
  38. data/lib/datadog/appsec/security_engine/runner.rb +10 -11
  39. data/lib/datadog/appsec.rb +4 -7
  40. data/lib/datadog/core/configuration/agent_settings.rb +52 -0
  41. data/lib/datadog/core/configuration/agent_settings_resolver.rb +1 -43
  42. data/lib/datadog/core/configuration/components.rb +2 -4
  43. data/lib/datadog/core/configuration/option.rb +9 -9
  44. data/lib/datadog/core/configuration/settings.rb +42 -10
  45. data/lib/datadog/core/configuration/stable_config.rb +1 -2
  46. data/lib/datadog/core/crashtracking/tag_builder.rb +4 -22
  47. data/lib/datadog/core/process_discovery/tracer_memfd.rb +15 -0
  48. data/lib/datadog/core/process_discovery.rb +5 -1
  49. data/lib/datadog/core/remote/configuration/repository.rb +12 -0
  50. data/lib/datadog/core/tag_builder.rb +56 -0
  51. data/lib/datadog/core/telemetry/component.rb +8 -4
  52. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +1 -0
  53. data/lib/datadog/core/telemetry/event/app_started.rb +148 -40
  54. data/lib/datadog/core/telemetry/logger.rb +5 -4
  55. data/lib/datadog/core/telemetry/logging.rb +11 -5
  56. data/lib/datadog/core/transport/http/adapters/net.rb +17 -2
  57. data/lib/datadog/core/transport/http/builder.rb +2 -2
  58. data/lib/datadog/core/transport/http/env.rb +8 -0
  59. data/lib/datadog/core/utils.rb +7 -0
  60. data/lib/datadog/di/instrumenter.rb +48 -5
  61. data/lib/datadog/di/probe_notification_builder.rb +37 -42
  62. data/lib/datadog/di/probe_notifier_worker.rb +9 -1
  63. data/lib/datadog/di/serializer.rb +10 -2
  64. data/lib/datadog/di/transport/http/input.rb +10 -0
  65. data/lib/datadog/di/transport/input.rb +10 -2
  66. data/lib/datadog/di.rb +0 -6
  67. data/lib/datadog/kit/appsec/events/v2.rb +195 -0
  68. data/lib/datadog/profiling/collectors/code_provenance.rb +17 -8
  69. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +6 -0
  70. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -0
  71. data/lib/datadog/profiling/collectors/info.rb +41 -0
  72. data/lib/datadog/profiling/collectors/thread_context.rb +16 -1
  73. data/lib/datadog/profiling/component.rb +8 -9
  74. data/lib/datadog/profiling/exporter.rb +9 -3
  75. data/lib/datadog/profiling/ext.rb +0 -12
  76. data/lib/datadog/profiling/http_transport.rb +2 -2
  77. data/lib/datadog/profiling/profiler.rb +2 -0
  78. data/lib/datadog/profiling/scheduler.rb +2 -1
  79. data/lib/datadog/profiling/sequence_tracker.rb +44 -0
  80. data/lib/datadog/profiling/stack_recorder.rb +5 -5
  81. data/lib/datadog/profiling/tag_builder.rb +7 -37
  82. data/lib/datadog/profiling/tasks/setup.rb +2 -0
  83. data/lib/datadog/profiling.rb +1 -0
  84. data/lib/datadog/single_step_instrument.rb +9 -0
  85. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +15 -0
  86. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +19 -12
  87. data/lib/datadog/tracing/contrib/action_pack/ext.rb +2 -0
  88. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +7 -1
  89. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +13 -0
  90. data/lib/datadog/tracing/contrib/lograge/patcher.rb +4 -2
  91. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +16 -6
  92. data/lib/datadog/tracing/contrib/rails/patcher.rb +4 -1
  93. data/lib/datadog/tracing/contrib/rails/runner.rb +61 -40
  94. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +1 -0
  95. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +5 -2
  96. data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -1
  97. data/lib/datadog/tracing/span_event.rb +1 -1
  98. data/lib/datadog/tracing/span_operation.rb +22 -0
  99. data/lib/datadog/tracing/sync_writer.rb +1 -1
  100. data/lib/datadog/tracing/trace_operation.rb +12 -4
  101. data/lib/datadog/tracing/tracer.rb +6 -2
  102. data/lib/datadog/version.rb +1 -1
  103. data/lib/datadog.rb +7 -0
  104. metadata +14 -10
  105. data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -321
  106. data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -1023
  107. data/lib/datadog/appsec/processor/rule_merger.rb +0 -171
  108. data/lib/datadog/appsec/processor.rb +0 -107
@@ -34,7 +34,7 @@ typedef struct {
34
34
 
35
35
  // The class of the object that we're tracking.
36
36
  // NOTE: This is optional and will be set to NULL if not set.
37
- char* class;
37
+ ddog_prof_ManagedStringId class;
38
38
 
39
39
  // The GC allocation gen in which we saw this object being allocated.
40
40
  //
@@ -59,7 +59,7 @@ typedef struct {
59
59
  } heap_recorder_iteration_data;
60
60
 
61
61
  // Initialize a new heap recorder.
62
- heap_recorder* heap_recorder_new(void);
62
+ heap_recorder* heap_recorder_new(ddog_prof_ManagedStringStorage string_storage);
63
63
 
64
64
  // Free a previously initialized heap recorder.
65
65
  void heap_recorder_free(heap_recorder *heap_recorder);
@@ -164,10 +164,6 @@ VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder);
164
164
 
165
165
  // v--- TEST-ONLY APIs ---v
166
166
 
167
- // Assert internal hashing logic is valid for the provided locations and its
168
- // corresponding internal representations in heap recorder.
169
- void heap_recorder_testonly_assert_hash_matches(ddog_prof_Slice_Location locations);
170
-
171
167
  // Returns a Ruby string with a representation of internal data helpful to
172
168
  // troubleshoot issues such as unexpected test failures.
173
169
  VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder);
@@ -177,3 +173,5 @@ VALUE heap_recorder_testonly_is_object_recorded(heap_recorder *heap_recorder, VA
177
173
 
178
174
  // Used to ensure that a GC actually triggers an update of the objects
179
175
  void heap_recorder_testonly_reset_last_update(heap_recorder *heap_recorder);
176
+
177
+ void heap_recorder_testonly_benchmark_intern(heap_recorder *heap_recorder, ddog_CharSlice string, int times, bool use_all);
@@ -60,3 +60,25 @@ size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capa
60
60
  ddog_Error_drop(error);
61
61
  return error_msg_size;
62
62
  }
63
+
64
+ ddog_prof_ManagedStringId intern_or_raise(ddog_prof_ManagedStringStorage string_storage, ddog_CharSlice string) {
65
+ if (string.len == 0) return (ddog_prof_ManagedStringId) { 0 }; // Id 0 is always an empty string, no need to ask
66
+
67
+ ddog_prof_ManagedStringStorageInternResult intern_result = ddog_prof_ManagedStringStorage_intern(string_storage, string);
68
+ if (intern_result.tag == DDOG_PROF_MANAGED_STRING_STORAGE_INTERN_RESULT_ERR) {
69
+ rb_raise(rb_eRuntimeError, "Failed to intern string: %"PRIsVALUE, get_error_details_and_drop(&intern_result.err));
70
+ }
71
+ return intern_result.ok;
72
+ }
73
+
74
+ void intern_all_or_raise(
75
+ ddog_prof_ManagedStringStorage string_storage,
76
+ ddog_prof_Slice_CharSlice strings,
77
+ ddog_prof_ManagedStringId *output_ids,
78
+ uintptr_t output_ids_size
79
+ ) {
80
+ ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_intern_all(string_storage, strings, output_ids, output_ids_size);
81
+ if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
82
+ rb_raise(rb_eRuntimeError, "Failed to intern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some));
83
+ }
84
+ }
@@ -18,8 +18,11 @@ size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capa
18
18
  const char *ruby_value_type_to_string(enum ruby_value_type type);
19
19
  ddog_CharSlice ruby_value_type_to_char_slice(enum ruby_value_type type);
20
20
 
21
- // Returns a dynamically allocated string from the provided char slice.
22
- // WARN: The returned string must be explicitly freed with ruby_xfree.
23
- static inline char* string_from_char_slice(ddog_CharSlice slice) {
24
- return ruby_strndup(slice.ptr, slice.len);
25
- }
21
+ ddog_prof_ManagedStringId intern_or_raise(ddog_prof_ManagedStringStorage string_storage, ddog_CharSlice string);
22
+
23
+ void intern_all_or_raise(
24
+ ddog_prof_ManagedStringStorage string_storage,
25
+ ddog_prof_Slice_CharSlice strings,
26
+ ddog_prof_ManagedStringId *output_ids,
27
+ uintptr_t output_ids_size
28
+ );
@@ -212,21 +212,6 @@ uint64_t native_thread_id_for(VALUE thread) {
212
212
  #endif
213
213
  }
214
214
 
215
- // Returns the stack depth by using the same approach as rb_profile_frames and backtrace_each: get the positions
216
- // of the end and current frame pointers and subtracting them.
217
- ptrdiff_t stack_depth_for(VALUE thread) {
218
- const rb_execution_context_t *ec = thread_struct_from_object(thread)->ec;
219
- const rb_control_frame_t *cfp = ec->cfp, *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
220
-
221
- if (end_cfp == NULL) return 0;
222
-
223
- // Skip dummy frame, as seen in `backtrace_each` (`vm_backtrace.c`) and our custom rb_profile_frames
224
- // ( https://github.com/ruby/ruby/blob/4bd38e8120f2fdfdd47a34211720e048502377f1/vm_backtrace.c#L890-L914 )
225
- end_cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
226
-
227
- return end_cfp <= cfp ? 0 : end_cfp - cfp - 1;
228
- }
229
-
230
215
  // This was renamed in Ruby 3.2
231
216
  #if !defined(ccan_list_for_each) && defined(list_for_each)
232
217
  #define ccan_list_for_each list_for_each
@@ -360,11 +345,16 @@ calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id)
360
345
  }
361
346
  #endif
362
347
 
363
- // In PROF-11475 we spotted a crash when calling `rb_iseq_line_no` from this method. We couldn't reproduce or
364
- // figure out the root cause, but "just in case", we're validating that the iseq looks valid and that the
365
- // `n` used for the position is also sane, and if they don't look good, we don't calculate the line, rather
366
- // than potentially trigger any issues.
367
- if (RB_UNLIKELY(!RB_TYPE_P((VALUE) iseq, T_IMEMO) || n < 0 || n > ISEQ_BODY(iseq)->iseq_size)) return 0;
348
+ // In PROF-11475 we spotted a crash when calling `rb_iseq_line_no` from this method.
349
+ // We were only able to reproduce this issue on Ruby 2.6 and 2.7, not 2.5 or the 3.x series (tried 3.0, 3.2 and 3.4).
350
+ // Note that going out of bounds doesn't crash every time, as usual with C we may just read garbage or get lucky.
351
+ //
352
+ // For those problematic Rubies, we observed that when we try to take a sample in the middle of processing the
353
+ // VM `LEAVE` instruction, the value of `n` can violate the documented assumptions above and be
354
+ // `n > ISEQ_BODY(iseq)->iseq_size)`.
355
+ //
356
+ // To work around this and any other potential issues, we validate here that the bytecode position is sane.
357
+ if (RB_UNLIKELY(n < 0 || n > ISEQ_BODY(iseq)->iseq_size)) return 0;
368
358
 
369
359
  if (lineno) *lineno = rb_iseq_line_no(iseq, pos);
370
360
  #ifdef USE_ISEQ_NODE_ID
@@ -410,6 +400,9 @@ calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
410
400
  // * Add frame_flags.same_frame and logic to skip redoing work if the buffer already contains the same data we're collecting
411
401
  // * Skipped use of rb_callable_method_entry_t (cme) for Ruby frames as it doesn't impact us.
412
402
  // * Imported fix from https://github.com/ruby/ruby/pull/8280 to keep us closer to upstream
403
+ // * Added potential fix for https://github.com/ruby/ruby/pull/13643 (this one is a just-in-case, unclear if it happens
404
+ // for ddtrace)
405
+ // * Reversed order of iteration to better enable caching
413
406
  //
414
407
  // What is rb_profile_frames?
415
408
  // `rb_profile_frames` is a Ruby VM debug API added for use by profilers for sampling the stack trace of a Ruby thread.
@@ -445,6 +438,16 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
445
438
  // support sampling any thread (including the current) passed as an argument
446
439
  rb_thread_t *th = thread_struct_from_object(thread);
447
440
  const rb_execution_context_t *ec = th->ec;
441
+
442
+ // As of this writing, we don't support profiling with MN enabled, and this only happens in that mode, but as we
443
+ // probably want to experiment with it in the future, I've decided to import https://github.com/ruby/ruby/pull/9310
444
+ // here.
445
+ if (ec == NULL) return 0;
446
+
447
+ // I suspect this won't happen for ddtrace, but just-in-case we've imported a potential fix for
448
+ // https://github.com/ruby/ruby/pull/13643 by assuming that these can be NULL/zero with the cfp being non-NULL yet.
449
+ if (ec->vm_stack == NULL || ec->vm_stack_size == 0) return 0;
450
+
448
451
  const rb_control_frame_t *cfp = ec->cfp, *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
449
452
  #ifndef NO_JIT_RETURN
450
453
  const rb_control_frame_t *top = cfp;
@@ -462,11 +465,6 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
462
465
  // it from https://github.com/ruby/ruby/pull/7116 in a "just in case" kind of mindset.
463
466
  if (cfp == NULL) return 0;
464
467
 
465
- // As of this writing, we don't support profiling with MN enabled, and this only happens in that mode, but as we
466
- // probably want to experiment with it in the future, I've decided to import https://github.com/ruby/ruby/pull/9310
467
- // here.
468
- if (ec == NULL) return 0;
469
-
470
468
  // Fix: Skip dummy frame that shows up in main thread.
471
469
  //
472
470
  // According to a comment in `backtrace_each` (`vm_backtrace.c`), there's two dummy frames that we should ignore
@@ -483,7 +481,20 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
483
481
  // See comment on `record_placeholder_stack_in_native_code` for a full explanation of what this means (and why we don't just return 0)
484
482
  if (end_cfp <= cfp) return PLACEHOLDER_STACK_IN_NATIVE_CODE;
485
483
 
486
- for (i=0; i<limit && cfp != end_cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) {
484
+ // This is the position just after the top of the stack -- e.g. where a new frame pushed on the stack would end up.
485
+ const rb_control_frame_t *top_sentinel = RUBY_VM_NEXT_CONTROL_FRAME(cfp);
486
+
487
+ // We iterate the stack from bottom (beginning of thread) to the top (currently-active frame). This is different
488
+ // from upstream rb_profile_frames, but actually matches what `backtrace_each` does (yes, different Ruby VM APIs
489
+ // iterate in different directions).
490
+ // We do this to better take advantage of the `same_frame` caching mechanism: By starting from the bottom of the
491
+ // stack towards the top, we can usually keep most of the stack intact when the code is only going up and down
492
+ // a few methods at the top. Before this change, the cache was really only useful if between samples the app had
493
+ // not moved from the current stack, as adding or removing one frame would invalidate the existing cache (because
494
+ // every position would shift).
495
+ cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
496
+
497
+ for (i=0; i<limit && cfp != top_sentinel; cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) {
487
498
  if (cfp->iseq && !cfp->pc) {
488
499
  // Fix: Do nothing -- this frame should not be used
489
500
  //
@@ -569,6 +580,7 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
569
580
 
570
581
  stack_buffer[i].as.native_frame.caching_cme = (VALUE)cme;
571
582
  stack_buffer[i].as.native_frame.method_id = cme->def->original_id;
583
+ stack_buffer[i].as.native_frame.function = cme->def->body.cfunc.func;
572
584
  stack_buffer[i].is_ruby_frame = false;
573
585
  i++;
574
586
  }
@@ -18,16 +18,19 @@ typedef struct {
18
18
  rb_nativethread_id_t owner;
19
19
  } current_gvl_owner;
20
20
 
21
+ // If a sample is kept around for later use, some of its fields need marking. Remember to
22
+ // update the marking code in `sampling_buffer_mark` if new fields are added.
21
23
  typedef struct {
22
24
  union {
23
25
  struct {
24
- VALUE iseq;
25
- void *caching_pc; // For caching only
26
+ VALUE iseq; // Needs marking if kept around
27
+ void *caching_pc; // For caching validation/invalidation only (does not need marking)
26
28
  int line;
27
29
  } ruby_frame;
28
30
  struct {
29
- VALUE caching_cme; // For caching only
31
+ VALUE caching_cme; // For caching validation/invalidation only (does not need marking)
30
32
  ID method_id;
33
+ void *function;
31
34
  } native_frame;
32
35
  } as;
33
36
  bool is_ruby_frame : 1;
@@ -38,7 +41,6 @@ rb_nativethread_id_t pthread_id_for(VALUE thread);
38
41
  bool is_current_thread_holding_the_gvl(void);
39
42
  current_gvl_owner gvl_owner(void);
40
43
  uint64_t native_thread_id_for(VALUE thread);
41
- ptrdiff_t stack_depth_for(VALUE thread);
42
44
  void ddtrace_thread_list(VALUE result_array);
43
45
  bool is_thread_alive(VALUE thread);
44
46
  VALUE thread_name_for(VALUE thread);
@@ -103,16 +103,6 @@ void raise_syserr(
103
103
  }
104
104
  }
105
105
 
106
- char* ruby_strndup(const char *str, size_t size) {
107
- char *dup;
108
-
109
- dup = xmalloc(size + 1);
110
- memcpy(dup, str, size);
111
- dup[size] = '\0';
112
-
113
- return dup;
114
- }
115
-
116
106
  static VALUE _id2ref(VALUE obj_id) {
117
107
  // Call ::ObjectSpace._id2ref natively. It will raise if the id is no longer valid
118
108
  return rb_funcall(module_object_space, _id2ref_id, 1, obj_id);
@@ -122,9 +112,7 @@ static VALUE _id2ref_failure(DDTRACE_UNUSED VALUE _unused1, DDTRACE_UNUSED VALUE
122
112
  return Qfalse;
123
113
  }
124
114
 
125
- // Native wrapper to get an object ref from an id. Returns true on success and
126
- // writes the ref to the value pointer parameter if !NULL. False if id doesn't
127
- // reference a valid object (in which case value is not changed).
115
+ // See notes on header for important details
128
116
  bool ruby_ref_from_id(VALUE obj_id, VALUE *value) {
129
117
  // Call ::ObjectSpace._id2ref natively. It will raise if the id is no longer valid
130
118
  // so we need to call it via rb_rescue2
@@ -67,20 +67,12 @@ NORETURN(void raise_syserr(
67
67
  const char *function_name
68
68
  ));
69
69
 
70
- // Alternative to ruby_strdup that takes a size argument.
71
- // Similar to C's strndup but slightly less smart as size is expected to
72
- // be smaller or equal to the real size of str (minus null termination if it
73
- // exists).
74
- // A new string will be returned with size+1 bytes and last byte set to '\0'.
75
- // The returned string must be freed explicitly.
76
- //
77
- // WARN: Cannot be used during GC or outside the GVL.
78
- char* ruby_strndup(const char *str, size_t size);
79
-
80
70
  // Native wrapper to get an object ref from an id. Returns true on success and
81
71
  // writes the ref to the value pointer parameter if !NULL. False if id doesn't
82
72
  // reference a valid object (in which case value is not changed).
83
- bool ruby_ref_from_id(size_t id, VALUE *value);
73
+ //
74
+ // Note: GVL can be released and other threads may get to run before this method returns
75
+ bool ruby_ref_from_id(VALUE obj_id, VALUE *value);
84
76
 
85
77
  // Native wrapper to get the approximate/estimated current size of the passed
86
78
  // object.
@@ -196,6 +196,10 @@ typedef struct {
196
196
  pthread_mutex_t mutex_slot_two;
197
197
  profile_slot profile_slot_two;
198
198
 
199
+ ddog_prof_ManagedStringStorage string_storage;
200
+ ddog_prof_ManagedStringId label_key_allocation_class;
201
+ ddog_prof_ManagedStringId label_key_gc_gen_age;
202
+
199
203
  short active_slot; // MUST NEVER BE ACCESSED FROM record_sample; this is NOT for the sampler thread to use.
200
204
 
201
205
  uint8_t position_for[ALL_VALUE_TYPES_COUNT];
@@ -230,6 +234,7 @@ typedef struct {
230
234
  ddog_prof_Profile_SerializeResult result;
231
235
  long heap_profile_build_time_ns;
232
236
  long serialize_no_gvl_time_ns;
237
+ ddog_prof_MaybeError advance_gen_result;
233
238
 
234
239
  // Set by both
235
240
  bool serialize_ran;
@@ -256,7 +261,6 @@ static void serializer_set_start_timestamp_for_next_profile(stack_recorder_state
256
261
  static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE local_root_span_id, VALUE endpoint);
257
262
  static void reset_profile_slot(profile_slot *slot, ddog_Timespec start_timestamp);
258
263
  static VALUE _native_track_object(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE new_obj, VALUE weight, VALUE alloc_class);
259
- static VALUE _native_check_heap_hashes(DDTRACE_UNUSED VALUE _self, VALUE locations);
260
264
  static VALUE _native_start_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
261
265
  static VALUE _native_end_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
262
266
  static VALUE _native_debug_heap_recorder(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
@@ -265,6 +269,8 @@ static VALUE build_profile_stats(profile_slot *slot, long serialization_time_ns,
265
269
  static VALUE _native_is_object_recorded(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE object_id);
266
270
  static VALUE _native_heap_recorder_reset_last_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
267
271
  static VALUE _native_recorder_after_gc_step(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
272
+ static VALUE _native_benchmark_intern(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE string, VALUE times, VALUE use_all);
273
+ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE_UNUSED VALUE _self);
268
274
 
269
275
  void stack_recorder_init(VALUE profiling_module) {
270
276
  VALUE stack_recorder_class = rb_define_class_under(profiling_module, "StackRecorder", rb_cObject);
@@ -290,7 +296,6 @@ void stack_recorder_init(VALUE profiling_module) {
290
296
  rb_define_singleton_method(testing_module, "_native_slot_two_mutex_locked?", _native_is_slot_two_mutex_locked, 1);
291
297
  rb_define_singleton_method(testing_module, "_native_record_endpoint", _native_record_endpoint, 3);
292
298
  rb_define_singleton_method(testing_module, "_native_track_object", _native_track_object, 4);
293
- rb_define_singleton_method(testing_module, "_native_check_heap_hashes", _native_check_heap_hashes, 1);
294
299
  rb_define_singleton_method(testing_module, "_native_start_fake_slow_heap_serialization",
295
300
  _native_start_fake_slow_heap_serialization, 1);
296
301
  rb_define_singleton_method(testing_module, "_native_end_fake_slow_heap_serialization",
@@ -300,6 +305,8 @@ void stack_recorder_init(VALUE profiling_module) {
300
305
  rb_define_singleton_method(testing_module, "_native_is_object_recorded?", _native_is_object_recorded, 2);
301
306
  rb_define_singleton_method(testing_module, "_native_heap_recorder_reset_last_update", _native_heap_recorder_reset_last_update, 1);
302
307
  rb_define_singleton_method(testing_module, "_native_recorder_after_gc_step", _native_recorder_after_gc_step, 1);
308
+ rb_define_singleton_method(testing_module, "_native_benchmark_intern", _native_benchmark_intern, 4);
309
+ rb_define_singleton_method(testing_module, "_native_test_managed_string_storage_produces_valid_profiles", _native_test_managed_string_storage_produces_valid_profiles, 0);
303
310
 
304
311
  ok_symbol = ID2SYM(rb_intern_const("ok"));
305
312
  error_symbol = ID2SYM(rb_intern_const("error"));
@@ -334,17 +341,27 @@ static VALUE _native_new(VALUE klass) {
334
341
  .serialization_time_ns_min = INT64_MAX,
335
342
  };
336
343
 
337
- // Note: At this point, slot_one_profile/slot_two_profile contain null pointers. Libdatadog validates pointers
344
+ // Note: At this point, slot_one_profile/slot_two_profile/string_storage contain null pointers. Libdatadog validates pointers
338
345
  // before using them so it's ok for us to go ahead and create the StackRecorder object.
339
346
 
340
347
  VALUE stack_recorder = TypedData_Wrap_Struct(klass, &stack_recorder_typed_data, state);
341
348
 
349
+ ddog_prof_ManagedStringStorageNewResult string_storage = ddog_prof_ManagedStringStorage_new();
350
+
351
+ if (string_storage.tag == DDOG_PROF_MANAGED_STRING_STORAGE_NEW_RESULT_ERR) {
352
+ rb_raise(rb_eRuntimeError, "Failed to initialize string storage: %"PRIsVALUE, get_error_details_and_drop(&string_storage.err));
353
+ }
354
+
355
+ state->string_storage = string_storage.ok;
356
+ state->label_key_allocation_class = intern_or_raise(state->string_storage, DDOG_CHARSLICE_C("allocation class"));
357
+ state->label_key_gc_gen_age = intern_or_raise(state->string_storage, DDOG_CHARSLICE_C("gc gen age"));
358
+
359
+ initialize_profiles(state, sample_types);
360
+
342
361
  // NOTE: We initialize this because we want a new recorder to be operational even before #initialize runs and our
343
362
  // default is everything enabled. However, if during recording initialization it turns out we don't want
344
363
  // heap samples, we will free and reset heap_recorder back to NULL.
345
- state->heap_recorder = heap_recorder_new();
346
-
347
- initialize_profiles(state, sample_types);
364
+ state->heap_recorder = heap_recorder_new(state->string_storage);
348
365
 
349
366
  return stack_recorder;
350
367
  }
@@ -363,7 +380,7 @@ static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_Val
363
380
  ddog_Timespec start_timestamp = system_epoch_now_timespec();
364
381
 
365
382
  ddog_prof_Profile_NewResult slot_one_profile_result =
366
- ddog_prof_Profile_new(sample_types, NULL /* period is optional */);
383
+ ddog_prof_Profile_with_string_storage(sample_types, NULL /* period is optional */, state->string_storage);
367
384
 
368
385
  if (slot_one_profile_result.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) {
369
386
  rb_raise(rb_eRuntimeError, "Failed to initialize slot one profile: %"PRIsVALUE, get_error_details_and_drop(&slot_one_profile_result.err));
@@ -372,7 +389,7 @@ static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_Val
372
389
  state->profile_slot_one = (profile_slot) { .profile = slot_one_profile_result.ok, .start_timestamp = start_timestamp };
373
390
 
374
391
  ddog_prof_Profile_NewResult slot_two_profile_result =
375
- ddog_prof_Profile_new(sample_types, NULL /* period is optional */);
392
+ ddog_prof_Profile_with_string_storage(sample_types, NULL /* period is optional */, state->string_storage);
376
393
 
377
394
  if (slot_two_profile_result.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) {
378
395
  // Note: No need to take any special care of slot one, it'll get cleaned up by stack_recorder_typed_data_free
@@ -393,6 +410,8 @@ static void stack_recorder_typed_data_free(void *state_ptr) {
393
410
 
394
411
  heap_recorder_free(state->heap_recorder);
395
412
 
413
+ ddog_prof_ManagedStringStorage_drop(state->string_storage);
414
+
396
415
  ruby_xfree(state);
397
416
  }
398
417
 
@@ -519,6 +538,8 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
519
538
  long heap_iteration_prep_start_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
520
539
  // Prepare the iteration on heap recorder we'll be doing outside the GVL. The preparation needs to
521
540
  // happen while holding on to the GVL.
541
+ // NOTE: While rare, it's possible for the GVL to be released inside this function (see comments on `heap_recorder_update`)
542
+ // and thus don't assume this is an "atomic" step -- other threads may get some running time in the meanwhile.
522
543
  heap_recorder_prepare_iteration(state->heap_recorder);
523
544
  long heap_iteration_prep_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - heap_iteration_prep_start_time_ns;
524
545
 
@@ -527,7 +548,7 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
527
548
  call_serialize_without_gvl_arguments args = {
528
549
  .state = state,
529
550
  .finish_timestamp = finish_timestamp,
530
- .serialize_ran = false
551
+ .serialize_ran = false,
531
552
  };
532
553
 
533
554
  while (!args.serialize_ran) {
@@ -551,13 +572,9 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
551
572
  // really cover the full serialization process but it gives a more useful number since it bypasses
552
573
  // the noise of acquiring GVLs and dealing with interruptions which is highly specific to runtime
553
574
  // conditions and over which we really have no control about.
554
- long serialization_time_ns = args.serialize_no_gvl_time_ns;
555
- if (serialization_time_ns >= 0) {
556
- // Only update stats if our serialization time is valid.
557
- state->stats_lifetime.serialization_time_ns_max = long_max_of(state->stats_lifetime.serialization_time_ns_max, serialization_time_ns);
558
- state->stats_lifetime.serialization_time_ns_min = long_min_of(state->stats_lifetime.serialization_time_ns_min, serialization_time_ns);
559
- state->stats_lifetime.serialization_time_ns_total += serialization_time_ns;
560
- }
575
+ state->stats_lifetime.serialization_time_ns_max = long_max_of(state->stats_lifetime.serialization_time_ns_max, args.serialize_no_gvl_time_ns);
576
+ state->stats_lifetime.serialization_time_ns_min = long_min_of(state->stats_lifetime.serialization_time_ns_min, args.serialize_no_gvl_time_ns);
577
+ state->stats_lifetime.serialization_time_ns_total += args.serialize_no_gvl_time_ns;
561
578
 
562
579
  ddog_prof_Profile_SerializeResult serialized_profile = args.result;
563
580
 
@@ -566,14 +583,20 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
566
583
  return rb_ary_new_from_args(2, error_symbol, get_error_details_and_drop(&serialized_profile.err));
567
584
  }
568
585
 
586
+ // Note: If we got here, the profile serialized correctly.
587
+ // Once we wrap this into a Ruby object, our `EncodedProfile` class will automatically manage memory for it and we
588
+ // can raise exceptions without worrying about leaking the profile.
569
589
  state->stats_lifetime.serialization_successes++;
570
-
571
- // Once we wrap this into a Ruby object, our `EncodedProfile` class will automatically manage memory for it
572
590
  VALUE encoded_profile = from_ddog_prof_EncodedProfile(serialized_profile.ok);
573
591
 
592
+ ddog_prof_MaybeError result = args.advance_gen_result;
593
+ if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
594
+ rb_raise(rb_eRuntimeError, "Failed to advance string storage gen: %"PRIsVALUE, get_error_details_and_drop(&result.some));
595
+ }
596
+
574
597
  VALUE start = ruby_time_from(args.slot->start_timestamp);
575
598
  VALUE finish = ruby_time_from(finish_timestamp);
576
- VALUE profile_stats = build_profile_stats(args.slot, serialization_time_ns, heap_iteration_prep_time_ns, args.heap_profile_build_time_ns);
599
+ VALUE profile_stats = build_profile_stats(args.slot, args.serialize_no_gvl_time_ns, heap_iteration_prep_time_ns, args.heap_profile_build_time_ns);
577
600
 
578
601
  return rb_ary_new_from_args(2, ok_symbol, rb_ary_new_from_args(4, start, finish, encoded_profile, profile_stats));
579
602
  }
@@ -696,18 +719,15 @@ static bool add_heap_sample_to_active_profile_without_gvl(heap_recorder_iteratio
696
719
  ddog_prof_Label labels[2];
697
720
  size_t label_offset = 0;
698
721
 
699
- if (object_data->class != NULL) {
722
+ if (object_data->class.value > 0) {
700
723
  labels[label_offset++] = (ddog_prof_Label) {
701
- .key = DDOG_CHARSLICE_C("allocation class"),
702
- .str = (ddog_CharSlice) {
703
- .ptr = object_data->class,
704
- .len = strlen(object_data->class),
705
- },
724
+ .key_id = context->state->label_key_allocation_class,
725
+ .str_id = object_data->class,
706
726
  .num = 0, // This shouldn't be needed but the tracer-2.7 docker image ships a buggy gcc that complains about this
707
727
  };
708
728
  }
709
729
  labels[label_offset++] = (ddog_prof_Label) {
710
- .key = DDOG_CHARSLICE_C("gc gen age"),
730
+ .key_id = context->state->label_key_gc_gen_age,
711
731
  .num = object_data->gen_age,
712
732
  };
713
733
 
@@ -772,8 +792,9 @@ static void *call_serialize_without_gvl(void *call_args) {
772
792
 
773
793
  // Note: The profile gets reset by the serialize call
774
794
  args->result = ddog_prof_Profile_serialize(&args->slot->profile, &args->slot->start_timestamp, &args->finish_timestamp);
795
+ args->advance_gen_result = ddog_prof_ManagedStringStorage_advance_gen(args->state->string_storage);
775
796
  args->serialize_ran = true;
776
- args->serialize_no_gvl_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - serialize_no_gvl_start_time_ns;
797
+ args->serialize_no_gvl_time_ns = long_max_of(0, monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - serialize_no_gvl_start_time_ns);
777
798
 
778
799
  return NULL; // Unused
779
800
  }
@@ -917,36 +938,6 @@ static VALUE _native_track_object(DDTRACE_UNUSED VALUE _self, VALUE recorder_ins
917
938
  return Qtrue;
918
939
  }
919
940
 
920
- static VALUE _native_check_heap_hashes(DDTRACE_UNUSED VALUE _self, VALUE locations) {
921
- ENFORCE_TYPE(locations, T_ARRAY);
922
- size_t locations_len = rb_array_len(locations);
923
- ddog_prof_Location locations_arr[locations_len];
924
- for (size_t i = 0; i < locations_len; i++) {
925
- VALUE location = rb_ary_entry(locations, i);
926
- ENFORCE_TYPE(location, T_ARRAY);
927
- VALUE name = rb_ary_entry(location, 0);
928
- VALUE filename = rb_ary_entry(location, 1);
929
- VALUE line = rb_ary_entry(location, 2);
930
- ENFORCE_TYPE(name, T_STRING);
931
- ENFORCE_TYPE(filename, T_STRING);
932
- ENFORCE_TYPE(line, T_FIXNUM);
933
- locations_arr[i] = (ddog_prof_Location) {
934
- .line = line,
935
- .function = (ddog_prof_Function) {
936
- .name = char_slice_from_ruby_string(name),
937
- .filename = char_slice_from_ruby_string(filename),
938
- }
939
- };
940
- }
941
- ddog_prof_Slice_Location ddog_locations = {
942
- .len = locations_len,
943
- .ptr = locations_arr,
944
- };
945
- heap_recorder_testonly_assert_hash_matches(ddog_locations);
946
-
947
- return Qnil;
948
- }
949
-
950
941
  static void reset_profile_slot(profile_slot *slot, ddog_Timespec start_timestamp) {
951
942
  ddog_prof_Profile_Result reset_result = ddog_prof_Profile_reset(&slot->profile);
952
943
  if (reset_result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
@@ -1046,3 +1037,109 @@ static VALUE _native_recorder_after_gc_step(DDTRACE_UNUSED VALUE _self, VALUE re
1046
1037
  recorder_after_gc_step(recorder_instance);
1047
1038
  return Qtrue;
1048
1039
  }
1040
+
1041
+ static VALUE _native_benchmark_intern(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE string, VALUE times, VALUE use_all) {
1042
+ ENFORCE_TYPE(string, T_STRING);
1043
+ ENFORCE_TYPE(times, T_FIXNUM);
1044
+ ENFORCE_BOOLEAN(use_all);
1045
+
1046
+ stack_recorder_state *state;
1047
+ TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
1048
+
1049
+ heap_recorder_testonly_benchmark_intern(state->heap_recorder, char_slice_from_ruby_string(string), FIX2INT(times), use_all == Qtrue);
1050
+
1051
+ return Qtrue;
1052
+ }
1053
+
1054
+ // See comments in rspec test for details on what we're testing here.
1055
+ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE_UNUSED VALUE _self) {
1056
+ ddog_prof_ManagedStringStorageNewResult string_storage = ddog_prof_ManagedStringStorage_new();
1057
+
1058
+ if (string_storage.tag == DDOG_PROF_MANAGED_STRING_STORAGE_NEW_RESULT_ERR) {
1059
+ rb_raise(rb_eRuntimeError, "Failed to initialize string storage: %"PRIsVALUE, get_error_details_and_drop(&string_storage.err));
1060
+ }
1061
+
1062
+ ddog_prof_Slice_ValueType sample_types = {.ptr = all_value_types, .len = ALL_VALUE_TYPES_COUNT};
1063
+ ddog_prof_Profile_NewResult profile = ddog_prof_Profile_with_string_storage(sample_types, NULL, string_storage.ok);
1064
+
1065
+ if (profile.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) {
1066
+ rb_raise(rb_eRuntimeError, "Failed to initialize profile: %"PRIsVALUE, get_error_details_and_drop(&profile.err));
1067
+ }
1068
+
1069
+ ddog_prof_ManagedStringId hello = intern_or_raise(string_storage.ok, DDOG_CHARSLICE_C("hello"));
1070
+ ddog_prof_ManagedStringId world = intern_or_raise(string_storage.ok, DDOG_CHARSLICE_C("world"));
1071
+ ddog_prof_ManagedStringId key = intern_or_raise(string_storage.ok, DDOG_CHARSLICE_C("key"));
1072
+
1073
+ int64_t metric_values[] = {1, 2, 3, 4, 5, 6, 7, 8};
1074
+ ddog_prof_Label labels[] = {{.key_id = key, .str_id = key}};
1075
+
1076
+ ddog_prof_Location locations[] = {
1077
+ (ddog_prof_Location) {
1078
+ .mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
1079
+ .function = {
1080
+ .name = DDOG_CHARSLICE_C(""),
1081
+ .name_id = hello,
1082
+ .filename = DDOG_CHARSLICE_C(""),
1083
+ .filename_id = world,
1084
+ },
1085
+ .line = 1,
1086
+ }
1087
+ };
1088
+
1089
+ ddog_prof_Profile_Result result = ddog_prof_Profile_add(
1090
+ &profile.ok,
1091
+ (ddog_prof_Sample) {
1092
+ .locations = (ddog_prof_Slice_Location) { .ptr = locations, .len = 1},
1093
+ .values = (ddog_Slice_I64) {.ptr = metric_values, .len = 8},
1094
+ .labels = (ddog_prof_Slice_Label) { .ptr = labels, .len = 1 }
1095
+ },
1096
+ 0
1097
+ );
1098
+
1099
+ if (result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
1100
+ rb_raise(rb_eArgError, "Failed to record sample: %"PRIsVALUE, get_error_details_and_drop(&result.err));
1101
+ }
1102
+
1103
+ ddog_Timespec finish_timestamp = system_epoch_now_timespec();
1104
+ ddog_Timespec start_timestamp = {.seconds = finish_timestamp.seconds - 60};
1105
+ ddog_prof_Profile_SerializeResult serialize_result = ddog_prof_Profile_serialize(&profile.ok, &start_timestamp, &finish_timestamp);
1106
+
1107
+ if (serialize_result.tag == DDOG_PROF_PROFILE_SERIALIZE_RESULT_ERR) {
1108
+ rb_raise(rb_eRuntimeError, "Failed to serialize: %"PRIsVALUE, get_error_details_and_drop(&serialize_result.err));
1109
+ }
1110
+
1111
+ ddog_prof_MaybeError advance_gen_result = ddog_prof_ManagedStringStorage_advance_gen(string_storage.ok);
1112
+
1113
+ if (advance_gen_result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
1114
+ rb_raise(rb_eRuntimeError, "Failed to advance string storage gen: %"PRIsVALUE, get_error_details_and_drop(&advance_gen_result.some));
1115
+ }
1116
+
1117
+ VALUE encoded_pprof_1 = from_ddog_prof_EncodedProfile(serialize_result.ok);
1118
+
1119
+ result = ddog_prof_Profile_add(
1120
+ &profile.ok,
1121
+ (ddog_prof_Sample) {
1122
+ .locations = (ddog_prof_Slice_Location) { .ptr = locations, .len = 1},
1123
+ .values = (ddog_Slice_I64) {.ptr = metric_values, .len = 8},
1124
+ .labels = (ddog_prof_Slice_Label) { .ptr = labels, .len = 1 }
1125
+ },
1126
+ 0
1127
+ );
1128
+
1129
+ if (result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
1130
+ rb_raise(rb_eArgError, "Failed to record sample: %"PRIsVALUE, get_error_details_and_drop(&result.err));
1131
+ }
1132
+
1133
+ serialize_result = ddog_prof_Profile_serialize(&profile.ok, &start_timestamp, &finish_timestamp);
1134
+
1135
+ if (serialize_result.tag == DDOG_PROF_PROFILE_SERIALIZE_RESULT_ERR) {
1136
+ rb_raise(rb_eArgError, "Failed to serialize: %"PRIsVALUE, get_error_details_and_drop(&serialize_result.err));
1137
+ }
1138
+
1139
+ VALUE encoded_pprof_2 = from_ddog_prof_EncodedProfile(serialize_result.ok);
1140
+
1141
+ ddog_prof_Profile_drop(&profile.ok);
1142
+ ddog_prof_ManagedStringStorage_drop(string_storage.ok);
1143
+
1144
+ return rb_ary_new_from_args(2, encoded_pprof_1, encoded_pprof_2);
1145
+ }
@@ -72,8 +72,8 @@ if ENV['DDTRACE_DEBUG'] == 'true'
72
72
  end
73
73
 
74
74
  # If we got here, libdatadog is available and loaded
75
- ENV['PKG_CONFIG_PATH'] = "#{ENV['PKG_CONFIG_PATH']}:#{Libdatadog.pkgconfig_folder}"
76
- Logging.message("[datadog] PKG_CONFIG_PATH set to #{ENV['PKG_CONFIG_PATH'].inspect}\n")
75
+ ENV['PKG_CONFIG_PATH'] = "#{ENV["PKG_CONFIG_PATH"]}:#{Libdatadog.pkgconfig_folder}"
76
+ Logging.message("[datadog] PKG_CONFIG_PATH set to #{ENV["PKG_CONFIG_PATH"].inspect}\n")
77
77
  $stderr.puts("Using libdatadog #{Libdatadog::VERSION} from #{Libdatadog.pkgconfig_folder}")
78
78
 
79
79
  unless pkg_config('datadog_profiling_with_rpath')