datadog 2.17.0 → 2.18.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +12 -46
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +227 -49
  5. data/ext/datadog_profiling_native_extension/collectors_stack.h +19 -3
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +63 -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 +1 -0
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +6 -3
  15. data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -13
  16. data/ext/datadog_profiling_native_extension/ruby_helpers.h +2 -10
  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 +65 -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/processor/rule_loader.rb +5 -6
  35. data/lib/datadog/appsec/remote.rb +15 -55
  36. data/lib/datadog/appsec/security_engine/engine.rb +194 -0
  37. data/lib/datadog/appsec/security_engine/runner.rb +10 -11
  38. data/lib/datadog/appsec.rb +4 -7
  39. data/lib/datadog/core/configuration/agent_settings.rb +52 -0
  40. data/lib/datadog/core/configuration/agent_settings_resolver.rb +1 -43
  41. data/lib/datadog/core/configuration/components.rb +2 -4
  42. data/lib/datadog/core/configuration/option.rb +9 -9
  43. data/lib/datadog/core/configuration/settings.rb +22 -10
  44. data/lib/datadog/core/configuration/stable_config.rb +1 -2
  45. data/lib/datadog/core/crashtracking/tag_builder.rb +4 -22
  46. data/lib/datadog/core/process_discovery/tracer_memfd.rb +15 -0
  47. data/lib/datadog/core/process_discovery.rb +5 -1
  48. data/lib/datadog/core/remote/configuration/repository.rb +12 -0
  49. data/lib/datadog/core/tag_builder.rb +56 -0
  50. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +1 -0
  51. data/lib/datadog/core/telemetry/event/app_started.rb +129 -39
  52. data/lib/datadog/core/telemetry/logger.rb +5 -4
  53. data/lib/datadog/core/telemetry/logging.rb +11 -5
  54. data/lib/datadog/core/transport/http/adapters/net.rb +17 -2
  55. data/lib/datadog/core/transport/http/builder.rb +2 -2
  56. data/lib/datadog/core/transport/http/env.rb +8 -0
  57. data/lib/datadog/core/utils.rb +7 -0
  58. data/lib/datadog/di/instrumenter.rb +52 -2
  59. data/lib/datadog/di/probe_notification_builder.rb +31 -41
  60. data/lib/datadog/di/probe_notifier_worker.rb +9 -1
  61. data/lib/datadog/di/serializer.rb +6 -2
  62. data/lib/datadog/di/transport/http/input.rb +10 -0
  63. data/lib/datadog/di/transport/input.rb +10 -2
  64. data/lib/datadog/profiling/collectors/code_provenance.rb +17 -8
  65. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +4 -0
  66. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -0
  67. data/lib/datadog/profiling/collectors/thread_context.rb +16 -1
  68. data/lib/datadog/profiling/component.rb +7 -9
  69. data/lib/datadog/profiling/ext.rb +0 -12
  70. data/lib/datadog/profiling/http_transport.rb +2 -2
  71. data/lib/datadog/profiling/profiler.rb +2 -0
  72. data/lib/datadog/profiling/scheduler.rb +2 -1
  73. data/lib/datadog/profiling/stack_recorder.rb +5 -5
  74. data/lib/datadog/profiling/tag_builder.rb +5 -37
  75. data/lib/datadog/profiling/tasks/setup.rb +2 -0
  76. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +15 -0
  77. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +19 -12
  78. data/lib/datadog/tracing/contrib/action_pack/ext.rb +2 -0
  79. data/lib/datadog/tracing/contrib/lograge/patcher.rb +4 -2
  80. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +1 -0
  81. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +5 -2
  82. data/lib/datadog/tracing/sync_writer.rb +1 -1
  83. data/lib/datadog/tracing/trace_operation.rb +12 -4
  84. data/lib/datadog/tracing/tracer.rb +6 -2
  85. data/lib/datadog/version.rb +1 -1
  86. metadata +12 -10
  87. data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -321
  88. data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -1023
  89. data/lib/datadog/appsec/processor/rule_merger.rb +0 -171
  90. 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
