datadog 2.45.0 → 2.47.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -1
  3. data/ext/datadog_profiling_native_extension/collectors_stack.c +31 -14
  4. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +5 -1
  5. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +5 -0
  6. data/ext/datadog_profiling_native_extension/profiling.c +6 -0
  7. data/ext/libdatadog_api/di.c +30 -5
  8. data/ext/libdatadog_api/extconf.rb +2 -0
  9. data/ext/libdatadog_api/otel_thread_context.c +20 -14
  10. data/ext/libdatadog_api/process_discovery.c +15 -1
  11. data/ext/libdatadog_api/trace_exporter.c +105 -25
  12. data/ext/libdatadog_extconf_helpers.rb +1 -1
  13. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +29 -2
  14. data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +8 -7
  15. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +14 -2
  16. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +8 -7
  17. data/lib/datadog/appsec/security_engine/engine.rb +2 -1
  18. data/lib/datadog/core/configuration/components.rb +40 -18
  19. data/lib/datadog/core/configuration/components_state.rb +8 -1
  20. data/lib/datadog/core/configuration/supported_configurations.rb +7 -2
  21. data/lib/datadog/core/process_discovery.rb +5 -1
  22. data/lib/datadog/core/remote/client/capabilities.rb +34 -29
  23. data/lib/datadog/core/remote/client.rb +44 -3
  24. data/lib/datadog/core/remote/component.rb +28 -12
  25. data/lib/datadog/core/remote/dispatcher.rb +47 -6
  26. data/lib/datadog/core/workers/runtime_metrics.rb +1 -1
  27. data/lib/datadog/di/boot.rb +1 -0
  28. data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
  29. data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
  30. data/lib/datadog/di/component.rb +108 -3
  31. data/lib/datadog/di/configuration.rb +53 -2
  32. data/lib/datadog/di/context.rb +7 -2
  33. data/lib/datadog/di/contrib/active_record.rb +8 -2
  34. data/lib/datadog/di/evaluation_failure.rb +28 -0
  35. data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
  36. data/lib/datadog/di/guardrails_telemetry.rb +203 -0
  37. data/lib/datadog/di/instrumenter.rb +65 -15
  38. data/lib/datadog/di/logger.rb +30 -8
  39. data/lib/datadog/di/probe_manager.rb +48 -5
  40. data/lib/datadog/di/probe_notification_builder.rb +28 -26
  41. data/lib/datadog/di/probe_notifier_worker.rb +56 -6
  42. data/lib/datadog/di/remote.rb +46 -2
  43. data/lib/datadog/di/serializer.rb +147 -55
  44. data/lib/datadog/di/snapshot_event.rb +38 -0
  45. data/lib/datadog/di/transport/http.rb +13 -3
  46. data/lib/datadog/di/transport/input.rb +32 -7
  47. data/lib/datadog/open_feature/activation.rb +268 -0
  48. data/lib/datadog/open_feature/component.rb +31 -15
  49. data/lib/datadog/open_feature/configuration.rb +6 -0
  50. data/lib/datadog/open_feature/hooks/evaluation_telemetry_opt_out_hook.rb +25 -0
  51. data/lib/datadog/open_feature/provider.rb +304 -8
  52. data/lib/datadog/open_feature.rb +31 -1
  53. data/lib/datadog/profiling/collectors/info.rb +37 -6
  54. data/lib/datadog/profiling/component.rb +22 -20
  55. data/lib/datadog/profiling/exporter.rb +3 -5
  56. data/lib/datadog/tracing/component.rb +21 -2
  57. data/lib/datadog/tracing/configuration/ext.rb +2 -0
  58. data/lib/datadog/tracing/configuration/settings.rb +14 -1
  59. data/lib/datadog/tracing/context_provider.rb +8 -0
  60. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +10 -5
  61. data/lib/datadog/tracing/contrib/excon/middleware.rb +10 -3
  62. data/lib/datadog/tracing/contrib/faraday/middleware.rb +10 -4
  63. data/lib/datadog/tracing/contrib/http/instrumentation.rb +16 -1
  64. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +8 -2
  65. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +10 -4
  66. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +10 -3
  67. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +102 -0
  68. data/lib/datadog/tracing/otel_thread_context.rb +3 -5
  69. data/lib/datadog/tracing/remote.rb +50 -19
  70. data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
  71. data/lib/datadog/tracing/transport/http/traces.rb +2 -0
  72. data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
  73. data/lib/datadog/tracing/transport/native.rb +16 -1
  74. data/lib/datadog/tracing/transport/telemetry.rb +62 -0
  75. data/lib/datadog/tracing/transport/traces.rb +82 -15
  76. data/lib/datadog/version.rb +1 -1
  77. metadata +19 -10
  78. data/lib/datadog/appsec/utils/hash_coercion.rb +0 -23
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "ext"
4
+ require_relative "../open_feature"
4
5
  require_relative "../core/utils/time"
