datadog 2.16.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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -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/encoded_profile.c +22 -12
  9. data/ext/datadog_profiling_native_extension/encoded_profile.h +1 -0
  10. data/ext/datadog_profiling_native_extension/extconf.rb +7 -0
  11. data/ext/datadog_profiling_native_extension/heap_recorder.c +239 -363
  12. data/ext/datadog_profiling_native_extension/heap_recorder.h +4 -6
  13. data/ext/datadog_profiling_native_extension/http_transport.c +45 -72
  14. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +22 -0
  15. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -5
  16. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +1 -0
  17. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +6 -3
  18. data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -13
  19. data/ext/datadog_profiling_native_extension/ruby_helpers.h +2 -10
  20. data/ext/datadog_profiling_native_extension/stack_recorder.c +156 -60
  21. data/ext/libdatadog_api/crashtracker.c +10 -3
  22. data/ext/libdatadog_api/extconf.rb +2 -2
  23. data/ext/libdatadog_api/library_config.c +54 -12
  24. data/ext/libdatadog_api/library_config.h +6 -0
  25. data/ext/libdatadog_api/macos_development.md +3 -3
  26. data/ext/libdatadog_api/process_discovery.c +2 -7
  27. data/ext/libdatadog_extconf_helpers.rb +2 -2
  28. data/lib/datadog/appsec/api_security/lru_cache.rb +56 -0
  29. data/lib/datadog/appsec/api_security/route_extractor.rb +65 -0
  30. data/lib/datadog/appsec/api_security/sampler.rb +59 -0
  31. data/lib/datadog/appsec/api_security.rb +23 -0
  32. data/lib/datadog/appsec/assets/waf_rules/recommended.json +257 -85
  33. data/lib/datadog/appsec/assets/waf_rules/strict.json +10 -78
  34. data/lib/datadog/appsec/component.rb +30 -54
  35. data/lib/datadog/appsec/configuration/settings.rb +60 -2
  36. data/lib/datadog/appsec/context.rb +6 -6
  37. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +1 -1
  38. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +27 -16
  39. data/lib/datadog/appsec/processor/rule_loader.rb +5 -6
  40. data/lib/datadog/appsec/remote.rb +15 -55
  41. data/lib/datadog/appsec/security_engine/engine.rb +194 -0
  42. data/lib/datadog/appsec/security_engine/runner.rb +10 -11
  43. data/lib/datadog/appsec.rb +4 -7
  44. data/lib/datadog/core/buffer/random.rb +18 -2
  45. data/lib/datadog/core/configuration/agent_settings.rb +52 -0
  46. data/lib/datadog/core/configuration/agent_settings_resolver.rb +4 -46
  47. data/lib/datadog/core/configuration/components.rb +31 -24
  48. data/lib/datadog/core/configuration/components_state.rb +23 -0
  49. data/lib/datadog/core/configuration/option.rb +27 -27
  50. data/lib/datadog/core/configuration/option_definition.rb +4 -4
  51. data/lib/datadog/core/configuration/options.rb +1 -1
  52. data/lib/datadog/core/configuration/settings.rb +32 -20
  53. data/lib/datadog/core/configuration/stable_config.rb +1 -2
  54. data/lib/datadog/core/configuration.rb +16 -16
  55. data/lib/datadog/core/crashtracking/component.rb +2 -1
  56. data/lib/datadog/core/crashtracking/tag_builder.rb +4 -22
  57. data/lib/datadog/core/encoding.rb +1 -1
  58. data/lib/datadog/core/environment/cgroup.rb +10 -12
  59. data/lib/datadog/core/environment/container.rb +38 -40
  60. data/lib/datadog/core/environment/ext.rb +6 -6
  61. data/lib/datadog/core/environment/identity.rb +3 -3
  62. data/lib/datadog/core/environment/platform.rb +3 -3
  63. data/lib/datadog/core/error.rb +11 -9
  64. data/lib/datadog/core/logger.rb +2 -2
  65. data/lib/datadog/core/metrics/client.rb +12 -14
  66. data/lib/datadog/core/metrics/logging.rb +5 -5
  67. data/lib/datadog/core/process_discovery/tracer_memfd.rb +15 -0
  68. data/lib/datadog/core/process_discovery.rb +5 -1
  69. data/lib/datadog/core/rate_limiter.rb +4 -2
  70. data/lib/datadog/core/remote/client.rb +32 -31
  71. data/lib/datadog/core/remote/component.rb +3 -3
  72. data/lib/datadog/core/remote/configuration/digest.rb +7 -7
  73. data/lib/datadog/core/remote/configuration/path.rb +1 -1
  74. data/lib/datadog/core/remote/configuration/repository.rb +12 -0
  75. data/lib/datadog/core/remote/transport/http/client.rb +1 -1
  76. data/lib/datadog/core/remote/transport/http/config.rb +21 -5
  77. data/lib/datadog/core/remote/transport/http/negotiation.rb +1 -1
  78. data/lib/datadog/core/runtime/metrics.rb +3 -3
  79. data/lib/datadog/core/tag_builder.rb +56 -0
  80. data/lib/datadog/core/telemetry/component.rb +39 -24
  81. data/lib/datadog/core/telemetry/emitter.rb +7 -1
  82. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +66 -0
  83. data/lib/datadog/core/telemetry/event/app_closing.rb +18 -0
  84. data/lib/datadog/core/telemetry/event/app_dependencies_loaded.rb +33 -0
  85. data/lib/datadog/core/telemetry/event/app_heartbeat.rb +18 -0
  86. data/lib/datadog/core/telemetry/event/app_integrations_change.rb +58 -0
  87. data/lib/datadog/core/telemetry/event/app_started.rb +269 -0
  88. data/lib/datadog/core/telemetry/event/base.rb +40 -0
  89. data/lib/datadog/core/telemetry/event/distributions.rb +18 -0
  90. data/lib/datadog/core/telemetry/event/generate_metrics.rb +43 -0
  91. data/lib/datadog/core/telemetry/event/log.rb +76 -0
  92. data/lib/datadog/core/telemetry/event/message_batch.rb +42 -0
  93. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +43 -0
  94. data/lib/datadog/core/telemetry/event.rb +17 -475
  95. data/lib/datadog/core/telemetry/logger.rb +5 -4
  96. data/lib/datadog/core/telemetry/logging.rb +11 -5
  97. data/lib/datadog/core/telemetry/metric.rb +3 -3
  98. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +2 -2
  99. data/lib/datadog/core/telemetry/transport/telemetry.rb +0 -1
  100. data/lib/datadog/core/telemetry/worker.rb +48 -27
  101. data/lib/datadog/core/transport/http/adapters/net.rb +17 -2
  102. data/lib/datadog/core/transport/http/adapters/test.rb +2 -1
  103. data/lib/datadog/core/transport/http/builder.rb +14 -14
  104. data/lib/datadog/core/transport/http/env.rb +8 -0
  105. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +6 -6
  106. data/lib/datadog/core/utils/duration.rb +32 -32
  107. data/lib/datadog/core/utils/forking.rb +2 -2
  108. data/lib/datadog/core/utils/network.rb +6 -6
  109. data/lib/datadog/core/utils/only_once_successful.rb +16 -5
  110. data/lib/datadog/core/utils/time.rb +10 -2
  111. data/lib/datadog/core/utils/truncation.rb +21 -0
  112. data/lib/datadog/core/utils.rb +7 -0
  113. data/lib/datadog/core/vendor/multipart-post/multipart/post/composite_read_io.rb +1 -1
  114. data/lib/datadog/core/vendor/multipart-post/multipart/post/multipartable.rb +8 -8
  115. data/lib/datadog/core/vendor/multipart-post/multipart/post/parts.rb +7 -7
  116. data/lib/datadog/core/worker.rb +1 -1
  117. data/lib/datadog/core/workers/async.rb +9 -10
  118. data/lib/datadog/di/instrumenter.rb +52 -2
  119. data/lib/datadog/di/probe_notification_builder.rb +31 -41
  120. data/lib/datadog/di/probe_notifier_worker.rb +9 -1
  121. data/lib/datadog/di/serializer.rb +6 -2
  122. data/lib/datadog/di/transport/http/input.rb +10 -0
  123. data/lib/datadog/di/transport/input.rb +10 -2
  124. data/lib/datadog/error_tracking/component.rb +2 -2
  125. data/lib/datadog/profiling/collectors/code_provenance.rb +18 -9
  126. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +4 -0
  127. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -0
  128. data/lib/datadog/profiling/collectors/thread_context.rb +16 -1
  129. data/lib/datadog/profiling/component.rb +7 -9
  130. data/lib/datadog/profiling/ext.rb +0 -13
  131. data/lib/datadog/profiling/flush.rb +1 -1
  132. data/lib/datadog/profiling/http_transport.rb +3 -8
  133. data/lib/datadog/profiling/profiler.rb +2 -0
  134. data/lib/datadog/profiling/scheduler.rb +10 -2
  135. data/lib/datadog/profiling/stack_recorder.rb +5 -5
  136. data/lib/datadog/profiling/tag_builder.rb +5 -41
  137. data/lib/datadog/profiling/tasks/setup.rb +2 -0
  138. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +15 -0
  139. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +19 -12
  140. data/lib/datadog/tracing/contrib/action_pack/ext.rb +2 -0
  141. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +4 -1
  142. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +33 -0
  143. data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +4 -0
  144. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +2 -4
  145. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +10 -0
  146. data/lib/datadog/tracing/contrib/aws/parsed_context.rb +5 -1
  147. data/lib/datadog/tracing/contrib/http/instrumentation.rb +1 -5
  148. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +1 -5
  149. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +1 -5
  150. data/lib/datadog/tracing/contrib/lograge/patcher.rb +4 -2
  151. data/lib/datadog/tracing/contrib/patcher.rb +5 -2
  152. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +1 -0
  153. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +5 -2
  154. data/lib/datadog/tracing/contrib/support.rb +28 -0
  155. data/lib/datadog/tracing/metadata/errors.rb +4 -4
  156. data/lib/datadog/tracing/sync_writer.rb +1 -1
  157. data/lib/datadog/tracing/trace_operation.rb +12 -4
  158. data/lib/datadog/tracing/tracer.rb +6 -2
  159. data/lib/datadog/version.rb +1 -1
  160. metadata +31 -12
  161. data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -321
  162. data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -1023
  163. data/lib/datadog/appsec/processor/rule_merger.rb +0 -171
  164. data/lib/datadog/appsec/processor.rb +0 -107
