datadog 2.3.0 → 2.5.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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -2
  3. data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
  4. data/ext/datadog_profiling_loader/extconf.rb +10 -22
  5. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +3 -3
  6. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +198 -41
  7. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -2
  8. data/ext/datadog_profiling_native_extension/collectors_stack.c +89 -46
  9. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +645 -107
  10. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +15 -1
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +0 -27
  12. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -4
  13. data/ext/datadog_profiling_native_extension/extconf.rb +42 -25
  14. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
  15. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
  16. data/ext/datadog_profiling_native_extension/heap_recorder.c +194 -34
  17. data/ext/datadog_profiling_native_extension/heap_recorder.h +11 -0
  18. data/ext/datadog_profiling_native_extension/http_transport.c +38 -6
  19. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +1 -1
  20. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +53 -2
  21. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -0
  22. data/ext/datadog_profiling_native_extension/profiling.c +1 -1
  23. data/ext/datadog_profiling_native_extension/ruby_helpers.c +14 -11
  24. data/ext/datadog_profiling_native_extension/stack_recorder.c +58 -22
  25. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
  26. data/ext/libdatadog_api/crashtracker.c +20 -18
  27. data/ext/libdatadog_api/datadog_ruby_common.c +0 -27
  28. data/ext/libdatadog_api/datadog_ruby_common.h +0 -4
  29. data/ext/libdatadog_extconf_helpers.rb +1 -1
  30. data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
  31. data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
  32. data/lib/datadog/appsec/component.rb +29 -8
  33. data/lib/datadog/appsec/configuration/settings.rb +10 -2
  34. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
  35. data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
  36. data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
  37. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +0 -14
  38. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +67 -31
  39. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +14 -15
  40. data/lib/datadog/appsec/contrib/graphql/integration.rb +14 -1
  41. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +7 -20
  42. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +2 -5
  43. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -15
  44. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +6 -18
  45. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +7 -20
  46. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +5 -18
  47. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +3 -1
  48. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +3 -5
  49. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +5 -18
  50. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +6 -10
  51. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +7 -20
  52. data/lib/datadog/appsec/event.rb +25 -1
  53. data/lib/datadog/appsec/ext.rb +4 -0
  54. data/lib/datadog/appsec/monitor/gateway/watcher.rb +3 -5
  55. data/lib/datadog/appsec/monitor/reactive/set_user.rb +7 -20
  56. data/lib/datadog/appsec/processor/context.rb +109 -0
  57. data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
  58. data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
  59. data/lib/datadog/appsec/processor.rb +42 -107
  60. data/lib/datadog/appsec/rate_limiter.rb +25 -40
  61. data/lib/datadog/appsec/remote.rb +7 -3
  62. data/lib/datadog/appsec/scope.rb +1 -4
  63. data/lib/datadog/appsec/utils/trace_operation.rb +15 -0
  64. data/lib/datadog/appsec/utils.rb +2 -0
  65. data/lib/datadog/appsec.rb +3 -2
  66. data/lib/datadog/core/configuration/agent_settings_resolver.rb +26 -25
  67. data/lib/datadog/core/configuration/components.rb +4 -3
  68. data/lib/datadog/core/configuration/settings.rb +96 -5
  69. data/lib/datadog/core/configuration.rb +1 -3
  70. data/lib/datadog/core/crashtracking/component.rb +9 -6
  71. data/lib/datadog/core/environment/execution.rb +5 -5
  72. data/lib/datadog/core/environment/yjit.rb +5 -0
  73. data/lib/datadog/core/metrics/client.rb +7 -0
  74. data/lib/datadog/core/rate_limiter.rb +183 -0
  75. data/lib/datadog/core/remote/client/capabilities.rb +4 -3
  76. data/lib/datadog/core/remote/component.rb +4 -2
  77. data/lib/datadog/core/remote/negotiation.rb +4 -4
  78. data/lib/datadog/core/remote/tie.rb +2 -0
  79. data/lib/datadog/core/remote/transport/http.rb +5 -0
  80. data/lib/datadog/core/remote/worker.rb +1 -1
  81. data/lib/datadog/core/runtime/ext.rb +1 -0
  82. data/lib/datadog/core/runtime/metrics.rb +5 -1
  83. data/lib/datadog/core/semaphore.rb +35 -0
  84. data/lib/datadog/core/telemetry/component.rb +2 -0
  85. data/lib/datadog/core/telemetry/event.rb +12 -7
  86. data/lib/datadog/core/telemetry/logger.rb +51 -0
  87. data/lib/datadog/core/telemetry/logging.rb +50 -14
  88. data/lib/datadog/core/telemetry/request.rb +13 -1
  89. data/lib/datadog/core/transport/ext.rb +1 -0
  90. data/lib/datadog/core/utils/time.rb +12 -0
  91. data/lib/datadog/core/workers/async.rb +1 -1
  92. data/lib/datadog/di/code_tracker.rb +166 -0
  93. data/lib/datadog/di/configuration/settings.rb +163 -0
  94. data/lib/datadog/di/configuration.rb +11 -0
  95. data/lib/datadog/di/error.rb +31 -0
  96. data/lib/datadog/di/extensions.rb +16 -0
  97. data/lib/datadog/di/instrumenter.rb +301 -0
  98. data/lib/datadog/di/probe.rb +162 -0
  99. data/lib/datadog/di/probe_builder.rb +47 -0
  100. data/lib/datadog/di/probe_notification_builder.rb +207 -0
  101. data/lib/datadog/di/probe_notifier_worker.rb +244 -0
  102. data/lib/datadog/di/redactor.rb +188 -0
  103. data/lib/datadog/di/serializer.rb +215 -0
  104. data/lib/datadog/di/transport.rb +67 -0
  105. data/lib/datadog/di/utils.rb +39 -0
  106. data/lib/datadog/di.rb +57 -0
  107. data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
  108. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +12 -10
  109. data/lib/datadog/profiling/collectors/info.rb +12 -3
  110. data/lib/datadog/profiling/collectors/thread_context.rb +32 -8
  111. data/lib/datadog/profiling/component.rb +21 -4
  112. data/lib/datadog/profiling/http_transport.rb +6 -1
  113. data/lib/datadog/profiling/scheduler.rb +2 -0
  114. data/lib/datadog/profiling/stack_recorder.rb +40 -9
  115. data/lib/datadog/single_step_instrument.rb +12 -0
  116. data/lib/datadog/tracing/component.rb +13 -0
  117. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
  118. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
  119. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
  120. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
  121. data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
  122. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
  123. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
  124. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +3 -1
  125. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
  126. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
  127. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
  128. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -0
  129. data/lib/datadog/tracing/contrib/excon/middleware.rb +3 -0
  130. data/lib/datadog/tracing/contrib/faraday/middleware.rb +12 -0
  131. data/lib/datadog/tracing/contrib/grape/endpoint.rb +24 -2
  132. data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
  133. data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
  134. data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
  135. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +13 -9
  136. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +6 -3
  137. data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +9 -0
  138. data/lib/datadog/tracing/contrib/http/instrumentation.rb +22 -15
  139. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +10 -5
  140. data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
  141. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +9 -0
  142. data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
  143. data/lib/datadog/tracing/contrib/lograge/patcher.rb +1 -2
  144. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
  145. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
  146. data/lib/datadog/tracing/contrib/patcher.rb +2 -1
  147. data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
  148. data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
  149. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  150. data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
  151. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +3 -0
  152. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
  153. data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
  154. data/lib/datadog/tracing/distributed/propagation.rb +7 -0
  155. data/lib/datadog/tracing/metadata/ext.rb +2 -0
  156. data/lib/datadog/tracing/remote.rb +5 -2
  157. data/lib/datadog/tracing/sampling/matcher.rb +6 -1
  158. data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
  159. data/lib/datadog/tracing/sampling/rule.rb +2 -0
  160. data/lib/datadog/tracing/sampling/rule_sampler.rb +15 -9
  161. data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
  162. data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
  163. data/lib/datadog/tracing/trace_operation.rb +26 -2
  164. data/lib/datadog/tracing/tracer.rb +29 -22
  165. data/lib/datadog/tracing/transport/http/client.rb +1 -0
  166. data/lib/datadog/tracing/transport/http.rb +4 -0
  167. data/lib/datadog/tracing/transport/io/client.rb +1 -0
  168. data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
  169. data/lib/datadog/tracing/workers.rb +2 -2
  170. data/lib/datadog/tracing/writer.rb +26 -28
  171. data/lib/datadog/version.rb +1 -1
  172. metadata +40 -15
  173. data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -3,6 +3,8 @@