6
+ require_relative "hooks/evaluation_telemetry_opt_out_hook"
5
7
  require "open_feature/sdk"
6
8
 
7
9
  module Datadog
8
10
  module OpenFeature
9
- # OpenFeature feature flagging provider backed by Datadog Remote Configuration.
11
+ # OpenFeature feature flagging provider backed by Datadog configuration delivery.
10
12
  #
11
13
  # Requires openfeature-sdk >= 0.5.1 for flag evaluation metrics and EVP hook support.
12
14
  #
@@ -24,13 +26,14 @@ module Datadog
24
26
  #
25
27
  # Example:
26
28
  #
27
- # Make sure to enable Remote Configuration and OpenFeature in the Datadog configuration.
29
+ # Feature Flags use agentless configuration delivery by default. To use
30
+ # Remote Configuration delivery instead, configure the source explicitly.
28
31
  #
29
32
  # ```ruby
30
33
  # # FILE: initializers/datadog.rb
31
34
  # Datadog.configure do |config|
32
35
  # config.remote.enabled = true
33
- # config.open_feature.enabled = true
36
+ # config.feature_flags.configuration_source = "remote_config"
34
37
  # end
35
38
  # ```
36
39
  #
@@ -58,28 +61,129 @@ module Datadog
58
61
  # # => 'Welcome back!'
59
62
  # ```
60
63
  class Provider
64
+ include ::OpenFeature::SDK::Provider::EventEmitter
65
+
61
66
  NAME = "Datadog Feature Flagging Provider"
67
+ INITIALIZATION_CANCELLED_MESSAGE = "Feature Flags provider initialization was cancelled by shutdown"
62
68
 
63
69
  attr_reader :metadata
64
70
 
65
- def initialize
71
+ def initialize(enable_evaluation_telemetry_opt_out_parameter: nil)
72
+ @enable_evaluation_telemetry_opt_out_parameter = case enable_evaluation_telemetry_opt_out_parameter
73
+ when nil
74
+ Datadog.configuration.feature_flags.evaluation_telemetry_per_subject_opt_out_enabled
75
+ when true, false
76
+ enable_evaluation_telemetry_opt_out_parameter
77
+ else
78
+ raise ArgumentError, "enable_evaluation_telemetry_opt_out_parameter expects true, false, or nil"
79
+ end
66
80
  @metadata = ::OpenFeature::SDK::Provider::ProviderMetadata.new(name: NAME).freeze
81
+ @initialization_mutex = Mutex.new
82
+ @initializing = false
83
+ @initialization_thread = nil
84
+ @awaiting_ready_after_fork = false
85
+ @initialization_failed = false
86
+ @provider_error_observed = false
87
+ @ready_pending = false
88
+ @ready_dispatch_claimed = false
89
+ @stale_emitted = false
90
+ @stale_pending = false
91
+ @error_handler = nil
92
+ @initialization_ready_handler = nil
93
+ @waiting_component = nil
94
+ @shutdown = false
67
95
  end
68
96
 
69
97
  def init