+ );
@@ -569,6 +569,7 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
569
569
 
570
570
  stack_buffer[i].as.native_frame.caching_cme = (VALUE)cme;
571
571
  stack_buffer[i].as.native_frame.method_id = cme->def->original_id;
572
+ stack_buffer[i].as.native_frame.function = cme->def->body.cfunc.func;
572
573
  stack_buffer[i].is_ruby_frame = false;
573
574
  i++;
574
575
  }
@@ -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;
@@ -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,19 +67,11 @@ 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).
73
+ //
74
+ // Note: GVL can be released and other threads may get to run before this method returns
83
75
  bool ruby_ref_from_id(size_t id, VALUE *value);
84
76
 
85
77
  // Native wrapper to get the approximate/estimated current size of the passed
@@ -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')
@@ -7,6 +7,10 @@
7
7
  static VALUE _native_configurator_new(VALUE klass);
8
8
  static VALUE _native_configurator_get(VALUE self);
9
9
 
10
+ // Used for testing in RSpec
11
+ static VALUE _native_configurator_with_local_path(DDTRACE_UNUSED VALUE _self, VALUE rb_configurator, VALUE path);
12
+ static VALUE _native_configurator_with_fleet_path(DDTRACE_UNUSED VALUE _self, VALUE rb_configurator, VALUE path);
13
+
10
14
  static VALUE config_vec_class = Qnil;
11
15
 
12
16
  // ddog_Configurator memory management
@@ -52,20 +56,42 @@ void library_config_init(VALUE core_module) {
52
56
  rb_define_alloc_func(configurator_class, _native_configurator_new);
53
57
  rb_define_method(configurator_class, "get", _native_configurator_get, 0);
54
58
 
59
+ // Used for testing in RSpec
60
+ VALUE testing_module = rb_define_module_under(stable_config_module, "Testing");
61
+ rb_define_singleton_method(testing_module, "with_local_path", _native_configurator_with_local_path, 2);
62
+ rb_define_singleton_method(testing_module, "with_fleet_path", _native_configurator_with_fleet_path, 2);
63
+
55
64
  rb_undef_alloc_func(config_vec_class); // It cannot be created from Ruby code and only serves as an intermediate object for the Ruby GC
56
65
  }
57
66
 
58
- // TODO: After libdatadog 17.1 release, delete rb_raise, uncomment code and change `DDTRACE_UNUSED VALUE _klass` by `VALUE klass`
59
- static VALUE _native_configurator_new(DDTRACE_UNUSED VALUE _klass) {
60
- /*
67
+ static VALUE _native_configurator_new(VALUE klass) {
61
68
  ddog_Configurator *configurator = ddog_library_configurator_new(false, DDOG_CHARSLICE_C("ruby"));
62
69
 
63
70
  ddog_library_configurator_with_detect_process_info(configurator);
64
71
 
65
72
  return TypedData_Wrap_Struct(klass, &configurator_typed_data, configurator);
66
- */
73
+ }
74
+
75
+ static VALUE _native_configurator_with_local_path(DDTRACE_UNUSED VALUE _self, VALUE rb_configurator, VALUE path) {
76
+ ddog_Configurator *configurator;
77
+ TypedData_Get_Struct(rb_configurator, ddog_Configurator, &configurator_typed_data, configurator);
78
+
79
+ ENFORCE_TYPE(path, T_STRING);
67
80
 
68
- rb_raise(rb_eNotImpError, "TODO: Not in use yet, waiting for libdatadog 17.1");
81
+ ddog_library_configurator_with_local_path(configurator, cstr_from_ruby_string(path));
82
+
83
+ return Qnil;
84
+ }
85
+
86
+ static VALUE _native_configurator_with_fleet_path(DDTRACE_UNUSED VALUE _self, VALUE rb_configurator, VALUE path) {
87
+ ddog_Configurator *configurator;
88
+ TypedData_Get_Struct(rb_configurator, ddog_Configurator, &configurator_typed_data, configurator);
89
+
90
+ ENFORCE_TYPE(path, T_STRING);
91
+
92
+ ddog_library_configurator_with_fleet_path(configurator, cstr_from_ruby_string(path));
93
+
94
+ return Qnil;
69
95
  }