3
3
  #include <ruby.h>
4
4
  #include <stdbool.h>
5
5
 
6
+ #include "gvl_profiling_helper.h"
7
+
6
8
  void thread_context_collector_sample(
7
9
  VALUE self_instance,
8
10
  long current_monotonic_wall_time_ns,
@@ -12,5 +14,17 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
12
14
  void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples);
13
15
  VALUE thread_context_collector_sample_after_gc(VALUE self_instance);
14
16
  void thread_context_collector_on_gc_start(VALUE self_instance);
15
- bool thread_context_collector_on_gc_finish(VALUE self_instance);
17
+ __attribute__((warn_unused_result)) bool thread_context_collector_on_gc_finish(VALUE self_instance);
16
18
  VALUE enforce_thread_context_collector_instance(VALUE object);
19
+
20
+ #ifndef NO_GVL_INSTRUMENTATION
21
+ typedef enum {
22
+ ON_GVL_RUNNING_UNKNOWN, // Thread is not known, it may not even be from the current Ractor
23
+ ON_GVL_RUNNING_DONT_SAMPLE, // Thread is known, but "Waiting for GVL" period was too small to be sampled
24
+ ON_GVL_RUNNING_SAMPLE, // Thread is known, and "Waiting for GVL" period should be sampled
25
+ } on_gvl_running_result;
26
+
27
+ void thread_context_collector_on_gvl_waiting(gvl_profiling_thread thread);
28
+ __attribute__((warn_unused_result)) on_gvl_running_result thread_context_collector_on_gvl_running(gvl_profiling_thread thread);
29
+ VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns);
30
+ #endif
@@ -28,33 +28,6 @@ VALUE datadog_gem_version(void) {
28
28
  return version_string;
29
29
  }