70
- # no-op
98
+ @initialization_mutex.synchronize do
99
+ raise INITIALIZATION_CANCELLED_MESSAGE if @shutdown
100
+
101
+ @initializing = true
102
+ @initialization_thread = Thread.current
103
+ @awaiting_ready_after_fork = false
104
+ @initialization_failed = false
105
+ @provider_error_observed = false
106
+ @ready_pending = false
107
+ @ready_dispatch_claimed = false
108
+ @stale_emitted = false
109
+ @stale_pending = false
110
+ end
111
+ ready_handler_installed = install_initialization_ready_handler
112
+
113
+ component, failure = OpenFeature.activate_provider(self)
114
+ cancel_initialization_if_shutdown!
115
+ fail_initialization(component, failure || "Feature Flags component could not be activated") unless component
116
+
117
+ @initialization_mutex.synchronize do
118
+ raise INITIALIZATION_CANCELLED_MESSAGE if @shutdown
119
+
120
+ @waiting_component = component
121
+ end
122
+ # Shutdown publishes this flag through the component lock; taking our mutex here would invert lock order.
123
+ wait_result = component.wait_for_configuration { @shutdown }
124
+ cancel_initialization_if_shutdown!
125
+
126
+ case wait_result
127
+ when Component::CONFIGURATION_READY
128
+ @initialization_mutex.synchronize { @initializing = false } unless ready_handler_installed
129
+ when Component::CONFIGURATION_TIMEOUT
130
+ message = "Feature Flags provider initialization timed out while waiting for configuration"
131
+ Datadog.logger.error(message)
132
+ fail_initialization(component, message)
133
+ else
134
+ fail_initialization(component, "Feature Flags provider initialization stopped before configuration arrived")
135
+ end
136
+ rescue
137
+ prepare_initialization_failure(component) if @initialization_mutex.synchronize { @initializing }
138
+ raise
139
+ ensure
140
+ @initialization_mutex.synchronize do
141
+ @waiting_component = nil
142
+ # The SDK still owes READY after init returns; the initializing thread owns that dispatch.
143
+ @initialization_thread = nil unless @initializing
144
+ end
71
145
  end
72
146
 
73
147
  def shutdown
74
- # no-op
148
+ configuration = @configuration
149
+ # @type var error_handler: Provider::provider_event_handler?
150
+ error_handler = nil
151
+ # @type var initialization_ready_handler: Provider::provider_event_handler?
152
+ initialization_ready_handler = nil
153
+ waiting_component = nil #: Component?
154
+ @initialization_mutex.synchronize do
155
+ return if @shutdown
156
+
157
+ @shutdown = true
158
+ @initializing = false
159
+ @initialization_thread = nil
160
+ error_handler = @error_handler
161
+ initialization_ready_handler = @initialization_ready_handler
162
+ waiting_component = @waiting_component
163
+ @error_handler = nil
164
+ @initialization_ready_handler = nil
165
+ end
166
+
167
+ waiting_component&.wake_configuration_waiters
168
+ configuration&.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, error_handler) if error_handler
169
+ if initialization_ready_handler
170
+ configuration&.remove_handler(
171
+ ::OpenFeature::SDK::ProviderEvent::PROVIDER_READY,
172
+ initialization_ready_handler,
173
+ )
174
+ end
175
+ OpenFeature.deactivate_provider(self)
75
176
  end
76
177
 
77
178
  def hooks
78
179
  component = Datadog.send(:components).open_feature
