datadog 2.39.0 → 2.40.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -1
  3. data/ext/datadog_profiling_native_extension/collectors_stack.c +218 -147
  4. data/ext/datadog_profiling_native_extension/collectors_stack.h +16 -2
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +12 -7
  6. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +5 -4
  7. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +19 -11
  8. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +7 -0
  9. data/ext/datadog_profiling_native_extension/extconf.rb +12 -7
  10. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +259 -65
  11. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +16 -10
  12. data/ext/libdatadog_api/datadog_ruby_common.c +19 -11
  13. data/ext/libdatadog_api/datadog_ruby_common.h +7 -0
  14. data/ext/libdatadog_api/init.c +4 -0
  15. data/ext/libdatadog_api/trace_exporter.c +974 -0
  16. data/ext/libdatadog_api/trace_exporter.h +5 -0
  17. data/lib/datadog/ai_guard/evaluation.rb +1 -0
  18. data/lib/datadog/ai_guard/ext.rb +2 -0
  19. data/lib/datadog/appsec/configuration.rb +9 -0
  20. data/lib/datadog/appsec/contrib/devise/patcher.rb +1 -1
  21. data/lib/datadog/appsec/contrib/rack/patcher.rb +1 -1
  22. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +6 -3
  23. data/lib/datadog/appsec/contrib/rails/request_middleware.rb +2 -1
  24. data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
  25. data/lib/datadog/core/configuration/settings.rb +33 -41
  26. data/lib/datadog/core/configuration/supported_configurations.rb +3 -0
  27. data/lib/datadog/core/environment/gc.rb +1 -1
  28. data/lib/datadog/core/environment/vm_cache.rb +1 -1
  29. data/lib/datadog/core/environment/yjit.rb +2 -2
  30. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
  31. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +80 -12
  32. data/lib/datadog/core/utils/sequence.rb +3 -6
  33. data/lib/datadog/core/utils/time.rb +16 -54
  34. data/lib/datadog/di/el/compiler.rb +65 -12
  35. data/lib/datadog/di/el/evaluator.rb +82 -2
  36. data/lib/datadog/di/el/expression.rb +10 -2
  37. data/lib/datadog/di/probe_builder.rb +6 -6
  38. data/lib/datadog/profiling/collectors/thread_context.rb +6 -13
  39. data/lib/datadog/profiling/component.rb +1 -0
  40. data/lib/datadog/profiling.rb +4 -0
  41. data/lib/datadog/tracing/component.rb +21 -0
  42. data/lib/datadog/tracing/configuration/ext.rb +1 -0
  43. data/lib/datadog/tracing/configuration/settings.rb +16 -0
  44. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +2 -1
  45. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +4 -0
  46. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +7 -2
  47. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +7 -2
  48. data/lib/datadog/tracing/contrib/sequel/database.rb +1 -5
  49. data/lib/datadog/tracing/distributed/propagation_policy.rb +5 -0
  50. data/lib/datadog/tracing/sync_writer.rb +8 -3
  51. data/lib/datadog/tracing/tracer.rb +8 -0
  52. data/lib/datadog/tracing/transport/native/response.rb +71 -0
  53. data/lib/datadog/tracing/transport/native.rb +363 -0
  54. data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
  55. data/lib/datadog/tracing/writer.rb +16 -3
  56. data/lib/datadog/version.rb +1 -1
  57. metadata +9 -5
@@ -8,7 +8,7 @@
8
8
  #define MAX_FRAMES_LIMIT 3000
9
9
  #define MAX_FRAMES_LIMIT_AS_STRING "3000"
10
10
 