30
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
31
  static VALUE log_failure_to_process_tag(VALUE err_details) {
59
32
  VALUE datadog_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
60
33
  VALUE logger = rb_funcall(datadog_module, rb_intern("logger"), 0);
@@ -27,7 +27,6 @@
27
27
  #define ENFORCE_BOOLEAN(value) \
28
28
  { if (RB_UNLIKELY(value != Qtrue && value != Qfalse)) raise_unexpected_type(value, ADD_QUOTES(value), "true or false", __FILE__, __LINE__, __func__); }
29
29
 
30
- // Called by ENFORCE_TYPE; should not be used directly
31
30
  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
31
 
33
32
  // Helper to retrieve Datadog::VERSION::STRING
@@ -39,9 +38,6 @@ static inline ddog_CharSlice char_slice_from_ruby_string(VALUE string) {
39
38
  return char_slice;
40
39
  }
41
40
 
42
- __attribute__((warn_unused_result))
43
- ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration);
44
-
45
41
  __attribute__((warn_unused_result))
46
42
  ddog_Vec_Tag convert_tags(VALUE tags_as_array);
47
43
 
@@ -74,35 +74,25 @@ Logging.message("[datadog] Using compiler:\n")
74
74
  xsystem("#{CONFIG["CC"]} -v")
75
75
  Logging.message("[datadog] End of compiler information\n")
76
76
 
77
- # mkmf on modern Rubies actually has an append_cflags that does something similar
78
- # (see https://github.com/ruby/ruby/pull/5760), but as usual we need a bit more boilerplate to deal with legacy Rubies
79
- def add_compiler_flag(flag)
80
- if try_cflags(flag)
81
- $CFLAGS << " " << flag
82
- else
83
- $stderr.puts("WARNING: '#{flag}' not accepted by compiler, skipping it")
84
- end
85
- end
86
-
87
77
  # Because we can't control what compiler versions our customers use, shipping with -Werror by default is a no-go.
88
78
  # But we can enable it in CI, so that we quickly spot any new warnings that just got introduced.
89
- add_compiler_flag "-Werror" if ENV["DATADOG_GEM_CI"] == "true"
79
+ append_cflags "-Werror" if ENV["DATADOG_GEM_CI"] == "true"
90
80
 
91
81
  # Older gcc releases may not default to C99 and we need to ask for this. This is also used:
92
82
  # * by upstream Ruby -- search for gnu99 in the codebase
93
83
  # * by msgpack, another datadog gem dependency
94
84
  # (https://github.com/msgpack/msgpack-ruby/blob/18ce08f6d612fe973843c366ac9a0b74c4e50599/ext/msgpack/extconf.rb#L8)
95
- add_compiler_flag "-std=gnu99"
85
+ append_cflags "-std=gnu99"
96
86
 
97
87
  # Gets really noisy when we include the MJIT header, let's omit it (TODO: Use #pragma GCC diagnostic instead?)