79
- [
180
+ hooks = [
80
181
  component&.flag_eval_metrics_hook,
81
182
  component&.flag_eval_evp_hook,
82
183
  ].compact
184
+ return hooks unless @enable_evaluation_telemetry_opt_out_parameter
185
+
186
+ hooks.map { |hook| Hooks::EvaluationTelemetryOptOutHook.new(hook) }
83
187
  end
84
188
 
85
189
  def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
@@ -108,6 +212,191 @@ module Datadog
108
212
 
109
213
  private
110
214
 
215
+ def after_fork(component)
216
+ handler = @initialization_mutex.synchronize do
217
+ return if @shutdown || !@initializing || @initialization_thread&.alive?
218
+
219
+ @initializing = false
220
+ @initialization_thread = nil
221
+ @waiting_component = nil
222
+ @awaiting_ready_after_fork = true
223
+ @stale_pending = false
224
+ previous_handler = @initialization_ready_handler
225
+ @initialization_ready_handler = nil
226
+ previous_handler
227
+ end
228
+ if handler
229
+ @configuration&.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY, handler)
230
+ end
231
+ configuration_changed(Component::CONFIGURATION_READY) if component&.configuration_received?
232
+ end
233
+
234
+ def fail_initialization(component, message)
235
+ prepare_initialization_failure(component)
236
+ cancel_initialization_if_shutdown!
237
+ raise message.to_s
238
+ end
239
+
240
+ def prepare_initialization_failure(component)
241
+ cancelled, initialization_ready_handler = @initialization_mutex.synchronize do
242
+ handler = @initialization_ready_handler
243
+ @initialization_ready_handler = nil
244
+ @stale_pending = false
245
+ @initializing = false
246
+ @initialization_thread = nil
247
+ if @shutdown
248
+ [true, handler]
249
+ else
250
+ @initialization_failed = true
251
+ @ready_pending ||= !!component&.configuration_received?
252
+ [false, handler]
253
+ end
254
+ end
255
+ if initialization_ready_handler
256
+ @configuration&.remove_handler(
257
+ ::OpenFeature::SDK::ProviderEvent::PROVIDER_READY,
258
+ initialization_ready_handler,
259
+ )
260
+ end
261
+ return if cancelled
262
+
263
+ install_error_handler
264
+ end
265
+
266
+ def cancel_initialization_if_shutdown!
267
+ cancelled = @initialization_mutex.synchronize do
268
+ @initializing = false if @shutdown
269
+ @shutdown
270
+ end
271
+ raise INITIALIZATION_CANCELLED_MESSAGE if cancelled
272
+ end
273
+
274
+ def shutdown?
275
+ @initialization_mutex.synchronize { @shutdown }
276
+ end
277
+
278
+ def install_initialization_ready_handler
279
+ configuration = @configuration
280
+ return false unless configuration
281
+
282
+ handler = @initialization_mutex.synchronize do
283
+ return false if @shutdown
284
+ return true if @initialization_ready_handler
285
+
286
+ @initialization_ready_handler = ->(details) { provider_ready(details) }
287
+ end
288
+ configuration.add_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY, handler)
289
+
290
+ cancelled = @initialization_mutex.synchronize { @shutdown }
291
+ configuration.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY, handler) if cancelled
292
+ return false if cancelled
293
+
294
+ true
295
+ end
296
+
297
+ def install_error_handler
298
+ configuration = @configuration
299
+ return unless configuration
300
+
301
+ handler = @initialization_mutex.synchronize do
302
+ return if @shutdown
303
+
304
+ @error_handler ||= ->(details) { provider_error(details) }
305
+ end
306
+ configuration.add_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler)
307
+
308
+ cancelled = @initialization_mutex.synchronize { @shutdown }
309
+ configuration.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler) if cancelled
310
+ end
311
+
312
+ def provider_error(_details)
313
+ configuration = @configuration
314
+ return unless configuration
315
+ return unless configuration.send(:provider_state, self) == ::OpenFeature::SDK::ProviderState::ERROR
316
+
317
+ emit_ready = @initialization_mutex.synchronize do
318
+ if @shutdown
319
+ false
320
+ else
321
+ @provider_error_observed = true
322
+ if @ready_pending && !@ready_dispatch_claimed
323
+ @ready_dispatch_claimed = true
324
+ else
325
+ false
326
+ end
327
+ end
328
+ end
329
+ emit_event(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY) if emit_ready
330
+ end
331
+
332
+ def provider_ready(_details)
333
+ configuration = @configuration
334
+ return unless configuration
335
+ return unless configuration.send(:provider_state, self) == ::OpenFeature::SDK::ProviderState::READY
336
+
337
+ handler, emit_stale = @initialization_mutex.synchronize do
338
+ handler = @initialization_ready_handler
339
+ @initialization_ready_handler = nil
340
+ @initializing = false
341
+ @initialization_thread = nil
342
+ if @stale_pending && !@shutdown
343
+ @stale_pending = false
344
+ @stale_emitted = true
345
+ [handler, true]
346
+ else
347
+ [handler, false]
348
+ end
349
+ end
350
+ configuration.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY, handler) if handler
351
+ emit_event(::OpenFeature::SDK::ProviderEvent::PROVIDER_STALE) if emit_stale
352
+ end
353
+
354
+ def configuration_changed(event)
355
+ event_type = @initialization_mutex.synchronize do
356
+ if @shutdown
357
+ nil
358
+ elsif event == Component::CONFIGURATION_READY
359
+ if @initializing
360
+ @ready_pending = true
361
+ @stale_pending = false
362
+ nil
363
+ elsif @awaiting_ready_after_fork
364
+ @awaiting_ready_after_fork = false
365
+ @stale_emitted = false
366
+ ::OpenFeature::SDK::ProviderEvent::PROVIDER_READY
367
+ elsif @initialization_failed && !@provider_error_observed
368
+ @stale_emitted = false
369
+ @ready_pending = true
370
+ nil
371
+ elsif @stale_emitted
372
+ @stale_emitted = false
373
+ ::OpenFeature::SDK::ProviderEvent::PROVIDER_READY
374
+ elsif @initialization_failed
375
+ if @provider_error_observed && !@ready_dispatch_claimed
376
+ @ready_dispatch_claimed = true
377
+ ::OpenFeature::SDK::ProviderEvent::PROVIDER_READY
378
+ else
379
+ @ready_pending = true
380
+ nil
381
+ end
382
+ end
383
+ elsif event == Component::CONFIGURATION_CHANGED && !@initializing
384
+ ::OpenFeature::SDK::ProviderEvent::PROVIDER_CONFIGURATION_CHANGED
385
+ elsif event == Component::CONFIGURATION_LOST
386
+ @ready_pending = false
387
+ if @initializing
388
+ @stale_pending = true
389
+ nil
390
+ else
391
+ @stale_emitted = true
392
+ ::OpenFeature::SDK::ProviderEvent::PROVIDER_STALE
393
+ end
394
+ end
395
+ end
396
+
397
+ emit_event(event_type) if event_type
398
+ end
399
+
111
400
  def evaluate(flag_key, default_value:, expected_type:, evaluation_context:)
