datadog 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -2
  3. data/ext/datadog_profiling_loader/extconf.rb +15 -15
  4. data/ext/datadog_profiling_native_extension/clock_id.h +1 -0
  5. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -2
  6. data/ext/datadog_profiling_native_extension/clock_id_noop.c +1 -2
  7. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +113 -43
  8. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +49 -26
  9. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +34 -4
  10. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +4 -0
  11. data/ext/datadog_profiling_native_extension/collectors_stack.c +49 -37
  12. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  13. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +81 -19
  14. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
  15. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +110 -0
  16. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +57 -0
  17. data/ext/datadog_profiling_native_extension/extconf.rb +65 -60
  18. data/ext/datadog_profiling_native_extension/heap_recorder.c +34 -6
  19. data/ext/datadog_profiling_native_extension/heap_recorder.h +3 -1
  20. data/ext/datadog_profiling_native_extension/helpers.h +6 -17
  21. data/ext/datadog_profiling_native_extension/http_transport.c +3 -3
  22. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +0 -86
  23. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +2 -23
  24. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +61 -172
  25. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +64 -138
  26. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +17 -11
  27. data/ext/datadog_profiling_native_extension/profiling.c +0 -2
  28. data/ext/datadog_profiling_native_extension/ruby_helpers.c +0 -33
  29. data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -26
  30. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -0
  31. data/ext/datadog_profiling_native_extension/stack_recorder.c +14 -2
  32. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
  33. data/ext/datadog_profiling_native_extension/time_helpers.c +0 -15
  34. data/ext/datadog_profiling_native_extension/time_helpers.h +36 -6
  35. data/ext/{datadog_profiling_native_extension → libdatadog_api}/crashtracker.c +19 -6
  36. data/ext/libdatadog_api/datadog_ruby_common.c +110 -0
  37. data/ext/libdatadog_api/datadog_ruby_common.h +57 -0
  38. data/ext/libdatadog_api/extconf.rb +108 -0
  39. data/ext/libdatadog_api/macos_development.md +26 -0
  40. data/ext/libdatadog_extconf_helpers.rb +130 -0
  41. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +49 -0
  42. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +73 -0
  43. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +68 -0
  44. data/lib/datadog/appsec/contrib/graphql/integration.rb +41 -0
  45. data/lib/datadog/appsec/contrib/graphql/patcher.rb +37 -0
  46. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +59 -0
  47. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
  48. data/lib/datadog/appsec/processor/actions.rb +1 -1
  49. data/lib/datadog/appsec/response.rb +15 -1
  50. data/lib/datadog/appsec.rb +1 -0
  51. data/lib/datadog/core/configuration/components.rb +14 -12
  52. data/lib/datadog/core/configuration/settings.rb +54 -7
  53. data/lib/datadog/core/crashtracking/agent_base_url.rb +21 -0
  54. data/lib/datadog/core/crashtracking/component.rb +111 -0
  55. data/lib/datadog/core/crashtracking/tag_builder.rb +39 -0
  56. data/lib/datadog/core/diagnostics/environment_logger.rb +8 -11
  57. data/lib/datadog/core/telemetry/component.rb +49 -2
  58. data/lib/datadog/core/telemetry/emitter.rb +9 -11
  59. data/lib/datadog/core/telemetry/event.rb +32 -1
  60. data/lib/datadog/core/telemetry/ext.rb +1 -0
  61. data/lib/datadog/core/telemetry/http/adapters/net.rb +10 -12
  62. data/lib/datadog/core/telemetry/http/ext.rb +3 -0
  63. data/lib/datadog/core/telemetry/http/transport.rb +38 -9
  64. data/lib/datadog/core/telemetry/logging.rb +35 -0
  65. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
  66. data/lib/datadog/kit/appsec/events.rb +2 -4
  67. data/lib/datadog/opentelemetry/sdk/span_processor.rb +10 -0
  68. data/lib/datadog/opentelemetry/sdk/trace/span.rb +23 -0
  69. data/lib/datadog/profiling/collectors/code_provenance.rb +7 -7
  70. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +17 -17
  71. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
  72. data/lib/datadog/profiling/collectors/info.rb +3 -3
  73. data/lib/datadog/profiling/collectors/thread_context.rb +4 -2
  74. data/lib/datadog/profiling/component.rb +69 -91
  75. data/lib/datadog/profiling/exporter.rb +3 -3
  76. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
  77. data/lib/datadog/profiling/ext.rb +21 -21
  78. data/lib/datadog/profiling/flush.rb +1 -1
  79. data/lib/datadog/profiling/http_transport.rb +8 -6
  80. data/lib/datadog/profiling/load_native_extension.rb +5 -5
  81. data/lib/datadog/profiling/preload.rb +1 -1
  82. data/lib/datadog/profiling/profiler.rb +5 -8
  83. data/lib/datadog/profiling/scheduler.rb +31 -25
  84. data/lib/datadog/profiling/tag_builder.rb +2 -2
  85. data/lib/datadog/profiling/tasks/exec.rb +5 -5
  86. data/lib/datadog/profiling/tasks/setup.rb +16 -35
  87. data/lib/datadog/profiling.rb +4 -5
  88. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
  89. data/lib/datadog/tracing/contrib/ext.rb +14 -0
  90. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +1 -1
  91. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +4 -1
  92. data/lib/datadog/tracing/contrib/lograge/patcher.rb +16 -0
  93. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +5 -0
  94. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +17 -13
  95. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
  96. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
  97. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +28 -0
  98. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
  99. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +22 -10
  100. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +5 -0
  101. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -1
  102. data/lib/datadog/tracing/diagnostics/environment_logger.rb +14 -16
  103. data/lib/datadog/tracing/metadata/errors.rb +9 -1
  104. data/lib/datadog/tracing/metadata/ext.rb +4 -0
  105. data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
  106. data/lib/datadog/tracing/span.rb +9 -2
  107. data/lib/datadog/tracing/span_event.rb +41 -0
  108. data/lib/datadog/tracing/span_operation.rb +6 -2
  109. data/lib/datadog/tracing/transport/serializable_trace.rb +3 -0
  110. data/lib/datadog/version.rb +1 -1
  111. metadata +28 -10
  112. data/lib/datadog/profiling/crashtracker.rb +0 -91
  113. data/lib/datadog/profiling/ext/forking.rb +0 -98
