datadog 2.3.0 → 2.4.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 (129) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -1
  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/collectors_cpu_and_wall_time_worker.c +148 -30
  6. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -2
  7. data/ext/datadog_profiling_native_extension/collectors_stack.c +89 -46
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +580 -29
  9. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +9 -1
  10. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +0 -27
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -4
  12. data/ext/datadog_profiling_native_extension/extconf.rb +38 -21
  13. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
  14. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
  15. data/ext/datadog_profiling_native_extension/heap_recorder.c +20 -6
  16. data/ext/datadog_profiling_native_extension/http_transport.c +38 -6
  17. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +52 -1
  18. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -0
  19. data/ext/datadog_profiling_native_extension/profiling.c +1 -1
  20. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
  21. data/ext/libdatadog_api/crashtracker.c +20 -18
  22. data/ext/libdatadog_api/datadog_ruby_common.c +0 -27
  23. data/ext/libdatadog_api/datadog_ruby_common.h +0 -4
  24. data/ext/libdatadog_extconf_helpers.rb +1 -1
  25. data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
  26. data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
  27. data/lib/datadog/appsec/component.rb +29 -8
  28. data/lib/datadog/appsec/configuration/settings.rb +2 -2
  29. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
  30. data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
  31. data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
  32. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +0 -14
  33. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +67 -31
  34. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +18 -15
  35. data/lib/datadog/appsec/contrib/graphql/integration.rb +14 -1
  36. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +2 -5
  37. data/lib/datadog/appsec/event.rb +1 -1
  38. data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
  39. data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
  40. data/lib/datadog/appsec/processor.rb +36 -37
  41. data/lib/datadog/appsec/rate_limiter.rb +25 -40
  42. data/lib/datadog/appsec/remote.rb +7 -3
  43. data/lib/datadog/appsec.rb +2 -2
  44. data/lib/datadog/core/configuration/components.rb +4 -3
  45. data/lib/datadog/core/configuration/settings.rb +84 -5
  46. data/lib/datadog/core/crashtracking/component.rb +1 -1
  47. data/lib/datadog/core/environment/execution.rb +5 -5
  48. data/lib/datadog/core/metrics/client.rb +7 -0
  49. data/lib/datadog/core/rate_limiter.rb +183 -0
  50. data/lib/datadog/core/remote/client/capabilities.rb +4 -3
  51. data/lib/datadog/core/remote/component.rb +4 -2
  52. data/lib/datadog/core/remote/negotiation.rb +4 -4
  53. data/lib/datadog/core/remote/tie.rb +2 -0
  54. data/lib/datadog/core/runtime/metrics.rb +1 -1
  55. data/lib/datadog/core/telemetry/component.rb +2 -0
  56. data/lib/datadog/core/telemetry/event.rb +12 -7
  57. data/lib/datadog/core/telemetry/logger.rb +51 -0
  58. data/lib/datadog/core/telemetry/logging.rb +50 -14
  59. data/lib/datadog/core/telemetry/request.rb +13 -1
  60. data/lib/datadog/core/utils/time.rb +12 -0
  61. data/lib/datadog/di/code_tracker.rb +168 -0
  62. data/lib/datadog/di/configuration/settings.rb +163 -0
  63. data/lib/datadog/di/configuration.rb +11 -0
  64. data/lib/datadog/di/error.rb +31 -0
  65. data/lib/datadog/di/extensions.rb +16 -0
  66. data/lib/datadog/di/probe.rb +133 -0
  67. data/lib/datadog/di/probe_builder.rb +41 -0
  68. data/lib/datadog/di/redactor.rb +188 -0
  69. data/lib/datadog/di/serializer.rb +193 -0
  70. data/lib/datadog/di.rb +14 -0
  71. data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
  72. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +12 -10
  73. data/lib/datadog/profiling/collectors/info.rb +12 -3
  74. data/lib/datadog/profiling/collectors/thread_context.rb +26 -0
  75. data/lib/datadog/profiling/component.rb +20 -4
  76. data/lib/datadog/profiling/http_transport.rb +6 -1
  77. data/lib/datadog/profiling/scheduler.rb +2 -0
  78. data/lib/datadog/profiling/stack_recorder.rb +3 -0
  79. data/lib/datadog/single_step_instrument.rb +12 -0
  80. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
  81. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
  82. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
  83. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
  84. data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
  85. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
  86. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
  87. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +3 -1
  88. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
  89. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
  90. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
  91. data/lib/datadog/tracing/contrib/faraday/middleware.rb +9 -0
  92. data/lib/datadog/tracing/contrib/grape/endpoint.rb +19 -0
  93. data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
  94. data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
  95. data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
  96. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +13 -9
  97. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +6 -3
  98. data/lib/datadog/tracing/contrib/http/instrumentation.rb +18 -15
  99. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +6 -5
  100. data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
  101. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +5 -0
  102. data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
  103. data/lib/datadog/tracing/contrib/lograge/patcher.rb +1 -2
  104. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
  105. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
  106. data/lib/datadog/tracing/contrib/patcher.rb +2 -1
  107. data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
  108. data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
  109. data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
  110. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
  111. data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
  112. data/lib/datadog/tracing/distributed/propagation.rb +7 -0
  113. data/lib/datadog/tracing/metadata/ext.rb +2 -0
  114. data/lib/datadog/tracing/remote.rb +5 -2
  115. data/lib/datadog/tracing/sampling/matcher.rb +6 -1
  116. data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
  117. data/lib/datadog/tracing/sampling/rule.rb +2 -0
  118. data/lib/datadog/tracing/sampling/rule_sampler.rb +9 -5
  119. data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
  120. data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
  121. data/lib/datadog/tracing/trace_operation.rb +26 -2
  122. data/lib/datadog/tracing/tracer.rb +14 -12
  123. data/lib/datadog/tracing/transport/http/client.rb +1 -0
  124. data/lib/datadog/tracing/transport/io/client.rb +1 -0
  125. data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
  126. data/lib/datadog/tracing/workers.rb +1 -1
  127. data/lib/datadog/version.rb +1 -1
  128. metadata +25 -8
  129. data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d6610c8ef7e86c023f8a3fca884807bd7e9cf6b84fc6cbdd79b98e8a8762c2e