112
401
  # Stamp evaluation entry time once, here on the eval thread. The EVP path uses this for
113
402
  # accurate first/last_evaluation bounds instead of a later hook-fire clock read.
@@ -129,7 +418,9 @@ module Datadog
129
418
  # Drive APM span enrichment directly from the evaluation path. This is the only reliable
130
419
  # way to attach `ffe_*` tags across all supported OpenFeature SDK versions, since older
131
420
  # SDKs do not dispatch provider hooks. Guarded so enrichment can never break evaluation.
132
- enrich_span(flag_key, result, evaluation_context)
421
+ unless evaluation_telemetry_opted_out?(evaluation_context)
422
+ enrich_span(flag_key, result, evaluation_context)
423
+ end
133
424
 
134
425
  sdk_success_details(result, flag_meta)
135
426
  rescue => e
@@ -218,6 +509,11 @@ module Datadog
218
509
  Datadog.send(:components).open_feature&.span_enrichment_hook
219
510
  end
220
511
 
512
+ def evaluation_telemetry_opted_out?(evaluation_context)
513
+ @enable_evaluation_telemetry_opt_out_parameter &&
514
+ Hooks::EvaluationTelemetryOptOutHook.opted_out?(evaluation_context)
515
+ end
516
+
221
517
  def component_not_configured_default(value, eval_time_ms)