@@ -18,6 +18,22 @@ typedef struct {
18
18
  rb_nativethread_id_t owner;
19
19
  } current_gvl_owner;
20
20
 
21
+ typedef struct frame_info {
22
+ union {
23
+ struct {
24
+ VALUE iseq;
25
+ void *caching_pc; // For caching only
26
+ int line;
27
+ } ruby_frame;
28
+ struct {
29
+ VALUE caching_cme; // For caching only
30
+ ID method_id;
31
+ } native_frame;
32
+ } as;
33
+ bool is_ruby_frame : 1;
34
+ bool same_frame : 1;
35
+ } frame_info;
36
+
21
37
  rb_nativethread_id_t pthread_id_for(VALUE thread);
22
38
  bool is_current_thread_holding_the_gvl(void);
23
39
  current_gvl_owner gvl_owner(void);
@@ -27,20 +43,10 @@ void ddtrace_thread_list(VALUE result_array);
27
43
  bool is_thread_alive(VALUE thread);
28
44
  VALUE thread_name_for(VALUE thread);
29
45
 
30
- int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, VALUE *buff, int *lines, bool* is_ruby_frame);
46
+ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *stack_buffer);
31
47
  // Returns true if the current thread belongs to the main Ractor or if Ruby has no Ractor support
32
48
  bool ddtrace_rb_ractor_main_p(void);
33
49
 
34
- // Ruby 3.0 finally added support for showing CFUNC frames (frames for methods written using native code)
35
- // in stack traces gathered via `rb_profile_frames` (https://github.com/ruby/ruby/pull/3299).
36
- // To access this information on older Rubies, beyond using our custom `ddtrace_rb_profile_frames` above, we also need
37
- // to backport the Ruby 3.0+ version of `rb_profile_frame_method_name`.
38
- #ifdef USE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME
39
- VALUE ddtrace_rb_profile_frame_method_name(VALUE frame);
40
- #else // Ruby > 3.0, just use the stock functionality
41
- #define ddtrace_rb_profile_frame_method_name rb_profile_frame_method_name
42
- #endif
43
-
44
50
  // 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)
45
51
  #define PLACEHOLDER_STACK_IN_NATIVE_CODE -1
46
52
 
@@ -19,7 +19,6 @@ void collectors_dynamic_sampling_rate_init(VALUE profiling_module);
19
19
  void collectors_idle_sampling_helper_init(VALUE profiling_module);
20
20
  void collectors_stack_init(VALUE profiling_module);
21
21
  void collectors_thread_context_init(VALUE profiling_module);
22
- void crashtracker_init(VALUE profiling_module);
23
22
  void http_transport_init(VALUE profiling_module);
24
23
  void stack_recorder_init(VALUE profiling_module);
25
24
 
@@ -54,7 +53,6 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
54
53
  collectors_idle_sampling_helper_init(profiling_module);
55
54
  collectors_stack_init(profiling_module);
56
55
  collectors_thread_context_init(profiling_module);
57
- crashtracker_init(profiling_module);
58
56
  http_transport_init(profiling_module);
59
57
  stack_recorder_init(profiling_module);
60
58
 
@@ -20,29 +20,6 @@ void ruby_helpers_init(void) {
20
20
  to_s_id = rb_intern("to_s");
21
21
  }
22
22
 
23
- void raise_unexpected_type(
24
- VALUE value,
25
- const char *value_name,
26
- const char *type_name,
27
- const char *file,
28
- int line,
29
- const char* function_name
30
- ) {
31
- rb_exc_raise(
32
- rb_exc_new_str(
33
- rb_eTypeError,
34
- rb_sprintf("wrong argument %"PRIsVALUE" for '%s' (expected a %s) at %s:%d:in `%s'",
35
- rb_inspect(value),
36
- value_name,
37
- type_name,
38
- file,
39
- line,
40
- function_name
41
- )
42
- )
43
- );
44
- }
45
-
46
23
  #define MAX_RAISE_MESSAGE_SIZE 256