11
- // Used as scratch space during sampling
11
+ // Per thread, where we store the stack sample
12
12
  typedef struct {
13
13
  uint16_t max_frames;
14
14
  frame_info *stack_buffer;
@@ -17,9 +17,19 @@ typedef struct {
17
17
  int pending_sample_result;
18
18
  } sampling_buffer;
19
19
 
20
+ // 1 per ThreadContext so effectively global.
21
+ // Used to pass the stack of ddog_prof_Location's to libdatadog.
20
22
  typedef struct {
21
23
  ddog_prof_Location *ptr;
22
24
  uint16_t len;
25
+ // This buffer is used when composing qualified method names (e.g. with module/class name).
26
+ // Because we get only the individual parts from Ruby, we need scratch space to lay out the contiguous frame name for libdatadog to use.
27
+ // The same big buffer is used to try to contain all of the qualified method names needed for a given stack, one after another.
28
+ // Whenever we run out of space, we stop composing the full qualified names and fall back
29
+ // to only showing the method names for that stack.
30
+ // TODO: In the future we'd hopefully intern the qualified names into libdatadog and stop needing this buffer.
31
+ char *qualified_name_buf;
32
+ size_t qualified_name_buf_size;
23
33
  } sample_locations;
24
34
 
25
35
  void sample_thread(
@@ -30,7 +40,8 @@ void sample_thread(
30
40
  sample_values values,
31
41
  sample_labels labels,
32
42
  bool native_filenames_enabled,
33
- st_table *native_filenames_cache
43
+ st_table *native_filenames_cache,
44
+ bool show_classes
34
45
  );
35
46
  void record_placeholder_stack(
36
47
  VALUE recorder_instance,
@@ -40,6 +51,9 @@ void record_placeholder_stack(
40
51
  );
41
52
  bool prepare_sample_thread(VALUE thread, sampling_buffer *buffer);
42
53
 
54
+ void sample_locations_initialize(sample_locations *locations, uint16_t max_frames, bool show_classes);
55
+ void sample_locations_free(sample_locations *locations);
56
+
43
57
  uint16_t sampling_buffer_check_max_frames(int max_frames);
44
58
  void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames);
45
59
  void sampling_buffer_free(sampling_buffer *buffer);
@@ -153,6 +153,8 @@ typedef struct {
153
153
  VALUE otel_current_span_key;
154
154
  // Used to enable native filenames in stack traces
155
155
  bool native_filenames_enabled;
156
+ // Used to include class/module names in method names (e.g. "Foo#bar" instead of "bar")
157
+ bool show_classes;
156
158
  // Used to cache native filename lookup results (Map[void *function_pointer, char *filename])
157
159
  st_table *native_filenames_cache;
158
160
  // Used to attribute overhead during sampling to this component
@@ -493,7 +495,7 @@ static void thread_context_collector_typed_data_free(void *state_ptr) {
493
495
 
494
496
  // Important: Remember that we're only guaranteed to see here what's been set in _native_new, aka
495
497
  // pointers that have been set NULL there may still be NULL here.
496
- if (state->locations.ptr != NULL) ruby_xfree(state->locations.ptr);
498
+ sample_locations_free(&state->locations);
497
499
 
498
500
  st_free_table(state->native_filenames_cache);
499
501
 
@@ -550,14 +552,13 @@ static VALUE _native_new(VALUE klass) {
550
552
  // being leaked.
551
553
 
552
554
  // Update this when modifying state struct
553
- state->locations.ptr = NULL;
554
- state->locations.len = 0;
555
555
  state->recorder_instance = Qnil;
556
556
  state->tracer_context_key = MISSING_TRACER_CONTEXT_KEY;
557
557
  VALUE thread_list_buffer = rb_ary_new();
558
558
  state->thread_list_buffer = thread_list_buffer;
559
559
  state->endpoint_collection_enabled = true;
560
560
  state->native_filenames_enabled = false;
561
+ state->show_classes = false;
561
562
  state->native_filenames_cache = st_init_numtable();
562
563
  state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
563
564
  state->otel_context_source = OTEL_CONTEXT_SOURCE_UNKNOWN;
@@ -593,12 +594,14 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
593
594
  VALUE waiting_for_gvl_threshold_ns = rb_hash_fetch(options, ID2SYM(rb_intern("waiting_for_gvl_threshold_ns")));
594
595
  VALUE otel_context_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("otel_context_enabled")));
595
596
  VALUE native_filenames_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("native_filenames_enabled")));
597
+ VALUE show_classes = rb_hash_fetch(options, ID2SYM(rb_intern("show_classes")));
596
598
  VALUE overhead_filename = rb_hash_fetch(options, ID2SYM(rb_intern("overhead_filename")));
597
599
 
598
600
  ENFORCE_TYPE(max_frames, T_FIXNUM);
599
601
  ENFORCE_BOOLEAN(endpoint_collection_enabled);
600
602
  ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
601
603
  ENFORCE_BOOLEAN(native_filenames_enabled);
604
+ ENFORCE_BOOLEAN(show_classes);
602
605
  ENFORCE_TYPE(overhead_filename, T_STRING);
603
606
 
604
607
  uint16_t max_frame_int = sampling_buffer_check_max_frames(NUM2INT(max_frames));
@@ -607,12 +610,12 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
607
610
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
608
611
 
609
612
  // Update this when modifying state struct
610
- state->locations.len = max_frame_int;
611
- state->locations.ptr = ruby_xcalloc(max_frame_int, sizeof(ddog_prof_Location));
613
+ sample_locations_initialize(&state->locations, max_frame_int, show_classes == Qtrue);
612
614
  state->recorder_instance = enforce_recorder_instance(recorder_instance);
613
615
  recorder_install_on_serialize(recorder_instance, self_instance);
614
616
  state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
615
617
  state->native_filenames_enabled = (native_filenames_enabled == Qtrue);
618
+ state->show_classes = (show_classes == Qtrue);
616
619
  state->overhead_filename = overhead_filename;
617
620
  if (otel_context_enabled == Qfalse || otel_context_enabled == Qnil) {
618
621
  state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
@@ -949,7 +952,7 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
949
952
 
950
953
  // Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample.
951
954
  // We don't do the same for wall-time, because GC is just like any other reason a thread didn't make
952
- // progress -- time always goes forward regardless of the thread making progress on what it wanted.
955
+ // progress -- time always goes forward regardless of the thread making progress on what it wanted.
953
956
  if (thread_context->cpu_time_at_previous_sample_ns != INVALID_TIME) {
954
957
  thread_context->cpu_time_at_previous_sample_ns += gc_cpu_time_elapsed_ns;
955
958
  }
@@ -1157,7 +1160,8 @@ static void trigger_sample_for_thread(
1157
1160
  .is_gvl_waiting_state = is_gvl_waiting_state,
1158
1161
  },
1159
1162
  state->native_filenames_enabled,
1160
- state->native_filenames_cache
1163
+ state->native_filenames_cache,
1164
+ state->show_classes
1161
1165
  );
1162
1166
  }