222
518
  ::OpenFeature::SDK::Provider::ResolutionDetails.new(
223
519
  value: value,
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "core/configuration"
4
4
  require_relative "open_feature/configuration"
5
+ require_relative "open_feature/configuration/source"
5
6
 
6
7
  module Datadog
7
8
  # A namespace for the OpenFeature component.
@@ -9,11 +10,40 @@ module Datadog
9
10
  Core::Configuration::Settings.extend(Configuration::Settings)
10
11
 
11
12
  def self.enabled?
12
- Datadog.configuration.open_feature.enabled
13
+ Configuration::Source.resolve(Datadog.configuration).enabled?
13
14
  end
14
15
 
15
16
  def self.engine
16
17
  Datadog.send(:components).open_feature&.engine
17
18
  end
19
+
20
+ def self.activate_provider(provider)
21
+ # Initialize the component tree before taking its reconfiguration lock.
22
+ Datadog.send(:components)
23
+ Datadog.send(:safely_synchronize) do
24
+ return [nil, nil] if provider.send(:shutdown?)
25
+
26
+ components = Datadog.send(:components, allow_initialization: false)
27
+ activation = components&.send(:open_feature_activation)
28
+ providers = @adopted_providers ||= {}.compare_by_identity
29
+ providers[provider] = true
30
+ [activation&.activate(provider), activation&.failure]
31
+ end
32
+ end
33
+
34
+ def self.deactivate_provider(provider)
35
+ Datadog.send(:safely_synchronize) do
36
+ @adopted_providers&.delete(provider)
37
+ components = Datadog.send(:components, allow_initialization: false)
38
+ components&.send(:open_feature_activation)&.deactivate(provider)
39
+ end
40
+ end
41
+
42
+ # Components startup already holds the non-reentrant reconfiguration lock.
43
+ def self.reattach(activation)
44
+ providers = @adopted_providers&.keys || []
45
+ providers.each { |provider| activation.activate(provider) }
46
+ nil
47
+ end
18
48
  end
19
49
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
3
4
  require "libdatadog"
4
5
  require_relative "../../core/utils/time"
5
6
 
@@ -9,11 +10,11 @@ module Datadog
9
10
  # Collects information of relevance for profiler. This will get sent alongside
10
11
  # the profile and show up in the UI or potentially influence processing in some way.
11
12
  #
12
- # Information is currently collected and frozen at construction time. A full collector
13
- # could be seen as overkill for this case but it allows us to centralize information
14
- # gathering and easily support more flexible/dynamic info collection in the future.
13
+ # Most information is static: collected and frozen at construction time. The exception is
14
+ # what `collect_dynamic_info` collects, which can change during the process lifetime.
15
15
  class Info
16
- # @rbs @info: ::Hash[::Symbol, ::Hash[::Symbol, untyped]]
16
+ # @rbs @static_info: ::Hash[::Symbol, ::Hash[::Symbol, untyped]]
17
+ # @rbs @info_json_cache: [::Hash[::Symbol, bool], ::String]?
17
18
  # @rbs @platform_info: ::Hash[::Symbol, untyped]
18
19
  # @rbs @runtime_info: ::Hash[::Symbol, untyped]
19
20
  # @rbs @application_info: ::Hash[::Symbol, untyped]
@@ -24,18 +25,48 @@ module Datadog
24
25
  def initialize(settings)
25
26
  @profiler_info = nil
26
27
 
27
- @info = {
28
+ @static_info = {
28
29
  platform: collect_platform_info,
29
30
  runtime: collect_runtime_info,
30
31
  application: collect_application_info(settings),
31
32
  profiler: collect_profiler_info(settings),
32
33
  }.freeze
34
+ @info_json_cache = nil
33
35
  end
34
36
 
35
- attr_reader :info #: ::Hash[::Symbol, ::Hash[::Symbol, untyped]]
37
+ #: () -> ::Hash[::Symbol, ::Hash[::Symbol, untyped]]
38
+ def info
39
+ build_info(collect_dynamic_info)
40
+ end
41
+
42
+ # Serializing the same info on every flush would be wasteful, so the latest result is
43
+ # cached and reused for as long as the dynamic info is unchanged.
44
+ #: () -> ::String
45
+ def info_json
46
+ dynamic_info = collect_dynamic_info
47
+ cached_dynamic_info, cached_json = @info_json_cache
48
+ return cached_json if cached_json && cached_dynamic_info == dynamic_info
49
+
50
+ json = JSON.generate(build_info(dynamic_info)).freeze
51
+ @info_json_cache = [dynamic_info, json].freeze #: [::Hash[::Symbol, bool], ::String]
52
+ json
53
+ end
36
54
 
37
55
  private
38
56
 
57
+ #: (::Hash[::Symbol, bool]) -> ::Hash[::Symbol, ::Hash[::Symbol, untyped]]
58
+ def build_info(dynamic_info)
59
+ @static_info.merge(runtime: @static_info.fetch(:runtime).merge(dynamic_info))
60
+ end
61
+
62
+ #: () -> ::Hash[::Symbol, bool]
63
+ def collect_dynamic_info
64
+ {
65
+ yjit_enabled: defined?(::RubyVM::YJIT) ? ::RubyVM::YJIT.enabled? : false,
66
+ zjit_enabled: defined?(::RubyVM::ZJIT) ? ::RubyVM::ZJIT.enabled? : false,
67
+ }
68
+ end
69
+
39
70
  # Ruby GC tuning environment variables
40
71
  RUBY_GC_TUNING_ENV_VARS = [
41
72
  "RUBY_GC_HEAP_FREE_SLOTS",
@@ -427,33 +427,35 @@ module Datadog
427
427
  end
428
428
  end
429
429
 
430
- # To add just a bit more complexity to our detection code, in https://github.com/DataDog/dd-trace-rb/issues/3334
431
- # a user reported that our code was incorrectly flagging the mariadb variant of libmysqlclient as being
432
- # incompatible. In fact we have no reports of the mariadb variant needing the "no signals" workaround,
433
- # so we flag it as compatible when it's in use.
430
+ # MariaDB's client has diverged from Oracle's and has no reported need for the "no signals" workaround, as
431
+ # reported in https://github.com/DataDog/dd-trace-rb/issues/3334, so we flag it as compatible when it's in use.
434
432
  #
435
- # A problem is that there doesn't seem to be an obvious way to query the mysql2 gem on which kind of
436
- # libmysqlclient it's using, so we detect it by looking at the version.
433
+ # There's no way to ask the mysql2 gem which client it was built against, so we infer it from the two versions
434
+ # that `Mysql2::Client.info` exposes:
437
435
  #
438
- # The info method for mysql2 with mariadb looks something like this:
439
- # `{:id=>30308, :version=>"3.3.8", :header_version=>"11.2.2"}`
436
+ # * `version` is `mysql_get_client_info()`. For MariaDB that's the version of
437
+ # https://github.com/mariadb-corporation/mariadb-connector-c, which is still 3.x.
438
+ # * `header_version` is a constant baked in when mysql2 was compiled. Up to mysql2 0.5.6 it was the MariaDB
439
+ # server version, which is what people usually call "the mariadb version"; 0.5.7 switched it to the
440
+ # Connector/C version (https://github.com/brianmario/mysql2/pull/1406) and that broke our detection, as
441
+ # reported in https://github.com/DataDog/dd-trace-rb/issues/6536.
440
442
  #
441
- # * The version seems to come from https://github.com/mariadb-corporation/mariadb-connector-c and the latest
442
- # one is 3.x.
443
- # * The header_version is what people usually see as the "mariadb version"
443
+ # So MariaDB shows up as `{version: "3.3.8", header_version: "11.2.2"}` with mysql2 < 0.5.7 and as
444
+ # `{version: "3.4.9", header_version: "3.4.9"}` from 0.5.7 on. We accept both shapes: a header that is a server
445
+ # version (10 or newer, a sane range for modern releases per https://endoflife.date/mariadb) or one that is a
446
+ # Connector/C version (3.x, matching the client version).
444
447
  #
445
- # As a comparison, for libmysql the info looks like:
446
- # * `{:id=>80035, :version=>"8.0.35", :header_version=>"8.0.35"}`
447
- #
448
- # Thus our detection is version 4 or older, because libmysqlclient 4 is almost 20 years old so it's most probably
449
- # not that one + header_version being 10 or newer, since according to https://endoflife.date/mariadb that's a
450
- # sane range for modern mariadb releases.
448
+ # Oracle's client repeats one version in both fields (`{version: "8.0.35", header_version: "8.0.35"}`), and our
449
+ # caller already considers 8.0.0 and above compatible, so only a client older than 5.0.0 gets this far -- and
450
+ # no Oracle release has been one since libmysqlclient 4.1, which is from 2004.
451
451
  private_class_method def self.looks_like_mariadb?(info, libmysqlclient_version)
452
452
  header_version = Gem::Version.new(info[:header_version]) if info[:header_version]
453
453
 
454
- !!(header_version &&
455
- libmysqlclient_version < Gem::Version.new("5.0.0") &&
456
- header_version >= Gem::Version.new("10.0.0"))
454
+ return false unless header_version && libmysqlclient_version < Gem::Version.new("5.0.0")
455
+ return true if header_version >= Gem::Version.new("10.0.0")
456
+
457
+ connector_c_versions = Gem::Requirement.new(">= 3.0.0", "< 4.0.0")
458
+ connector_c_versions.satisfied_by?(libmysqlclient_version) && connector_c_versions.satisfied_by?(header_version)
457
459
  end
458
460
 
459
461
  private_class_method def self.dir_interruption_workaround_enabled?(settings, no_signals_workaround_enabled)
@@ -27,7 +27,7 @@ module Datadog
27
27
  attr_reader :last_flush_finish_at #: ::Time?
28
28
  attr_reader :created_at #: ::Time
29
29
  attr_reader :internal_metadata #: ::Hash[::Symbol, untyped]
30
- attr_reader :info_json #: ::String
30
+ attr_reader :info_collector #: Datadog::Profiling::Collectors::Info
31
31
  attr_reader :sequence_tracker #: singleton(Datadog::Profiling::SequenceTracker)
32
32
 
33
33
  public
@@ -56,9 +56,7 @@ module Datadog
56
56
  @last_flush_finish_at = nil
57
57
  @created_at = time_provider.now.utc
58
58
  @internal_metadata = internal_metadata
59
- # NOTE: At the time of this comment collected info does not change over time so we'll hardcode
60
- # it on startup to prevent serializing the same info on every flush.
61
- @info_json = JSON.generate(info_collector.info).freeze
59
+ @info_collector = info_collector
62
60
  @sequence_tracker = sequence_tracker
63
61
  end
64
62
 
@@ -111,7 +109,7 @@ module Datadog
111
109
  gc: gc_stats,
112
110
  }
113
111
  ),
114
- info_json: info_json,
112
+ info_json: info_collector.info_json,
115
113
  )