@@ -2,19 +2,29 @@
2
2
 
3
3
  #include <datadog/profiling.h>
4
4
 
5
+ #include "private_vm_api_access.h"
5
6
  #include "stack_recorder.h"
6
7
 
7
8
  #define MAX_FRAMES_LIMIT 3000
8
9
  #define MAX_FRAMES_LIMIT_AS_STRING "3000"
9
10
 
10
- typedef struct sampling_buffer sampling_buffer;
11
+ // Used as scratch space during sampling
12
+ typedef struct {
13
+ uint16_t max_frames;
14
+ ddog_prof_Location *locations;
15
+ frame_info *stack_buffer;
16
+ bool pending_sample;
17
+ int pending_sample_result;
18
+ } sampling_buffer;
11
19
 
12
20
  void sample_thread(
13
21
  VALUE thread,
14
22
  sampling_buffer* buffer,
15
23
  VALUE recorder_instance,
16
24
  sample_values values,
17
- sample_labels labels
25
+ sample_labels labels,
26
+ bool native_filenames_enabled,
27
+ st_table *native_filenames_cache
18
28
  );
19
29
  void record_placeholder_stack(
20
30
  VALUE recorder_instance,
@@ -22,6 +32,12 @@ void record_placeholder_stack(
22
32
  sample_labels labels,
23
33
  ddog_CharSlice placeholder_stack
24
34
  );
35
+ void prepare_sample_thread(VALUE thread, sampling_buffer *buffer);
36
+
25
37
  uint16_t sampling_buffer_check_max_frames(int max_frames);
26
- sampling_buffer *sampling_buffer_new(uint16_t max_frames, ddog_prof_Location *locations);
38
+ void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames, ddog_prof_Location *locations);
27
39
  void sampling_buffer_free(sampling_buffer *buffer);