4
- data.tar.gz: 236fafc4b8e2c809552d97c8eb025654c0ef4279685c741ad9504b2af8265b94
3
+ metadata.gz: ba1dc00d9afe70c54ea0918673e832793bb7c7a629618dc3d36e066fd121ea56
4
+ data.tar.gz: 9818bf0ba8dbd989451b7b0492efe848a9cdf97239acac3637aaa9d17937b541
5
5
  SHA512:
6
- metadata.gz: e633db76f69b5d151629cde5e1b7024a6bea43343aa348ea50857d5e74d049d2b6a833be252c6c1c7aee95976805a795e1607a81d42e8c3246126abd9811777d
7
- data.tar.gz: fb2bcf3803689d8e499f2266e7d063f1c5b9b7d08bb28b5d27f81bd16e7e373c01c1d5224027d43229a5ca609f66c5179aa521ef272f347d069ca73e3613bdc3
6
+ metadata.gz: 419ac80497f45159d199d2b9d3b16c70c46903d65d828c6e5ab52029758c9b38e11b243e112d52e3e9d45f05d5e1acf814bcb3f7582496f1e5be120c13f0f685
7
+ data.tar.gz: ce8f36e974b194fbdedd40fdf634e49115c0bd6befb350092dfc4cb94185a173a7d3325b6adc9cf50e060b05a4968ca7858a52082748a4979090a7eb32aa96ba
data/CHANGELOG.md CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.4.0] - 2024-10-11
6
+
7
+ ### Added
8
+
9
+ * Core: Allow changing sampling rate for customer defined tags and resources ([#3956][])
10
+ * Profiling: Add GVL profiling for Ruby 3.2+ as a preview feature ([#3929][])
11
+ * Profiling: Otel: Add preview support for correlating profiling with otel ruby gem ([#3984][])
12
+ * Tracing: AppSec: Add http.route tag to Rails, Grape, and Sinatra integrations ([#3849][])
13
+ * Tracing: Add capabilities to remote config: tracing sample rate, tracing logs injection, tracing http header tags ([#3888][])
14
+ * AppSec: Add a force disable of AppSec feature when using Ruby >= 3.3 with old FFI gem version ([#3969][])
15
+
16
+ ### Changed
17
+
18
+ * AppSec: Improve PII compliance ([#3857][])
19
+ * AppSec: Integrations: Improve accuracy of login tracking for Devise ([#3867][])
20
+ * Crashtracking feature is now disabled by default ([#3970][])
21
+
22
+ ### Fixed
23
+
24
+ * AppSec: Integrations: Fix GraphQL instrumentation for query fragments ([#3887][])
25
+ * Bug: Profiling: Fix (small) memory leak in profiler when forking ([#3852][])
26
+ * Tracing: Integrations: Fix GraphQL integration reconfiguration ([#3859][])
27
+
5
28
  ## [2.3.0] - 2024-08-22
6
29
 
7
30
  ### Added
@@ -2962,7 +2985,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
2962
2985
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
2963
2986
 
2964
2987
 
2965
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.3.0...master
2988
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.4.0...master
2989
+ [2.4.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.3.0...v2.4.0
2966
2990
  [2.3.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.2.0...v2.3.0
2967
2991
  [2.2.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.1.0...v2.2.0
2968
2992
  [2.1.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.0.0...v2.1.0
@@ -4383,6 +4407,18 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4383
4407
  [#3837]: https://github.com/DataDog/dd-trace-rb/issues/3837
4384
4408
  [#3839]: https://github.com/DataDog/dd-trace-rb/issues/3839
4385
4409
  [#3841]: https://github.com/DataDog/dd-trace-rb/issues/3841
4410
+ [#3849]: https://github.com/DataDog/dd-trace-rb/issues/3849
4411
+ [#3852]: https://github.com/DataDog/dd-trace-rb/issues/3852
4412
+ [#3857]: https://github.com/DataDog/dd-trace-rb/issues/3857
4413
+ [#3859]: https://github.com/DataDog/dd-trace-rb/issues/3859
4414
+ [#3867]: https://github.com/DataDog/dd-trace-rb/issues/3867
4415
+ [#3887]: https://github.com/DataDog/dd-trace-rb/issues/3887
4416
+ [#3888]: https://github.com/DataDog/dd-trace-rb/issues/3888
4417
+ [#3929]: https://github.com/DataDog/dd-trace-rb/issues/3929
4418
+ [#3956]: https://github.com/DataDog/dd-trace-rb/issues/3956
4419
+ [#3969]: https://github.com/DataDog/dd-trace-rb/issues/3969
4420
+ [#3970]: https://github.com/DataDog/dd-trace-rb/issues/3970
4421
+ [#3984]: https://github.com/DataDog/dd-trace-rb/issues/3984
4386
4422
  [@AdrianLC]: https://github.com/AdrianLC
4387
4423
  [@Azure7111]: https://github.com/Azure7111
4388
4424
  [@BabyGroot]: https://github.com/BabyGroot
@@ -65,7 +65,15 @@ static VALUE _native_load(DDTRACE_UNUSED VALUE self, VALUE ruby_path, VALUE ruby
65
65
  char *path = StringValueCStr(ruby_path);
66
66
  char *init_name = StringValueCStr(ruby_init_name);
67
67
 
68
- void *handle = dlopen(path, RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND);
68
+ int dlopen_flags = RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND;
69
+
70
+ #if defined(__has_feature)
71
+ #if __has_feature(address_sanitizer)
72
+ dlopen_flags &= ~RTLD_DEEPBIND; // Not supported by ASAN
73
+ #endif
74
+ #endif
75
+
76
+ void *handle = dlopen(path, dlopen_flags);
69
77
 
70
78
  VALUE failure_details = Qnil;
71
79
 
@@ -1,5 +1,4 @@
1
1
  # rubocop:disable Style/StderrPuts
2
- # rubocop:disable Style/GlobalVars
3
2
 
4
3
  if RUBY_ENGINE != "ruby" || Gem.win_platform?
5
4
  $stderr.puts(
@@ -12,38 +11,28 @@ end
12
11
 
13
12
  require "mkmf"
14
13
 
15
- # mkmf on modern Rubies actually has an append_cflags that does something similar
16
- # (see https://github.com/ruby/ruby/pull/5760), but as usual we need a bit more boilerplate to deal with legacy Rubies
17
- def add_compiler_flag(flag)
18
- if try_cflags(flag)
19
- $CFLAGS << " " << flag
20
- else
21
- $stderr.puts("WARNING: '#{flag}' not accepted by compiler, skipping it")
22
- end
23
- end
24
-
25
14
  # Because we can't control what compiler versions our customers use, shipping with -Werror by default is a no-go.
26
15
  # But we can enable it in CI, so that we quickly spot any new warnings that just got introduced.
27
- add_compiler_flag "-Werror" if ENV["DATADOG_GEM_CI"] == "true"
16
+ append_cflags "-Werror" if ENV["DATADOG_GEM_CI"] == "true"
28
17
 
29
18
  # Older gcc releases may not default to C99 and we need to ask for this. This is also used:
30
19
  # * by upstream Ruby -- search for gnu99 in the codebase
31
20
  # * by msgpack, another datadog gem dependency
32
21
  # (https://github.com/msgpack/msgpack-ruby/blob/18ce08f6d612fe973843c366ac9a0b74c4e50599/ext/msgpack/extconf.rb#L8)
33
- add_compiler_flag "-std=gnu99"
22
+ append_cflags "-std=gnu99"
34
23
 
35
24
  # Gets really noisy when we include the MJIT header, let's omit it (TODO: Use #pragma GCC diagnostic instead?)
36
- add_compiler_flag "-Wno-unused-function"
25
+ append_cflags "-Wno-unused-function"
37
26
 
38
27
  # Allow defining variables at any point in a function
39
- add_compiler_flag "-Wno-declaration-after-statement"
28
+ append_cflags "-Wno-declaration-after-statement"
40
29
 
41
30
  # If we forget to include a Ruby header, the function call may still appear to work, but then
42
31
  # cause a segfault later. Let's ensure that never happens.
43
- add_compiler_flag "-Werror-implicit-function-declaration"
32
+ append_cflags "-Werror-implicit-function-declaration"
44
33
 
45
34
  # Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used.
46
- add_compiler_flag "-Wunused-parameter"
35
+ append_cflags "-Wunused-parameter"
47
36
 
48
37
  # The native extension is not intended to expose any symbols/functions for other native libraries to use;
49
38
  # the sole exception being `Init_datadog_profiling_loader` which needs to be visible for Ruby to call it when
@@ -51,14 +40,14 @@ add_compiler_flag "-Wunused-parameter"
51
40
  #
52
41
  # By setting this compiler flag, we tell it to assume that everything is private unless explicitly stated.
53
42
  # For more details see https://gcc.gnu.org/wiki/Visibility
54
- add_compiler_flag "-fvisibility=hidden"
43
+ append_cflags "-fvisibility=hidden"
55
44
 
56
45
  # Avoid legacy C definitions
57
- add_compiler_flag "-Wold-style-definition"
46
+ append_cflags "-Wold-style-definition"
58
47
 
59
48
  # Enable all other compiler warnings
60
- add_compiler_flag "-Wall"
61
- add_compiler_flag "-Wextra"
49
+ append_cflags "-Wall"
50
+ append_cflags "-Wextra"
62
51
 
63
52
  # Tag the native extension library with the Ruby version and Ruby platform.
64
53
  # This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
@@ -68,5 +57,4 @@ EXTENSION_NAME = "datadog_profiling_loader.#{RUBY_VERSION}_#{RUBY_PLATFORM}".fre
68
57
 
69
58
  create_makefile(EXTENSION_NAME)
70
59
 
71
- # rubocop:enable Style/GlobalVars
72
60
  # rubocop:enable Style/StderrPuts
@@ -88,6 +88,7 @@ unsigned int MAX_ALLOC_WEIGHT = 10000;
88
88
  // `collectors_cpu_and_wall_time_worker_init` below and always get reused after that.
89
89
  static rb_postponed_job_handle_t sample_from_postponed_job_handle;
90
90
  static rb_postponed_job_handle_t after_gc_from_postponed_job_handle;
91
+ static rb_postponed_job_handle_t after_gvl_running_from_postponed_job_handle;
91
92
  #endif
92
93
 
93
94
  // Contains state for a single CpuAndWallTimeWorker instance
@@ -99,6 +100,7 @@ struct cpu_and_wall_time_worker_state {
99
100
  bool dynamic_sampling_rate_enabled;
100
101
  bool allocation_profiling_enabled;
101
102
  bool allocation_counting_enabled;
103
+ bool gvl_profiling_enabled;
102
104
  bool skip_idle_samples_for_testing;
103
105
  VALUE self_instance;
104
106
  VALUE thread_context_collector_instance;
@@ -123,6 +125,11 @@ struct cpu_and_wall_time_worker_state {
123
125
  // that happens during another sample.
124
126
  bool during_sample;
125
127
 
128
+ #ifndef NO_GVL_INSTRUMENTATION
129
+ // Only set when sampling is active (gets created at start and cleaned on stop)
130
+ rb_internal_thread_event_hook_t *gvl_profiling_hook;
131
+ #endif
132
+
126
133
  struct stats {
127
134
  // # Generic stats
128
135
  // How many times we tried to trigger a sample
@@ -169,23 +176,15 @@ struct cpu_and_wall_time_worker_state {
169
176
  uint64_t allocation_sampling_time_ns_total;
170
177
  // How many times we saw allocations being done inside a sample
171
178
  unsigned int allocations_during_sample;
179
+
180
+ // # GVL profiling stats
181
+ // How many times we triggered the after_gvl_running sampling
182
+ unsigned int after_gvl_running;
172
183
  } stats;
173
184
  };
174
185
 
175
186
  static VALUE _native_new(VALUE klass);
176
- static VALUE _native_initialize(
177
- DDTRACE_UNUSED VALUE _self,
178
- VALUE self_instance,
179
- VALUE thread_context_collector_instance,
180
- VALUE gc_profiling_enabled,
181
- VALUE idle_sampling_helper_instance,
182
- VALUE no_signals_workaround_enabled,
183
- VALUE dynamic_sampling_rate_enabled,
184
- VALUE dynamic_sampling_rate_overhead_target_percentage,
185
- VALUE allocation_profiling_enabled,
186
- VALUE allocation_counting_enabled,
187
- VALUE skip_idle_samples_for_testing
188
- );
187
+ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
189
188
  static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr);
190
189
  static VALUE _native_sampling_loop(VALUE self, VALUE instance);
191
190
  static VALUE _native_stop(DDTRACE_UNUSED VALUE _self, VALUE self_instance, VALUE worker_thread);
@@ -227,6 +226,11 @@ static void delayed_error(struct cpu_and_wall_time_worker_state *state, const ch
227
226
  static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg);
228
227
  static VALUE _native_hold_signals(DDTRACE_UNUSED VALUE self);
229
228
  static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self);
229
+ #ifndef NO_GVL_INSTRUMENTATION
230
+ static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused);
231
+ static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused);
232
+ #endif
233
+ static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE instance);
230
234
 
231
235
  // We're using `on_newobj_event` function with `rb_add_event_hook2`, which requires in its public signature a function
232
236
  // with signature `rb_event_hook_func_t` which doesn't match `on_newobj_event`.
@@ -272,8 +276,13 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
272
276
  int unused_flags = 0;
273
277
  sample_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, sample_from_postponed_job, NULL);
274
278
  after_gc_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_gc_from_postponed_job, NULL);
279
+ after_gvl_running_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_gvl_running_from_postponed_job, NULL);
275
280
 
276
- if (sample_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID || after_gc_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID) {
281
+ if (
282
+ sample_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
283
+ after_gc_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
284
+ after_gvl_running_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID
285
+ ) {
277
286
  rb_raise(rb_eRuntimeError, "Failed to register profiler postponed jobs (got POSTPONED_JOB_HANDLE_INVALID)");
278
287
  }
279
288
  #else
@@ -295,7 +304,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
295
304
  // https://bugs.ruby-lang.org/issues/18007 for a discussion around this.
296
305
  rb_define_alloc_func(collectors_cpu_and_wall_time_worker_class, _native_new);
297
306
 
298
- rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_initialize", _native_initialize, 10);
307
+ rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_initialize", _native_initialize, -1);
299
308
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_sampling_loop", _native_sampling_loop, 1);
300
309
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_stop", _native_stop, 2);
301
310
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
@@ -317,6 +326,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
317
326
  rb_define_singleton_method(testing_module, "_native_is_sigprof_blocked_in_current_thread", _native_is_sigprof_blocked_in_current_thread, 0);
318
327
  rb_define_singleton_method(testing_module, "_native_with_blocked_sigprof", _native_with_blocked_sigprof, 0);
319
328
  rb_define_singleton_method(testing_module, "_native_delayed_error", _native_delayed_error, 2);
329
+ rb_define_singleton_method(testing_module, "_native_gvl_profiling_hook_active", _native_gvl_profiling_hook_active, 1);
320
330
  }
321
331
 
322
332
  // This structure is used to define a Ruby object that stores a pointer to a struct cpu_and_wall_time_worker_state
@@ -345,6 +355,7 @@ static VALUE _native_new(VALUE klass) {
345
355
  state->dynamic_sampling_rate_enabled = true;
346
356
  state->allocation_profiling_enabled = false;
347
357
  state->allocation_counting_enabled = false;
358
+ state->gvl_profiling_enabled = false;
348
359
  state->skip_idle_samples_for_testing = false;
349
360
  state->thread_context_collector_instance = Qnil;
350
361
  state->idle_sampling_helper_instance = Qnil;
@@ -358,6 +369,10 @@ static VALUE _native_new(VALUE klass) {
358
369
 
359
370
  state->during_sample = false;
360
371
 
372
+ #ifndef NO_GVL_INSTRUMENTATION
373
+ state->gvl_profiling_hook = NULL;
374
+ #endif
375
+
361
376
  reset_stats_not_thread_safe(state);
362
377
  discrete_dynamic_sampler_init(&state->allocation_sampler, "allocation", now);
363
378
 
@@ -368,25 +383,30 @@ static VALUE _native_new(VALUE klass) {
368
383
  return state->self_instance = TypedData_Wrap_Struct(klass, &cpu_and_wall_time_worker_typed_data, state);
369
384
  }
370
385
 
371
- static VALUE _native_initialize(
372
- DDTRACE_UNUSED VALUE _self,
373
- VALUE self_instance,
374
- VALUE thread_context_collector_instance,
375
- VALUE gc_profiling_enabled,
376
- VALUE idle_sampling_helper_instance,
377
- VALUE no_signals_workaround_enabled,
378
- VALUE dynamic_sampling_rate_enabled,
379
- VALUE dynamic_sampling_rate_overhead_target_percentage,
380
- VALUE allocation_profiling_enabled,
381
- VALUE allocation_counting_enabled,
382
- VALUE skip_idle_samples_for_testing
383
- ) {
386
+ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
387
+ VALUE options;
388
+ rb_scan_args(argc, argv, "0:", &options);
389
+ if (options == Qnil) options = rb_hash_new();
390
+
391
+ VALUE self_instance = rb_hash_fetch(options, ID2SYM(rb_intern("self_instance")));
392
+ VALUE thread_context_collector_instance = rb_hash_fetch(options, ID2SYM(rb_intern("thread_context_collector")));
393
+ VALUE gc_profiling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("gc_profiling_enabled")));
394
+ VALUE idle_sampling_helper_instance = rb_hash_fetch(options, ID2SYM(rb_intern("idle_sampling_helper")));
395
+ VALUE no_signals_workaround_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("no_signals_workaround_enabled")));
396
+ VALUE dynamic_sampling_rate_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("dynamic_sampling_rate_enabled")));
397
+ VALUE dynamic_sampling_rate_overhead_target_percentage = rb_hash_fetch(options, ID2SYM(rb_intern("dynamic_sampling_rate_overhead_target_percentage")));
398
+ VALUE allocation_profiling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("allocation_profiling_enabled")));
399
+ VALUE allocation_counting_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("allocation_counting_enabled")));
400
+ VALUE gvl_profiling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("gvl_profiling_enabled")));
401
+ VALUE skip_idle_samples_for_testing = rb_hash_fetch(options, ID2SYM(rb_intern("skip_idle_samples_for_testing")));
402
+
384
403
  ENFORCE_BOOLEAN(gc_profiling_enabled);
385
404
  ENFORCE_BOOLEAN(no_signals_workaround_enabled);
386
405
  ENFORCE_BOOLEAN(dynamic_sampling_rate_enabled);
387
406
  ENFORCE_TYPE(dynamic_sampling_rate_overhead_target_percentage, T_FLOAT);
388
407
  ENFORCE_BOOLEAN(allocation_profiling_enabled);
389
408
  ENFORCE_BOOLEAN(allocation_counting_enabled);
409
+ ENFORCE_BOOLEAN(gvl_profiling_enabled);
390
410
  ENFORCE_BOOLEAN(skip_idle_samples_for_testing)
391
411
 
392
412
  struct cpu_and_wall_time_worker_state *state;
@@ -397,6 +417,7 @@ static VALUE _native_initialize(
397
417
  state->dynamic_sampling_rate_enabled = (dynamic_sampling_rate_enabled == Qtrue);
398
418
  state->allocation_profiling_enabled = (allocation_profiling_enabled == Qtrue);
399
419
  state->allocation_counting_enabled = (allocation_counting_enabled == Qtrue);
420
+ state->gvl_profiling_enabled = (gvl_profiling_enabled == Qtrue);
400
421
  state->skip_idle_samples_for_testing = (skip_idle_samples_for_testing == Qtrue);
401
422
 
402
423
  double total_overhead_target_percentage = NUM2DBL(dynamic_sampling_rate_overhead_target_percentage);
@@ -781,6 +802,27 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
781
802
  ;
782
803
  }
783
804
 
805
+ if (state->gvl_profiling_enabled) {
806
+ #ifndef NO_GVL_INSTRUMENTATION
807
+ #ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS
808
+ gvl_profiling_state_thread_tracking_workaround();
809
+ #endif
810
+
811
+ state->gvl_profiling_hook = rb_internal_thread_add_event_hook(
812
+ on_gvl_event,
813
+ (
814
+ // For now we're only asking for these events, even though there's more
815
+ // (e.g. check docs or gvl-tracing gem)
816
+ RUBY_INTERNAL_THREAD_EVENT_READY /* waiting for gvl */ |
817
+ RUBY_INTERNAL_THREAD_EVENT_RESUMED /* running/runnable */
818
+ ),
819
+ NULL
820
+ );
821
+ #else
822
+ rb_raise(rb_eArgError, "GVL profiling is not supported in this Ruby version");
823
+ #endif
824
+ }
825
+
784
826
  // Flag the profiler as running before we release the GVL, in case anyone's waiting to know about it
785
827
  rb_funcall(instance, rb_intern("signal_running"), 0);
786
828
 
@@ -892,7 +934,6 @@ static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused) {
892
934
 
893
935
  state->during_sample = true;
894
936
 
895
- // Trigger sampling using the Collectors::ThreadState; rescue against any exceptions that happen during sampling
896
937
  safely_call(thread_context_collector_sample_after_gc, state->thread_context_collector_instance, state->self_instance);
897
938
 
898
939
  state->during_sample = false;
@@ -999,6 +1040,9 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
999
1040
  ID2SYM(rb_intern("allocation_sampling_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats.allocation_sampling_time_ns_total, state->stats.allocation_sampled),
1000
1041
  ID2SYM(rb_intern("allocation_sampler_snapshot")), /* => */ allocation_sampler_snapshot,
1001
1042
  ID2SYM(rb_intern("allocations_during_sample")), /* => */ state->allocation_profiling_enabled ? UINT2NUM(state->stats.allocations_during_sample) : Qnil,
1043
+
1044
+ // GVL profiling stats
1045
+ ID2SYM(rb_intern("after_gvl_running")), /* => */ UINT2NUM(state->stats.after_gvl_running),
1002
1046
  };
1003
1047
  for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
1004
1048
  return stats_as_hash;
@@ -1173,7 +1217,15 @@ static void disable_tracepoints(struct cpu_and_wall_time_worker_state *state) {
1173
1217
  if (state->gc_tracepoint != Qnil) {
1174
1218
  rb_tracepoint_disable(state->gc_tracepoint);
1175
1219
  }
1220
+
1176
1221
  rb_remove_event_hook_with_data(on_newobj_event_as_hook, state->self_instance);
1222
+
1223
+ #ifndef NO_GVL_INSTRUMENTATION
1224
+ if (state->gvl_profiling_hook) {
1225
+ rb_internal_thread_remove_event_hook(state->gvl_profiling_hook);
1226
+ state->gvl_profiling_hook = NULL;
1227
+ }
1228
+ #endif
1177
1229
  }
1178
1230
 
1179
1231
  static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self) {
@@ -1211,7 +1263,8 @@ static VALUE rescued_sample_allocation(DDTRACE_UNUSED VALUE unused) {
1211
1263
  thread_context_collector_sample_allocation(state->thread_context_collector_instance, weight, new_object);
1212
1264
  // ...but we still represent the skipped samples in the profile, thus the data will account for all allocations.
1213
1265
  if (weight < allocations_since_last_sample) {
1214
- thread_context_collector_sample_skipped_allocation_samples(state->thread_context_collector_instance, allocations_since_last_sample - weight);
1266
+ uint32_t skipped_samples = (uint32_t) uint64_min_of(allocations_since_last_sample - weight, UINT32_MAX);
1267
+ thread_context_collector_sample_skipped_allocation_samples(state->thread_context_collector_instance, skipped_samples);
1215
1268
  }
1216
1269
 
1217
1270
  // Return a dummy VALUE because we're called from rb_rescue2 which requires it
@@ -1247,3 +1300,68 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
1247
1300
  unblock_sigprof_signal_handler_from_running_in_current_thread();
1248
1301
  return Qtrue;
1249
1302
  }
1303
+
1304
+ #ifndef NO_GVL_INSTRUMENTATION
1305
+ static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused) {
1306
+ // Be very careful about touching the `state` here or doing anything at all:
1307
+ // This function gets called without the GVL, and potentially from background Ractors!
1308
+ //
1309
+ // In fact, the `target_thread` that this event is about may not even be the current thread. (So be careful with thread locals that
1310
+ // are not directly tied to the `target_thread` object and the like)
1311
+ gvl_profiling_thread target_thread = thread_from_event(event_data);
1312
+
1313
+ if (event_id == RUBY_INTERNAL_THREAD_EVENT_READY) { /* waiting for gvl */
1314
+ thread_context_collector_on_gvl_waiting(target_thread);
1315
+ } else if (event_id == RUBY_INTERNAL_THREAD_EVENT_RESUMED) { /* running/runnable */
1316
+ // Interesting note: A RUBY_INTERNAL_THREAD_EVENT_RESUMED is guaranteed to be called with the GVL being acquired.
1317
+ // (And... I think target_thread will be == rb_thread_current()?)
1318
+ // But we're not sure if we're on the main Ractor yet. The thread context collector actually can actually help here:
1319
+ // it tags threads it's tracking, so if a thread is tagged then by definition we know that thread belongs to the main
1320
+ // Ractor. Thus, if we really really wanted to access the state, we could do it after making sure we're on the correct Ractor.
1321
+
1322
+ #ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS
1323
+ target_thread = gvl_profiling_state_maybe_initialize();
1324
+ #endif
1325
+
1326
+ bool should_sample = thread_context_collector_on_gvl_running(target_thread);
1327
+
1328
+ if (should_sample) {
1329
+ // should_sample is only true if a thread belongs to the main Ractor, so we're good to go
1330
+ #ifndef NO_POSTPONED_TRIGGER
1331
+ rb_postponed_job_trigger(after_gvl_running_from_postponed_job_handle);
1332
+ #else
1333
+ rb_postponed_job_register_one(0, after_gvl_running_from_postponed_job, NULL);
1334
+ #endif
1335
+ }
1336
+ } else {
1337
+ // This is a very delicate time and it's hard for us to raise an exception so let's at least complain to stderr
1338
+ fprintf(stderr, "[ddtrace] Unexpected value in on_gvl_event (%d)\n", event_id);
1339
+ }
1340
+ }
1341
+
1342
+ static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused) {
1343
+ struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1344
+
1345
+ // This can potentially happen if the CpuAndWallTimeWorker was stopped while the postponed job was waiting to be executed; nothing to do
1346
+ if (state == NULL) return;
1347
+
1348
+ state->during_sample = true;
1349
+
1350
+ safely_call(thread_context_collector_sample_after_gvl_running, state->thread_context_collector_instance, state->self_instance);
1351
+
1352
+ state->stats.after_gvl_running++;
1353
+
1354
+ state->during_sample = false;
1355
+ }
1356
+
1357
+ static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE instance) {
1358
+ struct cpu_and_wall_time_worker_state *state;
1359
+ TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1360
+
1361
+ return state->gvl_profiling_hook != NULL ? Qtrue : Qfalse;
1362
+ }
1363
+ #else
1364
+ static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, DDTRACE_UNUSED VALUE instance) {
1365
+ return Qfalse;
1366
+ }
1367
+ #endif
@@ -92,7 +92,7 @@ double discrete_dynamic_sampler_probability(discrete_dynamic_sampler *sampler) {
92
92
  return sampler->sampling_probability * 100.;
93
93
  }
94
94
 
95
- size_t discrete_dynamic_sampler_events_since_last_sample(discrete_dynamic_sampler *sampler) {
95
+ unsigned long discrete_dynamic_sampler_events_since_last_sample(discrete_dynamic_sampler *sampler) {
96
96
  return sampler->events_since_last_sample;
97
97
  }
98
98
 
@@ -259,7 +259,9 @@ void discrete_dynamic_sampler_readjust(discrete_dynamic_sampler *sampler, long n
259
259
  // are so big they don't fit into the sampling_interval. In both cases lets just disable sampling until next readjustment
260
260
  // by setting interval to 0.
261
261
  double sampling_interval = sampler->sampling_probability == 0 ? 0 : ceil(1.0 / sampler->sampling_probability);
262
- sampler->sampling_interval = sampling_interval > ULONG_MAX ? 0 : sampling_interval;
262
+ // NOTE: We use UINT32_MAX instead of ULONG_MAX here to avoid clang warnings; in practice, we shouldn't ever hit
263
+ // such high sampling intervals.
264
+ sampler->sampling_interval = sampling_interval > UINT32_MAX ? 0 : sampling_interval;
263
265
 
264
266
  #ifdef DD_DEBUG
265
267
  double allocs_in_60s = sampler->events_per_ns * 1e9 * 60;