1163
1167
 
@@ -1329,6 +1333,7 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
1329
1333
  rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_to_ruby_hash(state, rb_hash_new())));
1330
1334
  rb_str_concat(result, rb_sprintf(" endpoint_collection_enabled=%"PRIsVALUE, state->endpoint_collection_enabled ? Qtrue : Qfalse));
1331
1335
  rb_str_concat(result, rb_sprintf(" native_filenames_enabled=%"PRIsVALUE, state->native_filenames_enabled ? Qtrue : Qfalse));
1336
+ rb_str_concat(result, rb_sprintf(" show_classes=%"PRIsVALUE, state->show_classes ? Qtrue : Qfalse));
1332
1337
  // Note: `st_table_size()` is available from Ruby 3.2+ but not before
1333
1338
  rb_str_concat(result, rb_sprintf(" native_filenames_cache_size=%zu", state->native_filenames_cache->num_entries));
1334
1339
  rb_str_concat(result, rb_sprintf(" otel_context_enabled=%d", state->otel_context_enabled));
@@ -159,7 +159,7 @@ static void ruby_runtime_stack_callback(
159
159
  frame_info *info = &runtime_stack_buffer[i];
160
160
 
161
161
  if (info->is_ruby_frame) {
162
- const void *iseq = (const void *)info->as.ruby_frame.iseq;
162
+ const rb_iseq_t *iseq = info->as.ruby_frame.iseq;
163
163
  ddog_CharSlice function_slice = DDOG_CHARSLICE_C("<unknown>");
164
164
  ddog_CharSlice file_slice = DDOG_CHARSLICE_C("<unknown>");
165
165
 
@@ -181,9 +181,10 @@ static void ruby_runtime_stack_callback(
181
181
  ddog_CharSlice function_slice = DDOG_CHARSLICE_C("<C method>");
182
182
  ddog_CharSlice file_slice = DDOG_CHARSLICE_C("<C extension>");
183
183
 
184
- if (info->as.native_frame.method_id) {
185
- const char *method_name = rb_id2name(info->as.native_frame.method_id);
186
- if (is_pointer_readable(method_name, 256)) {
184
+ const rb_callable_method_entry_t *cme = info->cme;
185
+ if (cme && is_pointer_readable(cme, sizeof_rb_callable_method_entry_t())) {
186
+ const char *method_name = ddtrace_cme_original_method_name(cme);
187
+ if (method_name && is_pointer_readable(method_name, 256)) {
187
188
  size_t method_name_len = strnlen(method_name, 256);
188
189
  if (method_name_len > 0 && method_name_len < 256) {
189
190
  function_slice = char_slice_from_cstr(method_name);
@@ -6,19 +6,27 @@
6
6
  static ID telemetry_message_id;
7
7
 
8
8
  void raise_unexpected_type(VALUE value, const char *value_name, const char *type_name, const char *file, int line, const char *function_name) {
9
- rb_exc_raise(
10
- rb_exc_new_str(
11
- rb_eTypeError,
12
- rb_sprintf("wrong argument %"PRIsVALUE" for '%s' (expected a %s) at %s:%d:in `%s'",
13
- rb_inspect(value),
14
- value_name,
15
- type_name,
16
- file,
17
- line,
18
- function_name
19
- )
9
+ // All of the below come from `ENFORCE_TYPE` and are constant for their call-site
10
+ VALUE simple_message = rb_sprintf("wrong argument for '%s' (expected a %s) at %s:%d:in `%s'",
11
+ value_name,
12
+ type_name,
13
+ file,
14
+ line,
15
+ function_name
16
+ );
17
+ VALUE exception = rb_exc_new_str(
18
+ rb_eTypeError,
19
+ rb_sprintf("wrong argument %"PRIsVALUE" for '%s' (expected a %s) at %s:%d:in `%s'",
20
+ rb_inspect(value),
21
+ value_name,
22
+ type_name,
23
+ file,
24
+ line,
25
+ function_name
20
26
  )
21
27
  );
28
+ rb_ivar_set(exception, telemetry_message_id, simple_message);
29
+ rb_exc_raise(exception);
22
30
  }
23
31
 
24
32
  // Raises an exception with separate telemetry-safe and detailed messages.
@@ -5,6 +5,13 @@
5
5
  #include <ruby.h>
6
6
  #include <datadog/common.h>
7
7
 
8
+ // All recommended backward compatible macros for keyword argument functions can be found here:
9
+ // https://github.com/ruby/ruby/blob/master/doc/extension.rdoc#defining-backward-compatible-macros-for-keyword-argument-functions
10
+ #ifndef RB_PASS_KEYWORDS
11
+ /* Only define macros on Ruby <2.7 */
12
+ #define rb_funcallv_kw(o, m, c, v, kw) rb_funcallv(o, m, c, v)
13
+ #endif
14
+
8
15
  // Must be called once during initialization
9
16
  void datadog_ruby_common_init(void);
10
17
 
@@ -139,13 +139,6 @@ end
139
139
 
140
140
  have_func "malloc_stats"
141
141
 
142
- # Used to get native filenames (dladdr1 is preferred, so we only check for the other if not available)
143
- # Note it's possible none are available
144
- if have_header("dlfcn.h")
145
- (have_struct_member("struct link_map", "l_name", "link.h") && have_func("dladdr1")) ||
146
- have_func("dladdr")
147
- end
148
-
149
142
  # On older Rubies, there was no primitive mutex and condition variable implemented in `thread_sync.rb` (internal)
150
143
  $defs << "-DNO_PRIMITIVE_MUTEX_AND_CONDITION_VARIABLE" if RUBY_VERSION < "4"
151
144
 
@@ -182,9 +175,18 @@ $defs << "-DUSE_RACTOR_INTERNAL_APIS_DIRECTLY" if RUBY_VERSION < "3.3"
182
175
  # On older Rubies, there was no GVL instrumentation API and APIs created to support it
183
176
  $defs << "-DNO_GVL_INSTRUMENTATION" if RUBY_VERSION < "3.2"
184
177
 
178
+ # On older Rubies, rb_class_attached_object is not available
179
+ $defs << "-DNO_CLASS_ATTACHED_OBJECT" if RUBY_VERSION < "3.2"
180
+
181
+ # On older Rubies, rb_mod_name allocates (rb_str_dup); use rb_attr_get(__classpath__) instead
182
+ $defs << "-DNO_ALLOC_FREE_MOD_NAME" if RUBY_VERSION < "2.7"
183
+
185
184
  # rb_internal_thread_specific_*()
186
185
  $defs << "-DHAVE_RUBY_THREAD_STORAGE_API" if RUBY_VERSION >= "3.3"
187
186
 
187
+ # RCLASS_EXT(mod)->permanent_classpath
188
+ $defs << "-DHAVE_PERMANENT_CLASSPATH" if RUBY_VERSION >= "3.3"
189
+
188
190
  # On older Rubies, there was no struct rb_native_thread. See private_vm_api_acccess.c for details.
189
191
  $defs << "-DNO_RB_NATIVE_THREAD" if RUBY_VERSION < "3.2"
190
192
 
@@ -207,6 +209,9 @@ $defs << "-DNO_THREAD_TID" if RUBY_VERSION < "3.1"
207
209
  # On older Rubies, there was no jit_return member on the rb_control_frame_t struct
208
210
  $defs << "-DNO_JIT_RETURN" if RUBY_VERSION < "3.1"
209
211
 
212
+ # internal/class.h is available since 3.0
213
+ $defs << "-DNO_INTERNAL_CLASS_HEADER_INCLUDE" if RUBY_VERSION < "3"
214
+
210
215
  # On older Rubies, there are no Ractors
211
216
  $defs << "-DNO_RACTORS" if RUBY_VERSION < "3"
212
217