98
- add_compiler_flag "-Wno-unused-function"
88
+ append_cflags "-Wno-unused-function"
99
89
 
100
90
  # Allow defining variables at any point in a function
101
- add_compiler_flag "-Wno-declaration-after-statement"
91
+ append_cflags "-Wno-declaration-after-statement"
102
92
 
103
93
  # If we forget to include a Ruby header, the function call may still appear to work, but then
104
94
  # cause a segfault later. Let's ensure that never happens.
105
- add_compiler_flag "-Werror-implicit-function-declaration"
95
+ append_cflags "-Werror-implicit-function-declaration"
106
96
 
107
97
  # The native extension is not intended to expose any symbols/functions for other native libraries to use;
108
98
  # the sole exception being `Init_datadog_profiling_native_extension` which needs to be visible for Ruby to call it when
@@ -110,14 +100,14 @@ add_compiler_flag "-Werror-implicit-function-declaration"
110
100
  #
111
101
  # By setting this compiler flag, we tell it to assume that everything is private unless explicitly stated.
112
102
  # For more details see https://gcc.gnu.org/wiki/Visibility
113
- add_compiler_flag "-fvisibility=hidden"
103
+ append_cflags "-fvisibility=hidden"
114
104
 
115
105
  # Avoid legacy C definitions
116
- add_compiler_flag "-Wold-style-definition"
106
+ append_cflags "-Wold-style-definition"
117
107
 
118
108
  # Enable all other compiler warnings
119
- add_compiler_flag "-Wall"
120
- add_compiler_flag "-Wextra"
109
+ append_cflags "-Wall"
110
+ append_cflags "-Wextra"
121
111
 
122
112
  if ENV["DDTRACE_DEBUG"] == "true"
123
113
  $defs << "-DDD_DEBUG"
@@ -153,6 +143,12 @@ $defs << "-DNO_RACTOR_HEADER_INCLUDE" if RUBY_VERSION < "3.3"
153
143
  # On older Rubies, some of the Ractor internal APIs were directly accessible
154
144
  $defs << "-DUSE_RACTOR_INTERNAL_APIS_DIRECTLY" if RUBY_VERSION < "3.3"
155
145
 
146
+ # On older Rubies, there was no GVL instrumentation API and APIs created to support it
147
+ $defs << "-DNO_GVL_INSTRUMENTATION" if RUBY_VERSION < "3.2"
148
+
149
+ # Supporting GVL instrumentation on 3.2 needs some workarounds
150
+ $defs << "-DUSE_GVL_PROFILING_3_2_WORKAROUNDS" if RUBY_VERSION.start_with?("3.2")
151
+
156
152
  # On older Rubies, there was no struct rb_native_thread. See private_vm_api_acccess.c for details.
157
153
  $defs << "-DNO_RB_NATIVE_THREAD" if RUBY_VERSION < "3.2"
158
154
 
@@ -255,21 +251,42 @@ if Datadog::Profiling::NativeExtensionHelpers::CAN_USE_MJIT_HEADER
255
251
 
256
252
  # Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used.
257
253
  # See the comment on the same flag below for why this is done last.
258
- add_compiler_flag "-Wunused-parameter"
254
+ append_cflags "-Wunused-parameter"
259
255
 
260
256
  create_makefile EXTENSION_NAME
261
257
  else
262
258
  # The MJIT header was introduced on 2.6 and removed on 3.3; for other Rubies we rely on
263
- # the debase-ruby_core_source gem to get access to private VM headers.
259
+ # the datadog-ruby_core_source gem to get access to private VM headers.
264
260
  # This gem ships source code copies of these VM headers for the different Ruby VM versions;
265
- # see https://github.com/ruby-debug/debase-ruby_core_source for details
261
+ # see https://github.com/DataDog/datadog-ruby_core_source for details
266
262
 
267
263
  create_header
268
264
 
269
- require "debase/ruby_core_source"
265
+ require "datadog/ruby_core_source"
270
266
  dir_config("ruby") # allow user to pass in non-standard core include directory
271
267
 