47
24
 
48
25
  struct raise_arguments {
@@ -255,13 +232,3 @@ VALUE ruby_safe_inspect(VALUE obj) {
255
232
  return rb_str_new_cstr("(Not inspectable)");
256
233
  }
257
234
  }
258
-
259
- VALUE ddtrace_version(void) {
260
- VALUE ddtrace_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
261
- ENFORCE_TYPE(ddtrace_module, T_MODULE);
262
- VALUE version_module = rb_const_get(ddtrace_module, rb_intern("VERSION"));
263
- ENFORCE_TYPE(version_module, T_MODULE);
264
- VALUE version_string = rb_const_get(version_module, rb_intern("STRING"));
265
- ENFORCE_TYPE(version_string, T_STRING);
266
- return version_string;
267
- }
@@ -1,9 +1,7 @@
1
1
  #pragma once
2
2
 
3
- #include <ruby.h>
4
3
  #include <stdbool.h>
5
-
6
- #include "helpers.h"
4
+ #include "datadog_ruby_common.h"
7
5
 
8
6
  // Initialize internal data needed by some ruby helpers. Should be called during start, before any actual
9
7
  // usage of ruby helpers.
@@ -39,27 +37,6 @@ static inline int check_if_pending_exception(void) {
39
37
  return pending_exception;
40
38
  }
41
39
 
42
- #define ADD_QUOTES_HELPER(x) #x
43
- #define ADD_QUOTES(x) ADD_QUOTES_HELPER(x)
44
-
45
- // Ruby has a Check_Type(value, type) that is roughly equivalent to this BUT Ruby's version is rather cryptic when it fails
46
- // e.g. "wrong argument type nil (expected String)". This is a replacement that prints more information to help debugging.
47
- #define ENFORCE_TYPE(value, type) \
48
- { if (RB_UNLIKELY(!RB_TYPE_P(value, type))) raise_unexpected_type(value, ADD_QUOTES(value), ADD_QUOTES(type), __FILE__, __LINE__, __func__); }
49
-
50
- #define ENFORCE_BOOLEAN(value) \
51
- { if (RB_UNLIKELY(value != Qtrue && value != Qfalse)) raise_unexpected_type(value, ADD_QUOTES(value), "true or false", __FILE__, __LINE__, __func__); }
52
-
53
- // Called by ENFORCE_TYPE; should not be used directly
54
- NORETURN(void raise_unexpected_type(
55
- VALUE value,
56
- const char *value_name,
57
- const char *type_name,
58
- const char *file,
59
- int line,
60
- const char *function_name
61
- ));
62
-
63
40
  #define VALUE_COUNT(array) (sizeof(array) / sizeof(VALUE))
64
41
 