40
+ void sampling_buffer_mark(sampling_buffer *buffer);
41
+ static inline bool sampling_buffer_needs_marking(sampling_buffer *buffer) {
42
+ return buffer->pending_sample && buffer->pending_sample_result > 0;
43
+ }
@@ -11,6 +11,7 @@
11
11
  #include "stack_recorder.h"
12
12
  #include "time_helpers.h"
13
13
  #include "unsafe_api_calls_check.h"
14
+ #include "extconf.h"
14
15
 
15
16
  // Used to trigger sampling of threads, based on external "events", such as:
16
17
  // * periodic timer for cpu-time and wall-time
@@ -124,6 +125,7 @@ typedef struct {
124
125
  ddog_prof_Location *locations;
125
126
  uint16_t max_frames;
126
127
  // Hashmap <Thread Object, per_thread_context>
128
+ // Note: Be very careful when mutating this map, as it gets read e.g. in the middle of GC and signal handlers.
127
129
  st_table *hash_map_per_thread_context;
128
130
  // Datadog::Profiling::StackRecorder instance
129
131
  VALUE recorder_instance;
@@ -153,6 +155,10 @@ typedef struct {
153
155
  // Qtrue serves as a marker we've not yet extracted it; when we try to extract it, we set it to an object if
154
156
  // successful and Qnil if not.
155
157
  VALUE otel_current_span_key;
158
+ // Used to enable native filenames in stack traces
159
+ bool native_filenames_enabled;
160
+ // Used to cache native filename lookup results (Map[void *function_pointer, char *filename])
161
+ st_table *native_filenames_cache;
156
162
 
157
163
  struct stats {
158
164
  // Track how many garbage collection samples we've taken.
@@ -172,7 +178,7 @@ typedef struct {
172
178
 
173
179
  // Tracks per-thread state
174
180
  typedef struct {
175
- sampling_buffer *sampling_buffer;
181
+ sampling_buffer sampling_buffer;
176
182
  char thread_id[THREAD_ID_LIMIT_CHARS];
177
183
  ddog_CharSlice thread_id_char_slice;
178
184
  char thread_invoke_location[THREAD_INVOKE_LOCATION_LIMIT_CHARS];
@@ -205,7 +211,7 @@ typedef struct {
205
211
 
206
212
  static void thread_context_collector_typed_data_mark(void *state_ptr);
207
213
  static void thread_context_collector_typed_data_free(void *state_ptr);
208
- static int hash_map_per_thread_context_mark(st_data_t key_thread, st_data_t _value, st_data_t _argument);
214
+ static int hash_map_per_thread_context_mark(st_data_t key_thread, st_data_t value_thread_context, DDTRACE_UNUSED st_data_t _argument);
209
215
  static int hash_map_per_thread_context_free_values(st_data_t _thread, st_data_t value_per_thread_context, st_data_t _argument);
210
216
  static VALUE _native_new(VALUE klass);
211
217
  static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
@@ -298,6 +304,7 @@ static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key)
298
304
  static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
299
305
  static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
300
306
  static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
307
+ static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
301
308
 
302
309
  void collectors_thread_context_init(VALUE profiling_module) {
303
310
  VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
@@ -330,6 +337,7 @@ void collectors_thread_context_init(VALUE profiling_module) {
330
337
  rb_define_singleton_method(testing_module, "_native_new_empty_thread", _native_new_empty_thread, 0);
331
338
  rb_define_singleton_method(testing_module, "_native_sample_skipped_allocation_samples", _native_sample_skipped_allocation_samples, 2);
332
339
  rb_define_singleton_method(testing_module, "_native_system_epoch_time_now_ns", _native_system_epoch_time_now_ns, 1);
340
+ rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler, 1);
333
341
  #ifndef NO_GVL_INSTRUMENTATION
334
342
  rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
335
343
  rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
@@ -405,13 +413,21 @@ static void thread_context_collector_typed_data_free(void *state_ptr) {
405
413
  // ...and then the map
406
414
  st_free_table(state->hash_map_per_thread_context);
407
415
 
416
+ st_free_table(state->native_filenames_cache);
417
+
408
418
  ruby_xfree(state);
409
419
  }
410
420
 
411
421
  // Mark Ruby thread references we keep as keys in hash_map_per_thread_context
412
- static int hash_map_per_thread_context_mark(st_data_t key_thread, DDTRACE_UNUSED st_data_t _value, DDTRACE_UNUSED st_data_t _argument) {
422
+ static int hash_map_per_thread_context_mark(st_data_t key_thread, st_data_t value_thread_context, DDTRACE_UNUSED st_data_t _argument) {
413
423
  VALUE thread = (VALUE) key_thread;
424
+ per_thread_context *thread_context = (per_thread_context *) value_thread_context;
425
+
414
426
  rb_gc_mark(thread);
427
+ if (sampling_buffer_needs_marking(&thread_context->sampling_buffer)) {
428
+ sampling_buffer_mark(&thread_context->sampling_buffer);
429
+ }
430
+
415
431
  return ST_CONTINUE;
416
432
  }
417
433
 
@@ -440,6 +456,8 @@ static VALUE _native_new(VALUE klass) {
440
456
  state->thread_list_buffer = thread_list_buffer;
441
457
  state->endpoint_collection_enabled = true;
442
458
  state->timeline_enabled = true;
459
+ state->native_filenames_enabled = false;
460
+ state->native_filenames_cache = st_init_numtable();
443
461
  state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
444
462
  state->otel_context_source = OTEL_CONTEXT_SOURCE_UNKNOWN;
445
463
  state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
@@ -474,11 +492,13 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
474
492
  VALUE timeline_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("timeline_enabled")));
475
493
  VALUE waiting_for_gvl_threshold_ns = rb_hash_fetch(options, ID2SYM(rb_intern("waiting_for_gvl_threshold_ns")));
476
494
  VALUE otel_context_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("otel_context_enabled")));
495
+ VALUE native_filenames_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("native_filenames_enabled")));
477
496
 
478
497
  ENFORCE_TYPE(max_frames, T_FIXNUM);
479
498
  ENFORCE_BOOLEAN(endpoint_collection_enabled);
480
499
  ENFORCE_BOOLEAN(timeline_enabled);
481
500
  ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
501
+ ENFORCE_BOOLEAN(native_filenames_enabled);
482
502
 
483
503
  thread_context_collector_state *state;
484
504
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
@@ -490,6 +510,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
490
510
  state->recorder_instance = enforce_recorder_instance(recorder_instance);
491
511
  state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
492
512
  state->timeline_enabled = (timeline_enabled == Qtrue);
513
+ state->native_filenames_enabled = (native_filenames_enabled == Qtrue);
493
514
  if (otel_context_enabled == Qfalse || otel_context_enabled == Qnil) {
494
515
  state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
495
516
  } else if (otel_context_enabled == ID2SYM(rb_intern("only"))) {
@@ -599,7 +620,7 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
599
620
  /* thread_being_sampled: */ thread,
600
621
  /* stack_from_thread: */ thread,
601
622
  thread_context,
602
- thread_context->sampling_buffer,
623
+ &thread_context->sampling_buffer,
603
624
  current_cpu_time_ns,
604
625
  current_monotonic_wall_time_ns
605
626
  );
@@ -617,7 +638,7 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
617
638
  /* stack_from_thread: */ profiler_overhead_stack_thread,
618
639
  current_thread_context,
619
640
  // Here we use the overhead thread's sampling buffer so as to not invalidate the cache in the buffer of the thread being sampled
620
- get_or_create_context_for(profiler_overhead_stack_thread, state)->sampling_buffer,
641
+ &get_or_create_context_for(profiler_overhead_stack_thread, state)->sampling_buffer,
621
642
  cpu_time_now_ns(current_thread_context),
622
643
  monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
623
644
  );
@@ -998,7 +1019,9 @@ static void trigger_sample_for_thread(
998
1019
  .state_label = state_label,
999
1020
  .end_timestamp_ns = end_timestamp_ns,
1000
1021
  .is_gvl_waiting_state = is_gvl_waiting_state,
1001
- }
1022
+ },
1023
+ state->native_filenames_enabled,
1024
+ state->native_filenames_cache
1002
1025
  );
