ddtrace 1.6.1 → 1.8.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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -2
  3. data/README.md +2 -2
  4. data/ext/ddtrace_profiling_loader/extconf.rb +5 -2
  5. data/ext/ddtrace_profiling_native_extension/NativeExtensionDesign.md +1 -1
  6. data/ext/ddtrace_profiling_native_extension/clock_id_from_pthread.c +3 -2
  7. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.c +81 -47
  8. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time.h +1 -1
  9. data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +332 -125
  10. data/ext/ddtrace_profiling_native_extension/collectors_dynamic_sampling_rate.c +142 -0
  11. data/ext/ddtrace_profiling_native_extension/collectors_dynamic_sampling_rate.h +14 -0
  12. data/ext/ddtrace_profiling_native_extension/collectors_idle_sampling_helper.c +241 -0
  13. data/ext/ddtrace_profiling_native_extension/collectors_idle_sampling_helper.h +3 -0
  14. data/ext/ddtrace_profiling_native_extension/collectors_stack.c +11 -13
  15. data/ext/ddtrace_profiling_native_extension/extconf.rb +22 -8
  16. data/ext/ddtrace_profiling_native_extension/helpers.h +5 -0
  17. data/ext/ddtrace_profiling_native_extension/native_extension_helpers.rb +8 -0
  18. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +111 -26
  19. data/ext/ddtrace_profiling_native_extension/private_vm_api_access.h +9 -0
  20. data/ext/ddtrace_profiling_native_extension/profiling.c +205 -0
  21. data/ext/ddtrace_profiling_native_extension/ruby_helpers.c +86 -0
  22. data/ext/ddtrace_profiling_native_extension/ruby_helpers.h +28 -6
  23. data/ext/ddtrace_profiling_native_extension/setup_signal_handler.c +115 -0
  24. data/ext/ddtrace_profiling_native_extension/setup_signal_handler.h +11 -0
  25. data/ext/ddtrace_profiling_native_extension/stack_recorder.c +84 -35
  26. data/ext/ddtrace_profiling_native_extension/stack_recorder.h +1 -0
  27. data/ext/ddtrace_profiling_native_extension/time_helpers.c +17 -0
  28. data/ext/ddtrace_profiling_native_extension/time_helpers.h +10 -0
  29. data/lib/datadog/appsec/assets/blocked.html +98 -3
  30. data/lib/datadog/appsec/assets/blocked.json +1 -0
  31. data/lib/datadog/appsec/assets/blocked.text +5 -0
  32. data/lib/datadog/appsec/assets/waf_rules/recommended.json +35 -46
  33. data/lib/datadog/appsec/assets/waf_rules/risky.json +1 -1
  34. data/lib/datadog/appsec/assets/waf_rules/strict.json +46 -1
  35. data/lib/datadog/appsec/assets.rb +2 -2
  36. data/lib/datadog/appsec/configuration/settings.rb +6 -0
  37. data/lib/datadog/appsec/configuration.rb +4 -0
  38. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +4 -8
  39. data/lib/datadog/appsec/contrib/rack/request.rb +17 -0
  40. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +2 -2
  41. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +2 -2
  42. data/lib/datadog/appsec/contrib/rails/patcher.rb +3 -6
  43. data/lib/datadog/appsec/contrib/sinatra/ext.rb +1 -0
  44. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +1 -1
  45. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +11 -8
  46. data/lib/datadog/appsec/extensions.rb +10 -0
  47. data/lib/datadog/appsec/processor.rb +18 -0
  48. data/lib/datadog/appsec/response.rb +54 -0
  49. data/lib/datadog/core/configuration/components.rb +27 -6
  50. data/lib/datadog/core/configuration/ext.rb +18 -0
  51. data/lib/datadog/core/configuration/settings.rb +14 -341
  52. data/lib/datadog/core/diagnostics/health.rb +4 -22
  53. data/lib/datadog/core/environment/variable_helpers.rb +58 -10
  54. data/lib/datadog/core/runtime/ext.rb +1 -1
  55. data/lib/datadog/core/utils.rb +0 -21
  56. data/lib/datadog/core.rb +21 -1
  57. data/lib/datadog/opentracer/distributed_headers.rb +7 -9
  58. data/lib/datadog/opentracer/rack_propagator.rb +0 -3
  59. data/lib/datadog/opentracer/text_map_propagator.rb +5 -7
  60. data/lib/datadog/profiling/collectors/cpu_and_wall_time.rb +10 -4
  61. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +20 -5
  62. data/lib/datadog/profiling/collectors/dynamic_sampling_rate.rb +14 -0
  63. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +68 -0
  64. data/lib/datadog/profiling/collectors/old_stack.rb +7 -0
  65. data/lib/datadog/profiling/exporter.rb +5 -0
  66. data/lib/datadog/profiling/old_recorder.rb +8 -0
  67. data/lib/datadog/profiling/profiler.rb +7 -0
  68. data/lib/datadog/profiling/scheduler.rb +4 -7
  69. data/lib/datadog/profiling/stack_recorder.rb +36 -0
  70. data/lib/datadog/profiling/tasks/setup.rb +0 -7
  71. data/lib/datadog/profiling.rb +2 -0
  72. data/lib/datadog/tracing/configuration/ext.rb +33 -3
  73. data/lib/datadog/tracing/configuration/settings.rb +433 -0
  74. data/lib/datadog/tracing/contrib/aws/configuration/settings.rb +4 -1
  75. data/lib/datadog/tracing/contrib/aws/ext.rb +1 -0
  76. data/lib/datadog/tracing/contrib/dalli/configuration/settings.rb +4 -1
  77. data/lib/datadog/tracing/contrib/dalli/ext.rb +1 -0
  78. data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +4 -0
  79. data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +5 -1
  80. data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +1 -0
  81. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +6 -1
  82. data/lib/datadog/tracing/contrib/ethon/ext.rb +1 -0
  83. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +5 -1
  84. data/lib/datadog/tracing/contrib/excon/ext.rb +1 -0
  85. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +5 -1
  86. data/lib/datadog/tracing/contrib/faraday/ext.rb +1 -0
  87. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +6 -1
  88. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +2 -1
  89. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +6 -12
  90. data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +27 -0
  91. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +43 -0
  92. data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
  93. data/lib/datadog/tracing/contrib/grpc/patcher.rb +0 -2
  94. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +6 -1
  95. data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +32 -0
  96. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +38 -0
  97. data/lib/datadog/tracing/contrib/http/ext.rb +1 -0
  98. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +6 -1
  99. data/lib/datadog/tracing/contrib/httpclient/ext.rb +1 -0
  100. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +6 -1
  101. data/lib/datadog/tracing/contrib/httprb/ext.rb +1 -0
  102. data/lib/datadog/tracing/contrib/kafka/consumer_event.rb +1 -0
  103. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +1 -0
  104. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +1 -0
  105. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +5 -1
  106. data/lib/datadog/tracing/contrib/mongodb/ext.rb +1 -0
  107. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
  108. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +4 -1
  109. data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -0
  110. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +2 -2
  111. data/lib/datadog/tracing/contrib/patcher.rb +3 -2
  112. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +4 -1
  113. data/lib/datadog/tracing/contrib/pg/ext.rb +1 -0
  114. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +12 -2
  115. data/lib/datadog/tracing/contrib/presto/configuration/settings.rb +4 -1
  116. data/lib/datadog/tracing/contrib/presto/ext.rb +1 -0
  117. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +1 -0
  118. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +10 -12
  119. data/lib/datadog/tracing/contrib/que/tracer.rb +2 -0
  120. data/lib/datadog/tracing/contrib/racecar/events/batch.rb +4 -1
  121. data/lib/datadog/tracing/contrib/racecar/events/message.rb +4 -1
  122. data/lib/datadog/tracing/contrib/rack/middlewares.rb +2 -0
  123. data/lib/datadog/tracing/contrib/redis/configuration/settings.rb +4 -1
  124. data/lib/datadog/tracing/contrib/redis/ext.rb +1 -0
  125. data/lib/datadog/tracing/contrib/redis/instrumentation.rb +30 -21
  126. data/lib/datadog/tracing/contrib/redis/integration.rb +34 -2
  127. data/lib/datadog/tracing/contrib/redis/patcher.rb +18 -14
  128. data/lib/datadog/tracing/contrib/redis/quantize.rb +12 -9
  129. data/lib/datadog/tracing/contrib/redis/tags.rb +4 -6
  130. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +72 -0
  131. data/lib/datadog/tracing/contrib/resque/resque_job.rb +2 -0
  132. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +6 -1
  133. data/lib/datadog/tracing/contrib/rest_client/ext.rb +1 -0
  134. data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +2 -0
  135. data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +5 -0
  136. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +5 -0
  137. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +2 -0
  138. data/lib/datadog/{core → tracing}/diagnostics/ext.rb +1 -6
  139. data/lib/datadog/tracing/diagnostics/health.rb +40 -0
  140. data/lib/datadog/tracing/distributed/b3_multi.rb +66 -0
  141. data/lib/datadog/tracing/distributed/b3_single.rb +66 -0
  142. data/lib/datadog/tracing/distributed/datadog.rb +153 -0
  143. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +1 -0
  144. data/lib/datadog/tracing/distributed/fetcher.rb +30 -0
  145. data/lib/datadog/tracing/distributed/headers/ext.rb +18 -16
  146. data/lib/datadog/tracing/distributed/helpers.rb +9 -7
  147. data/lib/datadog/tracing/distributed/none.rb +19 -0
  148. data/lib/datadog/tracing/distributed/propagation.rb +127 -0
  149. data/lib/datadog/tracing/distributed/trace_context.rb +369 -0
  150. data/lib/datadog/tracing/metadata/ext.rb +1 -1
  151. data/lib/datadog/tracing/propagation/http.rb +3 -106
  152. data/lib/datadog/tracing/sampling/priority_sampler.rb +11 -0
  153. data/lib/datadog/tracing/sampling/rate_sampler.rb +3 -3
  154. data/lib/datadog/tracing/span.rb +3 -19
  155. data/lib/datadog/tracing/span_operation.rb +5 -4
  156. data/lib/datadog/tracing/trace_digest.rb +75 -2
  157. data/lib/datadog/tracing/trace_operation.rb +5 -4
  158. data/lib/datadog/tracing/trace_segment.rb +1 -1
  159. data/lib/datadog/tracing/utils.rb +50 -0
  160. data/lib/ddtrace/transport/trace_formatter.rb +2 -5
  161. data/lib/ddtrace/version.rb +2 -2
  162. metadata +35 -15
  163. data/lib/datadog/tracing/distributed/headers/b3.rb +0 -55
  164. data/lib/datadog/tracing/distributed/headers/b3_single.rb +0 -67
  165. data/lib/datadog/tracing/distributed/headers/datadog.rb +0 -144
  166. data/lib/datadog/tracing/distributed/headers/parser.rb +0 -37
  167. data/lib/datadog/tracing/distributed/metadata/b3.rb +0 -55
  168. data/lib/datadog/tracing/distributed/metadata/b3_single.rb +0 -66
  169. data/lib/datadog/tracing/distributed/metadata/datadog.rb +0 -73
  170. data/lib/datadog/tracing/distributed/metadata/parser.rb +0 -34
  171. data/lib/datadog/tracing/propagation/grpc.rb +0 -98