116
114
  end
117
115
 
@@ -21,14 +21,14 @@ module Datadog
21
21
  tracer = settings.tracing.instance
22
22
  return tracer unless tracer.nil?
23
23
 
24
+ sampler = build_sampler(settings)
25
+
24
26
  # Apply test mode settings if test mode is activated
25
27
  if settings.tracing.test_mode.enabled
26
28
  trace_flush = build_test_mode_trace_flush(settings)
27
- sampler = build_test_mode_sampler
28
29
  writer = build_test_mode_writer(settings, agent_settings)
29
30
  else
30
31
  trace_flush = build_trace_flush(settings)
31
- sampler = build_sampler(settings)
32
32
  writer = build_writer(settings, agent_settings)
33
33
  end
34
34
 
@@ -62,7 +62,26 @@ module Datadog
62
62
  end
63
63
  end
64
64
 
65
+ def bind_transport_telemetry(tracer, telemetry)
66
+ return unless tracer.is_a?(Tracing::Tracer)
67
+
68
+ writer = tracer.writer
69
+ return unless writer.respond_to?(:transport)
70
+
71
+ transport = writer.transport
72
+ if transport.is_a?(Transport::Traces::Transport) ||
73
+ (defined?(Transport::Native::Transport) && transport.is_a?(Transport::Native::Transport))
74
+ transport.telemetry = telemetry
75
+ end
76
+ nil
77
+ end
78
+
79
+ # Builds the sampler for the given settings: the sampler that keeps
80
+ # every trace when test mode is enabled, otherwise the sampler built
81
+ # from the custom sampler, sampling rules and rates.
65
82
  def build_sampler(settings)