1003
1026
  }
1004
1027
 
@@ -1023,7 +1046,7 @@ static per_thread_context *get_or_create_context_for(VALUE thread, thread_contex
1023
1046
  if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
1024
1047
  thread_context = (per_thread_context*) value_context;
1025
1048
  } else {
1026
- thread_context = ruby_xcalloc(1, sizeof(per_thread_context));
1049
+ thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
1027
1050
  initialize_context(thread, thread_context, state);
1028
1051
  st_insert(state->hash_map_per_thread_context, (st_data_t) thread, (st_data_t) thread_context);
1029
1052
  }
@@ -1064,7 +1087,7 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
1064
1087
  }
1065
1088
 
1066
1089
  static void initialize_context(VALUE thread, per_thread_context *thread_context, thread_context_collector_state *state) {
1067
- thread_context->sampling_buffer = sampling_buffer_new(state->max_frames, state->locations);
1090
+ sampling_buffer_initialize(&thread_context->sampling_buffer, state->max_frames, state->locations);
1068
1091
 
1069
1092
  snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
1070
1093
  thread_context->thread_id_char_slice = (ddog_CharSlice) {.ptr = thread_context->thread_id, .len = strlen(thread_context->thread_id)};
@@ -1121,8 +1144,8 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context,
1121
1144
  }