@@ -0,0 +1,115 @@
1
+ #include <ruby.h>
2
+ #include <signal.h>
3
+ #include <errno.h>
4
+ #include <stdbool.h>
5
+
6
+ #include "helpers.h"
7
+ #include "setup_signal_handler.h"
8
+ #include "ruby_helpers.h"
9
+
10
+ // Used by Collectors::CpuAndWallTimeWorker to setup SIGPROF signal handlers used for cpu/wall-time profiling.
11
+
12
+ static void install_sigprof_signal_handler_internal(
13
+ void (*signal_handler_function)(int, siginfo_t *, void *),
14
+ const char *handler_pretty_name,
15
+ void (*signal_handler_to_replace)(int, siginfo_t *, void *)
16
+ );
17
+
18
+ void empty_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext) { }
19
+
20
+ void install_sigprof_signal_handler(void (*signal_handler_function)(int, siginfo_t *, void *), const char *handler_pretty_name) {
21
+ install_sigprof_signal_handler_internal(signal_handler_function, handler_pretty_name, NULL);
22
+ }
23
+
24
+ void replace_sigprof_signal_handler_with_empty_handler(void (*expected_existing_handler)(int, siginfo_t *, void *)) {
25
+ install_sigprof_signal_handler_internal(empty_signal_handler, "empty_signal_handler", expected_existing_handler);
26
+ }
27
+
28
+ static void install_sigprof_signal_handler_internal(
29
+ void (*signal_handler_function)(int, siginfo_t *, void *),
30
+ const char *handler_pretty_name,
31
+ void (*signal_handler_to_replace)(int, siginfo_t *, void *)
32
+ ) {
33
+ struct sigaction existing_signal_handler_config = {.sa_sigaction = NULL};
34
+ struct sigaction signal_handler_config = {
35
+ .sa_flags = SA_RESTART | SA_SIGINFO,
36
+ .sa_sigaction = signal_handler_function
37
+ };
38
+ sigemptyset(&signal_handler_config.sa_mask);
39
+
40
+ if (sigaction(SIGPROF, &signal_handler_config, &existing_signal_handler_config) != 0) {
41
+ rb_exc_raise(rb_syserr_new_str(errno, rb_sprintf("Could not install profiling signal handler (%s)", handler_pretty_name)));
42
+ }
43
+
44
+ // Because signal handler functions are global, let's check if we're not stepping on someone else's toes.
45
+
46
+ // If the existing signal handler was our empty one, that's ok as well
47
+ if (existing_signal_handler_config.sa_sigaction == empty_signal_handler ||
48
+ // In some corner cases (e.g. after a fork), our signal handler may still be around, and that's ok
49
+ existing_signal_handler_config.sa_sigaction == signal_handler_function ||
50
+ // Are we replacing a known handler with another one?
51
+ (signal_handler_to_replace != NULL && existing_signal_handler_config.sa_sigaction == signal_handler_to_replace)
52
+ ) { return; }
53
+
54
+ if (existing_signal_handler_config.sa_handler != NULL || existing_signal_handler_config.sa_sigaction != NULL) {
55
+ // An unexpected/unknown signal handler already existed. Currently we don't support this situation, so let's just back out
56
+ // of the installation.
57
+
58
+ if (sigaction(SIGPROF, &existing_signal_handler_config, NULL) != 0) {
59
+ rb_exc_raise(
60
+ rb_syserr_new_str(
61
+ errno,
62
+ rb_sprintf(
63
+ "Failed to install profiling signal handler (%s): " \
64
+ "While installing a SIGPROF signal handler, the profiler detected that another software/library/gem had " \
65
+ "previously installed a different SIGPROF signal handler. " \
66
+ "The profiler tried to restore the previous SIGPROF signal handler, but this failed. " \
67
+ "The other software/library/gem may have been left in a broken state. ",
68
+ handler_pretty_name
69
+ )
70
+ )
71
+ );
72
+ }
73
+
74
+ rb_raise(
75
+ rb_eRuntimeError,
76
+ "Could not install profiling signal handler (%s): There's a pre-existing SIGPROF signal handler",
77
+ handler_pretty_name
78
+ );
79
+ }
80
+ }
81
+
82
+ // Note: Be careful when using this; you probably want to use `replace_sigprof_signal_handler_with_empty_handler` instead.
83
+ // (See comments on `collectors_cpu_and_wall_time_worker.c` for details)
84
+ void remove_sigprof_signal_handler(void) {
85
+ struct sigaction signal_handler_config = {
86
+ .sa_handler = SIG_DFL, // Reset back to default
87
+ .sa_flags = SA_RESTART // TODO: Unclear if this is actually needed/does anything at all
88
+ };
89
+ sigemptyset(&signal_handler_config.sa_mask);
90
+
91
+ if (sigaction(SIGPROF, &signal_handler_config, NULL) != 0) rb_sys_fail("Failure while removing the signal handler");
92
+ }
93
+
94
+ static void toggle_sigprof_signal_handler_for_current_thread(int action) {
95
+ sigset_t signals_to_toggle;
96
+ sigemptyset(&signals_to_toggle);
97
+ sigaddset(&signals_to_toggle, SIGPROF);
98
+ int error = pthread_sigmask(action, &signals_to_toggle, NULL);
99
+ if (error) rb_exc_raise(rb_syserr_new_str(error, rb_sprintf("Unexpected failure in pthread_sigmask, action=%d", action)));
100
+ }
101
+
102
+ void block_sigprof_signal_handler_from_running_in_current_thread(void) {
103
+ toggle_sigprof_signal_handler_for_current_thread(SIG_BLOCK);
104
+ }
105
+
106
+ void unblock_sigprof_signal_handler_from_running_in_current_thread(void) {
107
+ toggle_sigprof_signal_handler_for_current_thread(SIG_UNBLOCK);
108
+ }
109
+
110
+ VALUE is_sigprof_blocked_in_current_thread(void) {
111
+ sigset_t current_signals;
112
+ sigemptyset(&current_signals);
113
+ ENFORCE_SUCCESS_GVL(pthread_sigmask(0, NULL, &current_signals));
114
+ return sigismember(&current_signals, SIGPROF) ? Qtrue : Qfalse;
115
+ }
@@ -0,0 +1,11 @@
1
+ #pragma once
2
+
3
+ #include <signal.h>
4
+
5
+ void empty_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
6
+ void install_sigprof_signal_handler(void (*signal_handler_function)(int, siginfo_t *, void *), const char *handler_pretty_name);
7
+ void replace_sigprof_signal_handler_with_empty_handler(void (*expected_existing_handler)(int, siginfo_t *, void *));
8
+ void remove_sigprof_signal_handler(void);
9
+ void block_sigprof_signal_handler_from_running_in_current_thread(void);
10
+ void unblock_sigprof_signal_handler_from_running_in_current_thread(void);
11
+ VALUE is_sigprof_blocked_in_current_thread(void);
@@ -152,6 +152,7 @@ struct active_slot_pair {
152
152
  struct call_serialize_without_gvl_arguments {
153
153
  // Set by caller
154
154
  struct stack_recorder_state *state;
155
+ ddog_Timespec finish_timestamp;
155
156
 
156
157
  // Set by callee
157
158
  ddog_Profile *profile;
@@ -162,18 +163,22 @@ struct call_serialize_without_gvl_arguments {
162
163
  };
163
164
 
164
165
  static VALUE _native_new(VALUE klass);
166
+ static void initialize_slot_concurrency_control(struct stack_recorder_state *state);
165
167
  static void stack_recorder_typed_data_free(void *data);
166
168
  static VALUE _native_serialize(VALUE self, VALUE recorder_instance);
167
169
  static VALUE ruby_time_from(ddog_Timespec ddprof_time);
168
170
  static void *call_serialize_without_gvl(void *call_args);
169
171
  static struct active_slot_pair sampler_lock_active_profile();
170
172
  static void sampler_unlock_active_profile(struct active_slot_pair active_slot);
171
- static ddog_Profile *serializer_flip_active_and_inactive_slots(struct stack_recorder_state *state, ddog_Timespec start_timestamp_for_next_profile);
173
+ static ddog_Profile *serializer_flip_active_and_inactive_slots(struct stack_recorder_state *state);
172
174
  static VALUE _native_active_slot(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
173
175
  static VALUE _native_is_slot_one_mutex_locked(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
174
176
  static VALUE _native_is_slot_two_mutex_locked(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
175
177
  static VALUE test_slot_mutex_state(VALUE recorder_instance, int slot);
176
- static ddog_Timespec time_now();
178
+ static ddog_Timespec time_now(void);
179
+ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE recorder_instance);
180
+ static void serializer_set_start_timestamp_for_next_profile(struct stack_recorder_state *state, ddog_Timespec timestamp);
181
+ static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE local_root_span_id, VALUE endpoint);
177
182
 
178
183
  void stack_recorder_init(VALUE profiling_module) {
179
184
  stack_recorder_class = rb_define_class_under(profiling_module, "StackRecorder", rb_cObject);
@@ -191,9 +196,11 @@ void stack_recorder_init(VALUE profiling_module) {
191
196
  rb_define_alloc_func(stack_recorder_class, _native_new);
192
197
 
193
198
  rb_define_singleton_method(stack_recorder_class, "_native_serialize", _native_serialize, 1);
199
+ rb_define_singleton_method(stack_recorder_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
194
200
  rb_define_singleton_method(testing_module, "_native_active_slot", _native_active_slot, 1);
195
201
  rb_define_singleton_method(testing_module, "_native_slot_one_mutex_locked?", _native_is_slot_one_mutex_locked, 1);
196
202
  rb_define_singleton_method(testing_module, "_native_slot_two_mutex_locked?", _native_is_slot_two_mutex_locked, 1);
203
+ rb_define_singleton_method(testing_module, "_native_record_endpoint", _native_record_endpoint, 3);
197
204
 
198
205
  ok_symbol = ID2SYM(rb_intern_const("ok"));
199
206
  error_symbol = ID2SYM(rb_intern_const("error"));
@@ -217,14 +224,7 @@ static VALUE _native_new(VALUE klass) {
217
224
 
218
225
  ddog_Slice_value_type sample_types = {.ptr = enabled_value_types, .len = ENABLED_VALUE_TYPES_COUNT};
219
226
 
220
- state->slot_one_mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
221
- state->slot_two_mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
222
-
223
- // A newly-created StackRecorder starts with slot one being active for samples, so let's lock slot two
224
- int error = pthread_mutex_lock(&state->slot_two_mutex);
225
- if (error) rb_syserr_fail(error, "Unexpected failure during pthread_mutex_lock");
226
-
227
- state->active_slot = 1;
227
+ initialize_slot_concurrency_control(state);
228
228
 
229
229
  // Note: Don't raise exceptions after this point, since it'll lead to libdatadog memory leaking!
230
230
 
@@ -234,6 +234,16 @@ static VALUE _native_new(VALUE klass) {
234
234
  return TypedData_Wrap_Struct(klass, &stack_recorder_typed_data, state);
235
235
  }
236
236
 
237
+ static void initialize_slot_concurrency_control(struct stack_recorder_state *state) {
238
+ state->slot_one_mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
239
+ state->slot_two_mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
240
+
241
+ // A newly-created StackRecorder starts with slot one being active for samples, so let's lock slot two
242
+ ENFORCE_SUCCESS_GVL(pthread_mutex_lock(&state->slot_two_mutex));
243
+
244
+ state->active_slot = 1;
245
+ }
246
+
237
247
  static void stack_recorder_typed_data_free(void *state_ptr) {
238
248
  struct stack_recorder_state *state = (struct stack_recorder_state *) state_ptr;
239
249
 
@@ -250,9 +260,13 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
250
260
  struct stack_recorder_state *state;
251
261
  TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
252
262
 
263
+ ddog_Timespec finish_timestamp = time_now();
264
+ // Need to do this while still holding on to the Global VM Lock; see comments on method for why
265
+ serializer_set_start_timestamp_for_next_profile(state, finish_timestamp);
266
+
253
267
  // We'll release the Global VM Lock while we're calling serialize, so that the Ruby VM can continue to work while this
254
268
  // is pending
255
- struct call_serialize_without_gvl_arguments args = {.state = state, .serialize_ran = false};
269
+ struct call_serialize_without_gvl_arguments args = {.state = state, .finish_timestamp = finish_timestamp, .serialize_ran = false};
256
270
 
257
271
  while (!args.serialize_ran) {
258
272
  // Give the Ruby VM an opportunity to process any pending interruptions (including raising exceptions).
@@ -315,13 +329,22 @@ void record_sample(VALUE recorder_instance, ddog_Sample sample) {
315
329
  sampler_unlock_active_profile(active_slot);
316
330
  }
317
331
 
332
+ void record_endpoint(VALUE recorder_instance, ddog_CharSlice local_root_span_id, ddog_CharSlice endpoint) {
333
+ struct stack_recorder_state *state;
334
+ TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
335
+
336
+ struct active_slot_pair active_slot = sampler_lock_active_profile(state);
337
+
338
+ ddog_Profile_set_endpoint(active_slot.profile, local_root_span_id, endpoint);
339
+
340
+ sampler_unlock_active_profile(active_slot);
341
+ }
342
+
318
343
  static void *call_serialize_without_gvl(void *call_args) {
319
344
  struct call_serialize_without_gvl_arguments *args = (struct call_serialize_without_gvl_arguments *) call_args;
320
345
 
321
- ddog_Timespec finish_timestamp = time_now();
322
-
323
- args->profile = serializer_flip_active_and_inactive_slots(args->state, finish_timestamp);
324
- args->result = ddog_Profile_serialize(args->profile, &finish_timestamp, NULL /* duration_nanos is optional */);
346
+ args->profile = serializer_flip_active_and_inactive_slots(args->state);
347
+ args->result = ddog_Profile_serialize(args->profile, &args->finish_timestamp, NULL /* duration_nanos is optional */);
325
348
  args->serialize_ran = true;
326
349
 
327
350
  return NULL; // Unused
@@ -337,7 +360,7 @@ static struct active_slot_pair sampler_lock_active_profile(struct stack_recorder
337
360
 
338
361
  for (int attempts = 0; attempts < 2; attempts++) {
339
362
  error = pthread_mutex_trylock(&state->slot_one_mutex);
340
- if (error && error != EBUSY) rb_syserr_fail(error, "Unexpected failure during sampler_lock_active_profile for slot_one_mutex");
363
+ if (error && error != EBUSY) ENFORCE_SUCCESS_GVL(error);
341
364
 
342
365
  // Slot one is active
343
366
  if (!error) return (struct active_slot_pair) {.mutex = &state->slot_one_mutex, .profile = state->slot_one_profile};
@@ -345,7 +368,7 @@ static struct active_slot_pair sampler_lock_active_profile(struct stack_recorder
345
368
  // If we got here, slot one was not active, let's try slot two
346
369
 
347
370
  error = pthread_mutex_trylock(&state->slot_two_mutex);
348
- if (error && error != EBUSY) rb_syserr_fail(error, "Unexpected failure during sampler_lock_active_profile for slot_two_mutex");
371
+ if (error && error != EBUSY) ENFORCE_SUCCESS_GVL(error);
349
372
 
350
373
  // Slot two is active
351
374
  if (!error) return (struct active_slot_pair) {.mutex = &state->slot_two_mutex, .profile = state->slot_two_profile};
@@ -356,32 +379,24 @@ static struct active_slot_pair sampler_lock_active_profile(struct stack_recorder
356
379
  }
357
380
 
358
381
  static void sampler_unlock_active_profile(struct active_slot_pair active_slot) {
359
- int error = pthread_mutex_unlock(active_slot.mutex);
360
- if (error != 0) rb_syserr_fail(error, "Unexpected failure in sampler_unlock_active_profile");
382
+ ENFORCE_SUCCESS_GVL(pthread_mutex_unlock(active_slot.mutex));
361
383
  }
362
384
 
363
- static ddog_Profile *serializer_flip_active_and_inactive_slots(struct stack_recorder_state *state, ddog_Timespec start_timestamp_for_next_profile) {
364
- int error;
385
+ static ddog_Profile *serializer_flip_active_and_inactive_slots(struct stack_recorder_state *state) {
365
386
  int previously_active_slot = state->active_slot;
366
387
 
367
388
  if (previously_active_slot != 1 && previously_active_slot != 2) {
368
- rb_raise(rb_eRuntimeError, "Unexpected active_slot state %d in serializer_flip_active_and_inactive_slots", previously_active_slot);
389
+ grab_gvl_and_raise(rb_eRuntimeError, "Unexpected active_slot state %d in serializer_flip_active_and_inactive_slots", previously_active_slot);
369
390
  }
370
391
 
371
392
  pthread_mutex_t *previously_active = (previously_active_slot == 1) ? &state->slot_one_mutex : &state->slot_two_mutex;
372
393
  pthread_mutex_t *previously_inactive = (previously_active_slot == 1) ? &state->slot_two_mutex : &state->slot_one_mutex;
373
394
 
374
- // Before making this profile active, we reset it so that it uses the correct timestamp for its start
375
- ddog_Profile *previously_inactive_profile = (previously_active_slot == 1) ? state->slot_two_profile : state->slot_one_profile;
376
- if (!ddog_Profile_reset(previously_inactive_profile, &start_timestamp_for_next_profile)) rb_raise(rb_eRuntimeError, "Failed to reset profile");
377
-
378
395
  // Release the lock, thus making this slot active
379
- error = pthread_mutex_unlock(previously_inactive);
380
- if (error) rb_syserr_fail(error, "Unexpected failure during serializer_flip_active_and_inactive_slots for previously_inactive");
396
+ ENFORCE_SUCCESS_NO_GVL(pthread_mutex_unlock(previously_inactive));
381
397
 
382
398
  // Grab the lock, thus making this slot inactive
383
- error = pthread_mutex_lock(previously_active);
384
- if (error) rb_syserr_fail(error, "Unexpected failure during serializer_flip_active_and_inactive_slots for previously_active");
399
+ ENFORCE_SUCCESS_NO_GVL(pthread_mutex_lock(previously_active));
385
400
 
386
401
  // Update active_slot
387
402
  state->active_slot = (previously_active_slot == 1) ? 2 : 1;
@@ -418,21 +433,55 @@ static VALUE test_slot_mutex_state(VALUE recorder_instance, int slot) {
418
433
 
419
434
  if (error == 0) {
420
435
  // Mutex was unlocked
421
- pthread_mutex_unlock(slot_mutex);
436
+ ENFORCE_SUCCESS_GVL(pthread_mutex_unlock(slot_mutex));
422
437
  return Qfalse;
423
438
  } else if (error == EBUSY) {
424
439
  // Mutex was locked
425
440
  return Qtrue;
426
441
  } else {
427
- rb_syserr_fail(error, "Unexpected failure when checking mutex state");
442
+ ENFORCE_SUCCESS_GVL(error);
443
+ rb_raise(rb_eRuntimeError, "Failed to raise exception in test_slot_mutex_state; this should never happen");
428
444
  }
429
445
  }
430
446
 
431
- // Note that this is using CLOCK_REALTIME (e.g. actual time since unix epoch) and not the CLOCK_MONOTONIC as we use in other parts of the codebase
432
- static ddog_Timespec time_now() {
447
+ // Note that this is using CLOCK_REALTIME (e.g. actual time since unix epoch) and not the CLOCK_MONOTONIC as we use in
448
+ // monotonic_wall_time_now_ns (used in other parts of the codebase)
449
+ static ddog_Timespec time_now(void) {
433
450
  struct timespec current_time;
434
451
 
435
- if (clock_gettime(CLOCK_REALTIME, &current_time) != 0) rb_sys_fail("Failed to read CLOCK_REALTIME");
452
+ if (clock_gettime(CLOCK_REALTIME, &current_time) != 0) ENFORCE_SUCCESS_GVL(errno);
436
453
 
437
454
  return (ddog_Timespec) {.seconds = current_time.tv_sec, .nanoseconds = (uint32_t) current_time.tv_nsec};
438
455
  }
456
+
457
+ // After the Ruby VM forks, this method gets called in the child process to clean up any leftover state from the parent.
458
+ //
459
+ // Assumption: This method gets called BEFORE restarting profiling -- e.g. there are no components attempting to
460
+ // trigger samples at the same time.
461
+ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE recorder_instance) {
462
+ struct stack_recorder_state *state;
463
+ TypedData_Get_Struct(recorder_instance, struct stack_recorder_state, &stack_recorder_typed_data, state);
464
+
465
+ // In case the fork happened halfway through `serializer_flip_active_and_inactive_slots` execution and the
466
+ // resulting state is inconsistent, we make sure to reset it back to the initial state.
467
+ initialize_slot_concurrency_control(state);
468
+
469
+ ddog_Profile_reset(state->slot_one_profile, /* start_time: */ NULL);
470
+ ddog_Profile_reset(state->slot_two_profile, /* start_time: */ NULL);
471
+
472
+ return Qtrue;
473
+ }
474
+
475
+ // Assumption 1: This method is called with the GVL being held, because `ddog_Profile_reset` mutates the profile and should
476
+ // not be interrupted part-way through by a VM fork.
477
+ static void serializer_set_start_timestamp_for_next_profile(struct stack_recorder_state *state, ddog_Timespec timestamp) {
478
+ // Before making this profile active, we reset it so that it uses the correct timestamp for its start
479
+ ddog_Profile *next_profile = (state->active_slot == 1) ? state->slot_two_profile : state->slot_one_profile;
480
+
481
+ if (!ddog_Profile_reset(next_profile, &timestamp)) rb_raise(rb_eRuntimeError, "Failed to reset profile");
482
+ }
483
+
484
+ static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE local_root_span_id, VALUE endpoint) {
485
+ record_endpoint(recorder_instance, char_slice_from_ruby_string(local_root_span_id), char_slice_from_ruby_string(endpoint));
486
+ return Qtrue;
487
+ }
@@ -35,4 +35,5 @@ static const ddog_ValueType enabled_value_types[] = {
35
35
  #define ENABLED_VALUE_TYPES_COUNT (sizeof(enabled_value_types) / sizeof(ddog_ValueType))
36
36
 
37
37
  void record_sample(VALUE recorder_instance, ddog_Sample sample);
38
+ void record_endpoint(VALUE recorder_instance, ddog_CharSlice local_root_span_id, ddog_CharSlice endpoint);
38
39
  VALUE enforce_recorder_instance(VALUE object);
@@ -0,0 +1,17 @@
1
+ #include <errno.h>
2
+ #include <time.h>
3
+
4
+ #include "ruby_helpers.h"
5
+ #include "time_helpers.h"
6
+
7
+ // Safety: This function is assumed never to raise exceptions by callers when raise_on_failure == false
8
+ long monotonic_wall_time_now_ns(bool raise_on_failure) {
9
+ struct timespec current_monotonic;
10
+
11
+ if (clock_gettime(CLOCK_MONOTONIC, &current_monotonic) != 0) {
12
+ if (raise_on_failure) ENFORCE_SUCCESS_GVL(errno);
13
+ return 0;
14
+ }
15
+
16
+ return current_monotonic.tv_nsec + SECONDS_AS_NS(current_monotonic.tv_sec);
17
+ }
@@ -0,0 +1,10 @@
1
+ #pragma once
2
+
3
+ #define SECONDS_AS_NS(value) (value * 1000 * 1000 * 1000L)
4
+ #define MILLIS_AS_NS(value) (value * 1000 * 1000L)
5
+
6
+ #define RAISE_ON_FAILURE true
7
+ #define DO_NOT_RAISE_ON_FAILURE false
8
+
9
+ // Safety: This function is assumed never to raise exceptions by callers when raise_on_failure == false
10
+ long monotonic_wall_time_now_ns(bool raise_on_failure);