272
- Debase::RubyCoreSource
268
+ # This is a workaround for a weird issue...
269
+ #
270
+ # The mkmf tool defines a `with_cppflags` helper that datadog-ruby_core_source uses. This helper temporarily
271
+ # replaces `$CPPFLAGS` (aka the C pre-processor [not c++!] flags) with a different set when doing something.
272
+ #
273
+ # The datadog-ruby_core_source gem uses `with_cppflags` during makefile generation to inject extra headers into the
274
+ # path. But because `with_cppflags` replaces `$CPPFLAGS`, well, the default `$CPPFLAGS` are not included in the
275
+ # makefile.
276
+ #
277
+ # This is a problem because the default `$CPPFLAGS` carries configuration that was set when Ruby was being built.
278
+ # Thus, if we ignore it, we don't compile the profiler with the exact same configuration as Ruby.
279
+ # In practice, this can generate crashes and weird bugs if the Ruby configuration is tweaked in a manner that
280
+ # changes some of the internal structures that the profiler relies on. Concretely, setting for instance
281
+ # `VM_CHECK_MODE=1` when building Ruby will trigger this issue (because somethings in structures the profiler reads
282
+ # are ifdef'd out using this setting).
283
+ #
284
+ # To workaround this issue, we override `with_cppflags` for datadog-ruby_core_source to still include `$CPPFLAGS`.
285
+ Datadog::RubyCoreSource.define_singleton_method(:with_cppflags) do |newflags, &block|
286
+ super("#{newflags} #{$CPPFLAGS}", &block)
287
+ end
288
+
289
+ Datadog::RubyCoreSource
273
290
  .create_makefile_with_core(
274
291
  proc do
275
292
  headers_available =
@@ -282,7 +299,7 @@ else
282
299
  # This is added as late as possible because in some Rubies we support (e.g. 3.3), adding this flag before
283
300
  # checking if internal VM headers are available causes those checks to fail because of this warning (and not
284
301
  # because the headers are not available.)
285
- add_compiler_flag "-Wunused-parameter"
302
+ append_cflags "-Wunused-parameter"
286
303
  end
287
304
 
288
305
  headers_available
@@ -0,0 +1,50 @@
1
+ #include <ruby.h>
2
+ #include <ruby/thread.h>
3
+ #include "gvl_profiling_helper.h"
4
+
5
+ #if !defined(NO_GVL_INSTRUMENTATION) && !defined(USE_GVL_PROFILING_3_2_WORKAROUNDS) // Ruby 3.3+
6
+ rb_internal_thread_specific_key_t gvl_waiting_tls_key;
7
+
8
+ void gvl_profiling_init(void) {
9
+ gvl_waiting_tls_key = rb_internal_thread_specific_key_create();
10
+ }
11
+
12
+ #endif
13
+
14
+ #ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS // Ruby 3.2
15
+ __thread gvl_profiling_thread gvl_waiting_tls;
16
+ static bool gvl_profiling_state_thread_tracking_workaround_installed = false;
17
+
18
+ static void on_thread_start(
19
+ DDTRACE_UNUSED rb_event_flag_t _unused1,
20
+ DDTRACE_UNUSED const rb_internal_thread_event_data_t *_unused2,
21
+ DDTRACE_UNUSED void *_unused3
22
+ ) {
23
+ gvl_waiting_tls = (gvl_profiling_thread) {.thread = NULL};
24
+ }
25
+
26
+ // Hack: We're using the gvl_waiting_tls native thread-local to store per-thread information. Unfortunately, Ruby puts a big hole
27
+ // in our plan because it reuses native threads -- specifically, in Ruby 3.2, native threads are still 1:1 to Ruby
28
+ // threads (M:N wasn't a thing yet) BUT once a Ruby thread dies, the VM will keep the native thread around for a
29
+ // bit, and if another Ruby thread starts right after, Ruby will reuse the native thread, rather than create a new one.
30
+ //
31
+ // This will mean that the new Ruby thread will still have the same native thread-local data that we set on the
32
+ // old thread. For the purposes of our tracking, where we're keeping a pointer to the current thread object in
33
+ // thread-local storage **this is disastrous** since it means we'll be pointing at the wrong thread (and its
34
+ // memory may have been freed or reused since!)
35
+ //
36
+ // To work around this issue, once GVL profiling is enabled, we install an event hook on thread start
37
+ // events that clears the thread-local data. This guarantees that there will be no stale data -- any existing
38
+ // data will be cleared at thread start.
39
+ //
40
+ // Note that once installed, this event hook becomes permanent -- stopping the profiler does not stop this event
41
+ // hook, unlike all others. This is because we can't afford to miss any thread start events while the
42
+ // profiler is stopped (e.g. during reconfiguration) as that would mean stale data once the profiler starts again.
43
+ void gvl_profiling_state_thread_tracking_workaround(void) {
44
+ if (gvl_profiling_state_thread_tracking_workaround_installed) return;
45
+
46
+ rb_internal_thread_add_event_hook(on_thread_start, RUBY_INTERNAL_THREAD_EVENT_STARTED, NULL);
47
+
48
+ gvl_profiling_state_thread_tracking_workaround_installed = true;
49
+ }
50
+ #endif
@@ -0,0 +1,75 @@
1
+ #pragma once
2
+
3
+ // This helper is used by the Datadog::Profiling::Collectors::ThreadContext to store data used when profiling the GVL.
4
+ // It's tested through that class' interfaces.
5
+ // ---
6
+
7
+ #include "extconf.h"
8
+
9
+ #if !defined(NO_GVL_INSTRUMENTATION) && !defined(USE_GVL_PROFILING_3_2_WORKAROUNDS) // Ruby 3.3+
10
+ #include <ruby.h>
11
+ #include <ruby/thread.h>
12
+ #include "datadog_ruby_common.h"
13
+
14
+ typedef struct { VALUE thread; } gvl_profiling_thread;
15
+ extern rb_internal_thread_specific_key_t gvl_waiting_tls_key;
16
+
17
+ void gvl_profiling_init(void);
18
+
19
+ static inline gvl_profiling_thread thread_from_thread_object(VALUE thread) {
20
+ return (gvl_profiling_thread) {.thread = thread};
21
+ }
22
+
23
+ static inline gvl_profiling_thread thread_from_event(const rb_internal_thread_event_data_t *event_data) {
24
+ return thread_from_thread_object(event_data->thread);
25
+ }
26
+
27
+ static inline intptr_t gvl_profiling_state_get(gvl_profiling_thread thread) {
28
+ return (intptr_t) rb_internal_thread_specific_get(thread.thread, gvl_waiting_tls_key);
29
+ }
30
+
31
+ static inline void gvl_profiling_state_set(gvl_profiling_thread thread, intptr_t value) {
32
+ rb_internal_thread_specific_set(thread.thread, gvl_waiting_tls_key, (void *) value);
33
+ }
34
+ #endif
35
+
36
+ #ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS // Ruby 3.2
37
+ typedef struct { void *thread; } gvl_profiling_thread;
38
+ extern __thread gvl_profiling_thread gvl_waiting_tls;
39
+
40
+ static inline void gvl_profiling_init(void) { }
41
+
42
+ // This header gets included in private_vm_access.c which can't include datadog_ruby_common.h so we replicate this
43
+ // helper here
44
+ #ifdef __GNUC__
45
+ #define DDTRACE_UNUSED __attribute__((unused))
46
+ #else
47
+ #define DDTRACE_UNUSED
48
+ #endif
49
+
50
+ // NOTE: This is a hack that relies on the knowledge that on Ruby 3.2 the
51
+ // RUBY_INTERNAL_THREAD_EVENT_READY and RUBY_INTERNAL_THREAD_EVENT_RESUMED events always get called on the thread they
52
+ // are about. Thus, we can use our thread local storage hack to get this data, even though the event doesn't include it.
53
+ static inline gvl_profiling_thread thread_from_event(DDTRACE_UNUSED const void *event_data) {
54
+ return gvl_waiting_tls;
55
+ }
56
+
57
+ void gvl_profiling_state_thread_tracking_workaround(void);
58
+ gvl_profiling_thread gvl_profiling_state_maybe_initialize(void);
59
+
60
+ // Implementing these on Ruby 3.2 requires access to private VM things, so the following methods are
61
+ // implemented in `private_vm_api_access.c`
62
+ gvl_profiling_thread thread_from_thread_object(VALUE thread);
63
+ intptr_t gvl_profiling_state_get(gvl_profiling_thread thread);
64
+ void gvl_profiling_state_set(gvl_profiling_thread thread, intptr_t value);
65
+ #endif
66
+
67
+ #ifndef NO_GVL_INSTRUMENTATION // For all Rubies supporting GVL profiling (3.2+)
68
+ static inline intptr_t gvl_profiling_state_thread_object_get(VALUE thread) {
69
+ return gvl_profiling_state_get(thread_from_thread_object(thread));
70
+ }
71
+
72
+ static inline void gvl_profiling_state_thread_object_set(VALUE thread, intptr_t value) {
73
+ gvl_profiling_state_set(thread_from_thread_object(thread), value);
74
+ }
75
+ #endif