1122
1145
 
1123
1146
  static void free_context(per_thread_context* thread_context) {
1124
- sampling_buffer_free(thread_context->sampling_buffer);
1125
- ruby_xfree(thread_context);
1147
+ sampling_buffer_free(&thread_context->sampling_buffer);
1148
+ free(thread_context); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
1126
1149
  }
1127
1150
 
1128
1151
  static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
@@ -1141,6 +1164,9 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
1141
1164
  rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_as_ruby_hash(state)));
1142
1165
  rb_str_concat(result, rb_sprintf(" endpoint_collection_enabled=%"PRIsVALUE, state->endpoint_collection_enabled ? Qtrue : Qfalse));
1143
1166
  rb_str_concat(result, rb_sprintf(" timeline_enabled=%"PRIsVALUE, state->timeline_enabled ? Qtrue : Qfalse));
1167
+ rb_str_concat(result, rb_sprintf(" native_filenames_enabled=%"PRIsVALUE, state->native_filenames_enabled ? Qtrue : Qfalse));
1168
+ // Note: `st_table_size()` is available from Ruby 3.2+ but not before
1169
+ rb_str_concat(result, rb_sprintf(" native_filenames_cache_size=%zu", state->native_filenames_cache->num_entries));
1144
1170
  rb_str_concat(result, rb_sprintf(" otel_context_enabled=%d", state->otel_context_enabled));