65
42
  NORETURN(
@@ -113,5 +90,3 @@ size_t ruby_obj_memsize_of(VALUE obj);
113
90
  // return a string with the result of that call. Elsif the object responds to
114
91
  // 'to_s', return a string with the result of that call. Otherwise, return Qnil.
115
92
  VALUE ruby_safe_inspect(VALUE obj);
116
-
117
- VALUE ddtrace_version(void);
@@ -1,6 +1,7 @@
1
1
  #pragma once
2
2
 
3
3
  #include <signal.h>
4
+ #include "datadog_ruby_common.h"
4
5
 
5
6
  void empty_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
6
7
  void install_sigprof_signal_handler(void (*signal_handler_function)(int, siginfo_t *, void *), const char *handler_pretty_name);
@@ -342,6 +342,10 @@ static VALUE _native_new(VALUE klass) {
342
342
  // Note: At this point, slot_one_profile and slot_two_profile contain null pointers. Libdatadog validates pointers
343
343
  // before using them so it's ok for us to go ahead and create the StackRecorder object.
344
344
 
345
+ // Note: As of this writing, no new Ruby objects get created and stored in the state. If that ever changes, remember
346
+ // to keep them on the stack and mark them with RB_GC_GUARD -- otherwise it's possible for a GC to run and
347
+ // since the instance representing the state does not yet exist, such objects will not get marked.
348
+
345
349
  VALUE stack_recorder = TypedData_Wrap_Struct(klass, &stack_recorder_typed_data, state);
346
350
 
347
351
  // NOTE: We initialize this because we want a new recorder to be operational even without initialization and our
@@ -612,12 +616,20 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
612
616
  metric_values[position_for[ALLOC_SAMPLES_UNSCALED_VALUE_ID]] = values.alloc_samples_unscaled;
613
617
  metric_values[position_for[TIMELINE_VALUE_ID]] = values.timeline_wall_time_ns;
614
618
 
615
- if (values.alloc_samples != 0) {
619
+ if (values.heap_sample) {
616
620
  // If we got an allocation sample end the heap allocation recording to commit the heap sample.
617
621
  // FIXME: Heap sampling currently has to be done in 2 parts because the construction of locations is happening
618
622
  // very late in the allocation-sampling path (which is shared with the cpu sampling path). This can
619
623
  // be fixed with some refactoring but for now this leads to a less impactful change.
620
- end_heap_allocation_recording(state->heap_recorder, locations);
624
+ //
625
+ // NOTE: The heap recorder is allowed to raise exceptions if something's wrong. But we also need to handle it
626
+ // on this side to make sure we properly unlock the active slot mutex on our way out. Otherwise, this would
627
+ // later lead to deadlocks (since the active slot mutex is not expected to be locked forever).
628
+ int exception_state = end_heap_allocation_recording_with_rb_protect(state->heap_recorder, locations);
629
+ if (exception_state) {
630
+ sampler_unlock_active_profile(active_slot);
631
+ rb_jump_tag(exception_state);
632
+ }
621
633
  }
622
634
 
623
635
  ddog_prof_Profile_Result result = ddog_prof_Profile_add(
@@ -9,6 +9,7 @@ typedef struct {
9
9
  uint32_t cpu_or_wall_samples;
10
10
  uint32_t alloc_samples;
11
11
  uint32_t alloc_samples_unscaled;
12
+ bool heap_sample;
12
13
  int64_t timeline_wall_time_ns;
13
14
  } sample_values;
14
15
 
@@ -4,21 +4,6 @@
4
4
  #include "ruby_helpers.h"
5
5
  #include "time_helpers.h"
6
6
 
7
- // Safety: This function is assumed never to raise exceptions by callers when raise_on_failure == false
8
- long retrieve_clock_as_ns(clockid_t clock_id, bool raise_on_failure) {
9
- struct timespec clock_value;
10
-
11
- if (clock_gettime(clock_id, &clock_value) != 0) {
12
- if (raise_on_failure) ENFORCE_SUCCESS_GVL(errno);
13
- return 0;
14
- }
15
-
16
- return clock_value.tv_nsec + SECONDS_AS_NS(clock_value.tv_sec);
17
- }
18
-
19
- long monotonic_wall_time_now_ns(bool raise_on_failure) { return retrieve_clock_as_ns(CLOCK_MONOTONIC, raise_on_failure); }
20
- long system_epoch_time_now_ns(bool raise_on_failure) { return retrieve_clock_as_ns(CLOCK_REALTIME, raise_on_failure); }
21
-
22
7
  // Design: The monotonic_to_system_epoch_state struct is kept somewhere by the caller, and MUST be initialized to
23
8
  // MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER.
24
9
  //
@@ -1,12 +1,16 @@
1
1
  #pragma once
2
2
 
3
3
  #include <stdbool.h>
4
+ #include <errno.h>
5
+ #include <time.h>
6
+
7
+ #include "extconf.h" // Needed for HAVE_CLOCK_MONOTONIC_COARSE
8
+ #include "ruby_helpers.h"
4
9
 
5
10
  #define SECONDS_AS_NS(value) (value * 1000 * 1000 * 1000L)
6
11
  #define MILLIS_AS_NS(value) (value * 1000 * 1000L)
7
12
 
8
- #define RAISE_ON_FAILURE true
9
- #define DO_NOT_RAISE_ON_FAILURE false
13
+ typedef enum { RAISE_ON_FAILURE, DO_NOT_RAISE_ON_FAILURE } raise_on_failure_setting;
10
14
 
11
15
  #define INVALID_TIME -1
12
16
 
@@ -17,10 +21,36 @@ typedef struct {
17
21
 
18
22
  #define MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER {.system_epoch_ns_reference = INVALID_TIME, .delta_to_epoch_ns = INVALID_TIME}
19
23
 
20
- // Safety: This function is assumed never to raise exceptions by callers when raise_on_failure == false
21
- long monotonic_wall_time_now_ns(bool raise_on_failure);
24
+ static inline long retrieve_clock_as_ns(clockid_t clock_id, raise_on_failure_setting raise_on_failure) {
25
+ struct timespec clock_value;
26
+
27
+ if (clock_gettime(clock_id, &clock_value) != 0) {
28
+ if (raise_on_failure == RAISE_ON_FAILURE) ENFORCE_SUCCESS_GVL(errno);
29
+ return 0;
30
+ }
31
+
32
+ return clock_value.tv_nsec + SECONDS_AS_NS(clock_value.tv_sec);
33
+ }
34
+
35
+ static inline long monotonic_wall_time_now_ns(raise_on_failure_setting raise_on_failure) { return retrieve_clock_as_ns(CLOCK_MONOTONIC, raise_on_failure); }
36
+ static inline long system_epoch_time_now_ns(raise_on_failure_setting raise_on_failure) { return retrieve_clock_as_ns(CLOCK_REALTIME, raise_on_failure); }
37
+
38
+ // Coarse instants use CLOCK_MONOTONIC_COARSE on Linux which is expected to provide resolution in the millisecond range:
39
+ // https://docs.redhat.com/en/documentation/red_hat_enterprise_linux_for_real_time/7/html/reference_guide/sect-posix_clocks#Using_clock_getres_to_compare_clock_resolution
40
+ // We introduce here a separate type for it, so as to make it harder to misuse/more explicit when these timestamps are used
41
+
42
+ typedef struct coarse_instant {
43
+ long timestamp_ns;
44
+ } coarse_instant;
45
+
46
+ static inline coarse_instant to_coarse_instant(long timestamp_ns) { return (coarse_instant) {.timestamp_ns = timestamp_ns}; }
22
47
 
23
- // Safety: This function is assumed never to raise exceptions by callers when raise_on_failure == false
24
- long system_epoch_time_now_ns(bool raise_on_failure);
48
+ static inline coarse_instant monotonic_coarse_wall_time_now_ns(void) {
49
+ #ifdef HAVE_CLOCK_MONOTONIC_COARSE // Linux
50
+ return to_coarse_instant(retrieve_clock_as_ns(CLOCK_MONOTONIC_COARSE, DO_NOT_RAISE_ON_FAILURE));
51
+ #else // macOS
52
+ return to_coarse_instant(retrieve_clock_as_ns(CLOCK_MONOTONIC, DO_NOT_RAISE_ON_FAILURE));
53
+ #endif
54
+ }
25
55
 
26
56
  long monotonic_to_system_epoch_ns(monotonic_to_system_epoch_state *state, long monotonic_wall_time_ns);
@@ -1,15 +1,25 @@
1
1
  #include <ruby.h>
2
- #include <datadog/common.h>
3
- #include <libdatadog_helpers.h>
2
+ #include <datadog/profiling.h>
3
+
4
+ #include "datadog_ruby_common.h"
4
5
 
5
6
  static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
6
7
  static VALUE _native_stop(DDTRACE_UNUSED VALUE _self);
8
+ static void crashtracker_init(VALUE crashtracking_module);
7
9
 
8
10
  // Used to report Ruby VM crashes.
9
11
  // Once initialized, segfaults will be reported automatically using libdatadog.
10
12
 
11
- void crashtracker_init(VALUE profiling_module) {
12
- VALUE crashtracker_class = rb_define_class_under(profiling_module, "Crashtracker", rb_cObject);
13
+ void DDTRACE_EXPORT Init_libdatadog_api(void) {
14
+ VALUE datadog_module = rb_define_module("Datadog");
15
+ VALUE core_module = rb_define_module_under(datadog_module, "Core");
16
+ VALUE crashtracking_module = rb_define_module_under(core_module, "Crashtracking");
17
+
18
+ crashtracker_init(crashtracking_module);
19
+ }
20
+
21
+ void crashtracker_init(VALUE crashtracking_module) {
22
+ VALUE crashtracker_class = rb_define_class_under(crashtracking_module, "Component", rb_cObject);
13
23
 
14
24
  rb_define_singleton_method(crashtracker_class, "_native_start_or_update_on_fork", _native_start_or_update_on_fork, -1);
15
25
  rb_define_singleton_method(crashtracker_class, "_native_stop", _native_stop, 0);
@@ -38,7 +48,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
38
48
 
39
49
  if (action != start_action && action != update_on_fork_action) rb_raise(rb_eArgError, "Unexpected action: %+"PRIsVALUE, action);
40
50
 
41
- VALUE version = ddtrace_version();
51
+ VALUE version = datadog_gem_version();
42
52
  ddog_prof_Endpoint endpoint = endpoint_from(exporter_configuration);
43
53
 
44
54
  // Tags are heap-allocated, so after here we can't raise exceptions otherwise we'll leak this memory
@@ -59,6 +69,9 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
59
69
  .endpoint = endpoint,
60
70
  .resolve_frames = DDOG_PROF_STACKTRACE_COLLECTION_ENABLED_WITH_SYMBOLS_IN_RECEIVER,
61
71
  .timeout_secs = FIX2INT(upload_timeout_seconds),
72
+ // Waits for crash tracker to finish reporting the issue before letting the Ruby process die; see
73
+ // https://github.com/DataDog/libdatadog/pull/477 for details
74
+ .wait_for_receiver = true,
62
75
  };
63
76
 
64
77
  ddog_prof_CrashtrackerMetadata metadata = {
@@ -83,7 +96,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
83
96
 
84
97
  ddog_prof_CrashtrackerResult result =
85
98
  action == start_action ?
86
- ddog_prof_Crashtracker_init(config, receiver_config, metadata) :
99
+ ddog_prof_Crashtracker_init_with_receiver(config, receiver_config, metadata) :
87
100
  ddog_prof_Crashtracker_update_on_fork(config, receiver_config, metadata);
88
101
 
89
102
  // Clean up before potentially raising any exceptions
@@ -0,0 +1,110 @@
1
+ #include "datadog_ruby_common.h"
2
+
3
+ // IMPORTANT: Currently this file is copy-pasted between extensions. Make sure to update all versions when doing any change!
4
+
5
+ void raise_unexpected_type(VALUE value, const char *value_name, const char *type_name, const char *file, int line, const char* function_name) {
6
+ rb_exc_raise(
7
+ rb_exc_new_str(
8
+ rb_eTypeError,
9
+ rb_sprintf("wrong argument %"PRIsVALUE" for '%s' (expected a %s) at %s:%d:in `%s'",
10
+ rb_inspect(value),
11
+ value_name,
12
+ type_name,
13
+ file,
14
+ line,
15
+ function_name
16
+ )
17
+ )
18
+ );
19
+ }
20
+
21
+ VALUE datadog_gem_version(void) {
22
+ VALUE ddtrace_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
23
+ ENFORCE_TYPE(ddtrace_module, T_MODULE);
24
+ VALUE version_module = rb_const_get(ddtrace_module, rb_intern("VERSION"));
25
+ ENFORCE_TYPE(version_module, T_MODULE);
26
+ VALUE version_string = rb_const_get(version_module, rb_intern("STRING"));
27
+ ENFORCE_TYPE(version_string, T_STRING);
28
+ return version_string;
29
+ }
30
+
31
+ __attribute__((warn_unused_result))
32
+ ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration) {
33
+ ENFORCE_TYPE(exporter_configuration, T_ARRAY);
34
+
35
+ VALUE exporter_working_mode = rb_ary_entry(exporter_configuration, 0);
36
+ ENFORCE_TYPE(exporter_working_mode, T_SYMBOL);
37
+ ID working_mode = SYM2ID(exporter_working_mode);
38
+
39
+ ID agentless_id = rb_intern("agentless");
40
+ ID agent_id = rb_intern("agent");
41
+
42
+ if (working_mode != agentless_id && working_mode != agent_id) {
43
+ rb_raise(rb_eArgError, "Failed to initialize transport: Unexpected working mode, expected :agentless or :agent");
44
+ }
45
+
46
+ if (working_mode == agentless_id) {
47
+ VALUE site = rb_ary_entry(exporter_configuration, 1);
48
+ VALUE api_key = rb_ary_entry(exporter_configuration, 2);
49
+
50
+ return ddog_prof_Endpoint_agentless(char_slice_from_ruby_string(site), char_slice_from_ruby_string(api_key));
51
+ } else { // agent_id
52
+ VALUE base_url = rb_ary_entry(exporter_configuration, 1);
53
+
54
+ return ddog_prof_Endpoint_agent(char_slice_from_ruby_string(base_url));
55
+ }
56
+ }
57
+
58
+ static VALUE log_failure_to_process_tag(VALUE err_details) {
59
+ VALUE datadog_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
60
+ VALUE logger = rb_funcall(datadog_module, rb_intern("logger"), 0);
61
+
62
+ return rb_funcall(logger, rb_intern("warn"), 1, rb_sprintf("Failed to convert tag: %"PRIsVALUE, err_details));
63
+ }
64
+
65
+ __attribute__((warn_unused_result))
66
+ ddog_Vec_Tag convert_tags(VALUE tags_as_array) {
67
+ ENFORCE_TYPE(tags_as_array, T_ARRAY);
68
+
69
+ long tags_count = RARRAY_LEN(tags_as_array);
70
+ ddog_Vec_Tag tags = ddog_Vec_Tag_new();
71
+
72
+ for (long i = 0; i < tags_count; i++) {
73
+ VALUE name_value_pair = rb_ary_entry(tags_as_array, i);
74
+
75
+ if (!RB_TYPE_P(name_value_pair, T_ARRAY)) {
76
+ ddog_Vec_Tag_drop(tags);
77
+ ENFORCE_TYPE(name_value_pair, T_ARRAY);
78
+ }
79
+
80
+ // Note: We can index the array without checking its size first because rb_ary_entry returns Qnil if out of bounds
81
+ VALUE tag_name = rb_ary_entry(name_value_pair, 0);
82
+ VALUE tag_value = rb_ary_entry(name_value_pair, 1);
83
+
84
+ if (!(RB_TYPE_P(tag_name, T_STRING) && RB_TYPE_P(tag_value, T_STRING))) {
85
+ ddog_Vec_Tag_drop(tags);
86
+ ENFORCE_TYPE(tag_name, T_STRING);
87
+ ENFORCE_TYPE(tag_value, T_STRING);
88
+ }
89
+
90
+ ddog_Vec_Tag_PushResult push_result =
91
+ ddog_Vec_Tag_push(&tags, char_slice_from_ruby_string(tag_name), char_slice_from_ruby_string(tag_value));
92
+
93
+ if (push_result.tag == DDOG_VEC_TAG_PUSH_RESULT_ERR) {
94
+ // libdatadog validates tags and may catch invalid tags that ddtrace didn't actually catch.
95
+ // We warn users about such tags, and then just ignore them.
96
+
97
+ int exception_state;
98
+ rb_protect(log_failure_to_process_tag, get_error_details_and_drop(&push_result.err), &exception_state);
99
+
100
+ // Since we are calling into Ruby code, it may raise an exception. Ensure that dynamically-allocated tags
101
+ // get cleaned before propagating the exception.
102
+ if (exception_state) {
103
+ ddog_Vec_Tag_drop(tags);
104
+ rb_jump_tag(exception_state); // "Re-raise" exception
105
+ }
106
+ }
107
+ }
108
+
109
+ return tags;
110
+ }
@@ -0,0 +1,57 @@
1
+ #pragma once
2
+
3
+ // IMPORTANT: Currently this file is copy-pasted between extensions. Make sure to update all versions when doing any change!
4
+
5
+ #include <ruby.h>
6
+ #include <datadog/profiling.h>
7
+
8
+ // Used to mark symbols to be exported to the outside of the extension.
9
+ // Consider very carefully before tagging a function with this.
10
+ #define DDTRACE_EXPORT __attribute__ ((visibility ("default")))
11
+
12
+ // Used to mark function arguments that are deliberately left unused
13
+ #ifdef __GNUC__
14
+ #define DDTRACE_UNUSED __attribute__((unused))
15
+ #else
16
+ #define DDTRACE_UNUSED
17
+ #endif
18
+
19
+ #define ADD_QUOTES_HELPER(x) #x
20
+ #define ADD_QUOTES(x) ADD_QUOTES_HELPER(x)
21
+
22
+ // Ruby has a Check_Type(value, type) that is roughly equivalent to this BUT Ruby's version is rather cryptic when it fails
23
+ // e.g. "wrong argument type nil (expected String)". This is a replacement that prints more information to help debugging.
24
+ #define ENFORCE_TYPE(value, type) \
25
+ { if (RB_UNLIKELY(!RB_TYPE_P(value, type))) raise_unexpected_type(value, ADD_QUOTES(value), ADD_QUOTES(type), __FILE__, __LINE__, __func__); }
26
+
27
+ #define ENFORCE_BOOLEAN(value) \
28
+ { if (RB_UNLIKELY(value != Qtrue && value != Qfalse)) raise_unexpected_type(value, ADD_QUOTES(value), "true or false", __FILE__, __LINE__, __func__); }
29
+
30
+ // Called by ENFORCE_TYPE; should not be used directly
31
+ NORETURN(void raise_unexpected_type(VALUE value, const char *value_name, const char *type_name, const char *file, int line, const char* function_name));
32
+
33
+ // Helper to retrieve Datadog::VERSION::STRING
34
+ VALUE datadog_gem_version(void);
35
+
36
+ static inline ddog_CharSlice char_slice_from_ruby_string(VALUE string) {
37
+ ENFORCE_TYPE(string, T_STRING);
38
+ ddog_CharSlice char_slice = {.ptr = RSTRING_PTR(string), .len = RSTRING_LEN(string)};
39
+ return char_slice;
40
+ }
41
+
42
+ __attribute__((warn_unused_result))
43
+ ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration);
44
+
45
+ __attribute__((warn_unused_result))
46
+ ddog_Vec_Tag convert_tags(VALUE tags_as_array);
47
+
48
+ static inline VALUE ruby_string_from_error(const ddog_Error *error) {
49
+ ddog_CharSlice char_slice = ddog_Error_message(error);
50
+ return rb_str_new(char_slice.ptr, char_slice.len);
51
+ }
52
+
53
+ static inline VALUE get_error_details_and_drop(ddog_Error *error) {
54
+ VALUE result = ruby_string_from_error(error);
55
+ ddog_Error_drop(error);
56
+ return result;
57
+ }
@@ -0,0 +1,108 @@
1
+ # rubocop:disable Style/StderrPuts
2
+ # rubocop:disable Style/GlobalVars
3
+
4
+ require 'rubygems'
5
+ require_relative '../libdatadog_extconf_helpers'
6
+
7
+ def skip_building_extension!(reason)
8
+ $stderr.puts(
9
+ "WARN: Skipping build of libdatadog_api (#{reason}). Some functionality will not be available."
10
+ )
11
+
12
+ fail_install_if_missing_extension = ENV['DD_FAIL_INSTALL_IF_MISSING_EXTENSION'].to_s.strip.downcase == 'true'
13
+
14
+ if fail_install_if_missing_extension
15
+ require 'mkmf'
16
+ Logging.message("[datadog] Failure cause: #{reason}")
17
+ else
18
+ File.write('Makefile', 'all install clean: # dummy makefile that does nothing')
19
+ end
20
+
21
+ exit
22
+ end
23
+
24
+ if ENV['DD_NO_EXTENSION'].to_s.strip.downcase == 'true'
25
+ skip_building_extension!('the `DD_NO_EXTENSION` environment variable is/was set to `true` during installation')
26
+ end
27
+ skip_building_extension!('current Ruby VM is not supported') if RUBY_ENGINE != 'ruby'
28
+ skip_building_extension!('Microsoft Windows is not supported') if Gem.win_platform?
29
+ skip_building_extension!('issue setting up `libdatadog` gem') if Datadog::LibdatadogExtconfHelpers.libdatadog_issue?
30
+
31
+ require 'mkmf'
32
+
33
+ # Because we can't control what compiler versions our customers use, shipping with -Werror by default is a no-go.
34
+ # But we can enable it in CI, so that we quickly spot any new warnings that just got introduced.
35
+ append_cflags '-Werror' if ENV['DATADOG_GEM_CI'] == 'true'
36
+
37
+ # Older gcc releases may not default to C99 and we need to ask for this. This is also used:
38
+ # * by upstream Ruby -- search for gnu99 in the codebase
39
+ # * by msgpack, another datadog gem dependency
40
+ # (https://github.com/msgpack/msgpack-ruby/blob/18ce08f6d612fe973843c366ac9a0b74c4e50599/ext/msgpack/extconf.rb#L8)
41
+ append_cflags '-std=gnu99'
42
+
43
+ # Allow defining variables at any point in a function
44
+ append_cflags '-Wno-declaration-after-statement'
45
+
46
+ # If we forget to include a Ruby header, the function call may still appear to work, but then
47
+ # cause a segfault later. Let's ensure that never happens.
48
+ append_cflags '-Werror-implicit-function-declaration'
49
+
50
+ # Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used.
51
+ append_cflags '-Wunused-parameter'
52
+
53
+ # The native extension is not intended to expose any symbols/functions for other native libraries to use;
54
+ # the sole exception being `Init_libdatadog_api` which needs to be visible for Ruby to call it when
55
+ # it `dlopen`s the library.
56
+ #
57
+ # By setting this compiler flag, we tell it to assume that everything is private unless explicitly stated.
58
+ # For more details see https://gcc.gnu.org/wiki/Visibility
59
+ append_cflags '-fvisibility=hidden'
60
+
61
+ # Avoid legacy C definitions
62
+ append_cflags '-Wold-style-definition'
63
+
64
+ # Enable all other compiler warnings
65
+ append_cflags '-Wall'
66
+ append_cflags '-Wextra'
67
+
68
+ if ENV['DDTRACE_DEBUG'] == 'true'
69
+ $defs << '-DDD_DEBUG'
70
+ CONFIG['optflags'] = '-O0'
71
+ CONFIG['debugflags'] = '-ggdb3'
72
+ end
73
+
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")
77
+ $stderr.puts("Using libdatadog #{Libdatadog::VERSION} from #{Libdatadog.pkgconfig_folder}")
78
+
79
+ unless pkg_config('datadog_profiling_with_rpath')
80
+ Logging.message("[datadog] Ruby detected the pkg-config command is #{$PKGCONFIG.inspect}\n")
81
+
82
+ if Datadog::LibdatadogExtconfHelpers.pkg_config_missing?
83
+ skip_building_extension!('the `pkg-config` system tool is missing')
84
+ else
85
+ skip_building_extension!('there was a problem in setting up the `libdatadog` dependency')
86
+ end
87
+ end
88
+
89
+ # See comments on the helper methods being used for why we need to additionally set this.
90
+ # The extremely excessive escaping around ORIGIN below seems to be correct and was determined after a lot of
91
+ # experimentation. We need to get these special characters across a lot of tools untouched...
92
+ extra_relative_rpaths = [
93
+ Datadog::LibdatadogExtconfHelpers.libdatadog_folder_relative_to_native_lib_folder(current_folder: __dir__),
94
+ *Datadog::LibdatadogExtconfHelpers.libdatadog_folder_relative_to_ruby_extensions_folders,
95
+ ]
96
+ extra_relative_rpaths.each { |folder| $LDFLAGS += " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{folder.to_str}" }
97
+ Logging.message("[datadog] After pkg-config $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
98
+
99
+ # Tag the native extension library with the Ruby version and Ruby platform.
100
+ # This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
101
+ # the wrong library is never loaded.
102
+ # When requiring, we need to use the exact same string, including the version and the platform.
103
+ EXTENSION_NAME = "libdatadog_api.#{RUBY_VERSION[/\d+.\d+/]}_#{RUBY_PLATFORM}".freeze
104
+
105
+ create_makefile(EXTENSION_NAME)
106
+
107
+ # rubocop:enable Style/GlobalVars
108
+ # rubocop:enable Style/StderrPuts
@@ -0,0 +1,26 @@
1
+ # Developing on macOS
2
+
3
+ As of this writing (August 2024), the libdatadog builds on rubygems.org only support Linux.
4
+
5
+ We don't officially support using libdatadog for Ruby on other platforms yet, but it is possible to use it for local development on macOS.
6
+ (**Note that you don't need these instructions if you develop inside docker.**)
7
+
8
+ Here's how you can do so:
9
+
10
+ 1. [Install rust](https://www.rust-lang.org/tools/install)
11
+ 2. Install `cbindgen`: `cargo install cbindgen`
12
+ 3. Clone [libdatadog](https://github.com/datadog/libdatadog)
13
+ 4. Create a folder for building into based on your ruby platform:
14
+
15
+ ```
16
+ export DD_RUBY_PLATFORM=`ruby -e 'puts Gem::Platform.local.to_s'`
17
+ mkdir -p my-libdatadog-build/$DD_RUBY_PLATFORM
18
+ ```
19
+
20
+ 5. Build libdatadog into this folder: `./build-profiling-ffi.sh my-libdatadog-build/$DD_RUBY_PLATFORM`
21
+ 6. Tell the Ruby where to find libdatadog: `export LIBDATADOG_VENDOR_OVERRIDE=/full/path/to/my-libdatadog-build/` (Notice no platform here)
22
+ 7. Run `bundle exec rake clean compile`
23
+
24
+ If you additionally want to run the profiler test suite, also remember to `export DD_PROFILING_MACOS_TESTING=true` and re-run `rake clean compile`.
25
+
26
+ These instructions can quickly get outdated, so feel free to open an issue if they're not working (and/or ping @ivoanjo).