70
96
 
71
97
  static VALUE _native_configurator_get(VALUE self) {
@@ -96,26 +122,42 @@ static VALUE _native_configurator_get(VALUE self) {
96
122
 
97
123
  VALUE local_config_hash = rb_hash_new();
98
124
  VALUE fleet_config_hash = rb_hash_new();
99
- // TODO: Uncomment next block after libdatadog 17.1 release
100
- /*
125
+
126
+ bool local_config_id_set = false;
127
+ bool fleet_config_id_set = false;
128
+ VALUE local_hash = rb_hash_new();
129
+ VALUE fleet_hash = rb_hash_new();
101
130
  for (uintptr_t i = 0; i < config_vec->len; i++) {
102
131
  ddog_LibraryConfig config = config_vec->ptr[i];
103
132
  VALUE selected_hash;
104
133
  if (config.source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG) {
105
134
  selected_hash = local_config_hash;
135
+ if (!local_config_id_set) {
136
+ local_config_id_set = true;
137
+ if (config.config_id.length > 0) {
138
+ rb_hash_aset(local_hash, ID2SYM(rb_intern("id")), rb_utf8_str_new_cstr(config.config_id.ptr));
139
+ }
140
+ }
106
141
  }
107
142
  else {
108
143
  selected_hash = fleet_config_hash;
144
+ if (!fleet_config_id_set) {
145
+ fleet_config_id_set = true;
146
+ if (config.config_id.length > 0) {
147
+ rb_hash_aset(fleet_hash, ID2SYM(rb_intern("id")), rb_utf8_str_new_cstr(config.config_id.ptr));
148
+ }
149
+ }
109
150
  }
110
151
 
111
- ddog_CStr name = ddog_library_config_name_to_env(config.name);
112
- rb_hash_aset(selected_hash, rb_str_new(name.ptr, name.length), rb_str_new(config.value.ptr, config.value.length));
152
+ rb_hash_aset(selected_hash, rb_utf8_str_new_cstr(config.name.ptr), rb_utf8_str_new_cstr(config.value.ptr));
113
153
  }
114
- */
154
+
155
+ rb_hash_aset(local_hash, ID2SYM(rb_intern("config")), local_config_hash);
156
+ rb_hash_aset(fleet_hash, ID2SYM(rb_intern("config")), fleet_config_hash);
115
157
 
116
158
  VALUE result = rb_hash_new();
117
- rb_hash_aset(result, ID2SYM(rb_intern("local")), local_config_hash);
118
- rb_hash_aset(result, ID2SYM(rb_intern("fleet")), fleet_config_hash);
159
+ rb_hash_aset(result, ID2SYM(rb_intern("local")), local_hash);
160
+ rb_hash_aset(result, ID2SYM(rb_intern("fleet")), fleet_hash);
119
161
 
120
162
  RB_GC_GUARD(config_vec_rb);
121
163
  return result;
@@ -17,3 +17,9 @@ static inline VALUE log_warning_without_config(VALUE warning) {
17
17
 
18
18
  return rb_funcall(logger, rb_intern("warn"), 1, warning);
19
19
  }
20
+
21
+ static inline ddog_CStr cstr_from_ruby_string(VALUE string) {
22
+ ENFORCE_TYPE(string, T_STRING);
23
+ ddog_CStr cstr = {.ptr = RSTRING_PTR(string), .length = RSTRING_LEN(string)};
24
+ return cstr;
25
+ }