1145
1171
  rb_str_concat(result, rb_sprintf(
1146
1172
  " time_converter_state={.system_epoch_ns_reference=%ld, .delta_to_epoch_ns=%ld}",
@@ -1436,6 +1462,26 @@ static VALUE thread_list(thread_context_collector_state *state) {
1436
1462
  return result;
1437
1463
  }
1438
1464
 
1465
+ // Inside a signal handler, we don't want to do the whole work of recording a sample, but we only record the stack of
1466
+ // the current thread.
1467
+ //
1468
+ // Assumptions for this function are same as for `thread_context_collector_sample` except that this function is
1469
+ // expected to be called from a signal handler and to be async-signal-safe.
1470
+ //
1471
+ // Also, no allocation (Ruby or malloc) can happen.
1472
+ void thread_context_collector_prepare_sample_inside_signal_handler(VALUE self_instance) {
1473
+ thread_context_collector_state *state;
1474
+ if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return;
1475
+ // This should never fail if the above check passes
1476
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1477
+
1478
+ VALUE current_thread = rb_thread_current();
1479
+ per_thread_context *thread_context = get_context_for(current_thread, state);
1480
+ if (thread_context == NULL) return;
1481
+
1482
+ prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
1483
+ }
1484
+
1439
1485
  void thread_context_collector_sample_allocation(VALUE self_instance, unsigned int sample_weight, VALUE new_object) {
1440
1486
  thread_context_collector_state *state;
1441
1487
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
@@ -1515,7 +1561,7 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
1515
1561
  /* thread: */ current_thread,
1516
1562
  /* stack_from_thread: */ current_thread,
1517
1563
  thread_context,
1518
- thread_context->sampling_buffer,
1564
+ &thread_context->sampling_buffer,
1519
1565
  (sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
1520
1566
  INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
1521
1567
  &ruby_vm_type,
@@ -1941,7 +1987,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1941
1987
  /* thread_being_sampled: */ current_thread,
1942
1988
  /* stack_from_thread: */ current_thread,
1943
1989
  thread_context,
1944
- thread_context->sampling_buffer,
1990
+ &thread_context->sampling_buffer,
1945
1991
  cpu_time_for_thread,
1946
1992
  current_monotonic_wall_time_ns
1947
1993
  );
@@ -2169,3 +2215,8 @@ static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE c
2169
2215
 
2170
2216
  return LONG2NUM(system_epoch_time_ns);
2171
2217
  }
2218
+
2219
+ static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
2220
+ thread_context_collector_prepare_sample_inside_signal_handler(collector_instance);
2221
+ return Qtrue;
2222
+ }
@@ -10,6 +10,7 @@ void thread_context_collector_sample(
10
10
  long current_monotonic_wall_time_ns,
11
11
  VALUE profiler_overhead_stack_thread
12
12
  );
13
+ void thread_context_collector_prepare_sample_inside_signal_handler(VALUE self_instance);
13
14
  void thread_context_collector_sample_allocation(VALUE self_instance, unsigned int sample_weight, VALUE new_object);
14
15
  void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples);