83
+ return build_test_mode_sampler if settings.tracing.test_mode.enabled
84
+
66
85
  # A custom sampler is provided
67
86
  if (sampler = settings.tracing.sampler)
68
87
  return sampler
@@ -128,6 +128,8 @@ module Datadog
128
128
  ENV_SERVER_ERROR_STATUSES = "DD_TRACE_HTTP_SERVER_ERROR_STATUSES"
129
129
  ENV_CLIENT_ERROR_STATUSES = "DD_TRACE_HTTP_CLIENT_ERROR_STATUSES"
130
130
  end
131
+
132
+ ENV_HTTP_CLIENT_RESOURCE_NAME_QUANTIZE = "DD_TRACE_HTTP_CLIENT_RESOURCE_NAME_QUANTIZE"
131
133
  end
132
134
  end
133
135
  end
@@ -258,7 +258,9 @@ module Datadog
258
258
 
259
259
  option :otel_thread_context_enabled do |o|
260
260
  o.env Tracing::Configuration::Ext::ENV_OTEL_CONTEXT_ENABLED
261
- o.default true
261
+ o.default do
262
+ RUBY_ENGINE == "ruby" && RUBY_PLATFORM.include?("linux")
263
+ end
262
264
  o.type :bool
263
265
  end
264
266
 
@@ -566,6 +568,17 @@ module Datadog
566
568
  o.default 512
567
569
  end
568
570
 
571
+ # Whether outbound HTTP client resource names include a quantized request path.
572
+ # URL tags and outgoing requests are unchanged.
573
+ #
574
+ # @default `DD_TRACE_HTTP_CLIENT_RESOURCE_NAME_QUANTIZE` environment variable, otherwise `false`.
575
+ # @return [Boolean]
576
+ option :http_client_resource_name_quantize do |o|
577
+ o.env Tracing::Configuration::Ext::ENV_HTTP_CLIENT_RESOURCE_NAME_QUANTIZE
578
+ o.default false
579
+ o.type :bool
580
+ end
581
+
569
582
  # HTTP error statuses configuration
570
583
  # @public_api
571
584
  settings :http_error_statuses do