15
16
  VALUE thread_context_collector_sample_after_gc(VALUE self_instance);
@@ -38,6 +38,26 @@ VALUE from_ddog_prof_EncodedProfile(ddog_prof_EncodedProfile profile) {
38
38
  return TypedData_Wrap_Struct(encoded_profile_class, &encoded_profile_typed_data, state);
39
39
  }
40
40
 
41
+ static ddog_ByteSlice get_bytes(ddog_prof_EncodedProfile *state) {
42
+ ddog_prof_Result_ByteSlice raw_bytes = ddog_prof_EncodedProfile_bytes(state);
43
+ if (raw_bytes.tag == DDOG_PROF_RESULT_BYTE_SLICE_ERR_BYTE_SLICE) {
44
+ rb_raise(rb_eRuntimeError, "Failed to get bytes from profile: %"PRIsVALUE, get_error_details_and_drop(&raw_bytes.err));
45
+ }
46
+ return raw_bytes.ok;
47
+ }
48
+
49
+ static ddog_prof_EncodedProfile *internal_to_ddog_prof_EncodedProfile(VALUE object) {
50
+ ddog_prof_EncodedProfile *state;
51
+ TypedData_Get_Struct(object, ddog_prof_EncodedProfile, &encoded_profile_typed_data, state);
52
+ return state;
53
+ }
54
+
55
+ ddog_prof_EncodedProfile *to_ddog_prof_EncodedProfile(VALUE object) {
56
+ ddog_prof_EncodedProfile *state = internal_to_ddog_prof_EncodedProfile(object);
57
+ get_bytes(state); // Validate profile is still usable -- if it's not, this will raise an exception
58
+ return state;
59
+ }
60
+
41
61
  static void encoded_profile_typed_data_free(void *state_ptr) {
42
62
  ddog_prof_EncodedProfile *state = (ddog_prof_EncodedProfile *) state_ptr;
43
63
 
@@ -49,18 +69,8 @@ static void encoded_profile_typed_data_free(void *state_ptr) {
49
69
  }
50
70
 
51
71
  static VALUE _native_bytes(VALUE self) {
52
- ddog_prof_EncodedProfile *state;
53
- TypedData_Get_Struct(self, ddog_prof_EncodedProfile, &encoded_profile_typed_data, state);
54
-
55
- return ruby_string_from_vec_u8(state->buffer);
56
-
57
- // TODO: This will be used for libdatadog 17
58
- /*ddog_prof_Result_ByteSlice raw_bytes = ddog_prof_EncodedProfile_bytes(state);
59
- if (raw_bytes.tag == DDOG_PROF_RESULT_BYTE_SLICE_ERR_BYTE_SLICE) {
60
- rb_raise(rb_eRuntimeError, "Failed to get bytes from profile: %"PRIsVALUE, get_error_details_and_drop(&raw_bytes.err));
61
- }
62
-
63
- return rb_str_new((const char *) raw_bytes.ok.ptr, raw_bytes.ok.len);*/
72
+ ddog_ByteSlice bytes = get_bytes(internal_to_ddog_prof_EncodedProfile(self));
73
+ return rb_str_new((const char *) bytes.ptr, bytes.len);
64
74
  }
65
75
 
66
76
  VALUE enforce_encoded_profile_instance(VALUE object) {
@@ -5,3 +5,4 @@
5
5
 
6
6
  VALUE from_ddog_prof_EncodedProfile(ddog_prof_EncodedProfile profile);
7
7
  VALUE enforce_encoded_profile_instance(VALUE object);
8
+ ddog_prof_EncodedProfile *to_ddog_prof_EncodedProfile(VALUE object);
@@ -131,6 +131,13 @@ end
131
131
 
132
132
  have_func "malloc_stats"
133
133
 
134
+ # Used to get native filenames (dladdr1 is preferred, so we only check for the other if not available)
135
+ # Note it's possible none are available
136
+ if have_header("dlfcn.h")
137
+ (have_struct_member("struct link_map", "l_name", "link.h") && have_func("dladdr1")) ||
138
+ have_func("dladdr")
139
+ end
140
+
134
141
  # On Ruby 3.5, we can't ask the object_id from IMEMOs (https://github.com/ruby/ruby/pull/13347)
135
142
  $defs << "-DNO_IMEMO_OBJECT_ID" unless RUBY_VERSION < "3.5"
136
143