datadog 2.46.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.c +31 -14
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +29 -2
- data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +8 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +14 -2
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +8 -7
- data/lib/datadog/appsec/security_engine/engine.rb +2 -1
- data/lib/datadog/core/configuration/components.rb +11 -10
- data/lib/datadog/core/configuration/components_state.rb +8 -1
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
- data/lib/datadog/core/remote/client.rb +44 -3
- data/lib/datadog/core/remote/dispatcher.rb +34 -3
- data/lib/datadog/core/workers/runtime_metrics.rb +1 -1
- data/lib/datadog/di/capture_expression_evaluator.rb +1 -1
- data/lib/datadog/di/component.rb +70 -0
- data/lib/datadog/di/remote.rb +46 -2
- data/lib/datadog/di/serializer.rb +27 -11
- data/lib/datadog/open_feature/configuration.rb +6 -0
- data/lib/datadog/open_feature/hooks/evaluation_telemetry_opt_out_hook.rb +25 -0
- data/lib/datadog/open_feature/provider.rb +22 -3
- data/lib/datadog/profiling/collectors/info.rb +37 -6
- data/lib/datadog/profiling/component.rb +22 -20
- data/lib/datadog/profiling/exporter.rb +3 -5
- data/lib/datadog/tracing/component.rb +7 -2
- data/lib/datadog/tracing/configuration/ext.rb +2 -0
- data/lib/datadog/tracing/configuration/settings.rb +11 -0
- data/lib/datadog/tracing/context_provider.rb +8 -0
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +10 -5
- data/lib/datadog/tracing/contrib/excon/middleware.rb +10 -3
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +10 -4
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +16 -1
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +8 -2
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +10 -4
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +10 -3
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +102 -0
- data/lib/datadog/tracing/remote.rb +50 -19
- data/lib/datadog/version.rb +1 -1
- metadata +8 -8
- data/lib/datadog/appsec/utils/hash_coercion.rb +0 -23
data/lib/datadog/di/component.rb
CHANGED
|
@@ -122,6 +122,7 @@ module Datadog
|
|
|
122
122
|
# cannot race a foreground start! with a background stop!.
|
|
123
123
|
@lifecycle_mutex = Mutex.new
|
|
124
124
|
@started = false
|
|
125
|
+
@rc_enablement_signal_paths = []
|
|
125
126
|
end
|
|
126
127
|
|
|
127
128
|
attr_reader :settings
|
|
@@ -201,6 +202,75 @@ module Datadog
|
|
|
201
202
|
@started
|
|
202
203
|
end
|
|
203
204
|
|
|
205
|
+
# Whether the component is started while the customer left
|
|
206
|
+
# DD_DYNAMIC_INSTRUMENTATION_ENABLED at its default, the state produced
|
|
207
|
+
# by remote-configuration enablement.
|
|
208
|
+
#
|
|
209
|
+
# An explicit `enabled = false` takes precedence over a prior RC
|
|
210
|
+
# enablement: once the customer sets the option, this returns false
|
|
211
|
+
# even while the component is still started.
|
|
212
|
+
#
|
|
213
|
+
# @return [Boolean]
|
|
214
|
+
def implicitly_enabled?
|
|
215
|
+
started? && settings.dynamic_instrumentation.using_default?(:enabled)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Whether an APM_TRACING merge that omitted dynamic_instrumentation_enabled
|
|
219
|
+
# withdraws this component's RC enable signal, so the component must stop.
|
|
220
|
+
#
|
|
221
|
+
# Returns true when the component is implicitly enabled and no unparseable
|
|
222
|
+
# content can be the signal carrier. A content at a recorded signal path can be
|
|
223
|
+
# the carrier. When no paths are recorded, every unparseable content can be the
|
|
224
|
+
# carrier.
|
|
225
|
+
#
|
|
226
|
+
# @param unparseable_contents [Array<Core::Remote::Configuration::Content>] the APM_TRACING contents that failed to parse at this merge
|
|
227
|
+
# @return [Boolean]
|
|
228
|
+
def rc_enablement_signal_withdrawn?(unparseable_contents)
|
|
229
|
+
return false unless implicitly_enabled?
|
|
230
|
+
|
|
231
|
+
recorded_signal_paths = rc_enablement_signal_paths
|
|
232
|
+
signal_source_unparseable =
|
|
233
|
+
if recorded_signal_paths.empty?
|
|
234
|
+
unparseable_contents.any?
|
|
235
|
+
else
|
|
236
|
+
unparseable_contents.any? { |content| recorded_signal_paths.include?(content.path) }
|
|
237
|
+
end
|
|
238
|
+
if signal_source_unparseable
|
|
239
|
+
if recorded_signal_paths.empty?
|
|
240
|
+
logger.trace do
|
|
241
|
+
"APM_TRACING RC: dynamic_instrumentation_enabled absent while an APM_TRACING config " \
|
|
242
|
+
"failed to parse; the enable signal carrier is unknown (no signal paths recorded), " \
|
|
243
|
+
"keeping the DI state"
|
|
244
|
+
end
|
|
245
|
+
else
|
|
246
|
+
logger.trace do
|
|
247
|
+
"APM_TRACING RC: dynamic_instrumentation_enabled absent while the config that carried " \
|
|
248
|
+
"the enable signal failed to parse; keeping the DI state"
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
false
|
|
252
|
+
else
|
|
253
|
+
logger.trace { "APM_TRACING RC: dynamic_instrumentation_enabled withdrawn; treating as false" }
|
|
254
|
+
true
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# The repository paths of the APM_TRACING contents that supplied the
|
|
259
|
+
# dynamic_instrumentation_enabled value at the last merge where the field was
|
|
260
|
+
# present. The component holds the paths so that they outlive the RC client.
|
|
261
|
+
#
|
|
262
|
+
# @return [Array<Core::Remote::Configuration::Path>]
|
|
263
|
+
attr_reader :rc_enablement_signal_paths
|
|
264
|
+
|
|
265
|
+
# Replaces the recorded enable-signal carrier paths.
|
|
266
|
+
#
|
|
267
|
+
# @param paths [Array<Core::Remote::Configuration::Path>] the carrier paths, empty when the merged field was absent
|
|
268
|
+
# @return [void]
|
|
269
|
+
def record_rc_enablement_signal_paths(paths)
|
|
270
|
+
@rc_enablement_signal_paths = paths.dup
|
|
271
|
+
nil
|
|
272
|
+
end
|
|
273
|
+
|
|
204
274
|
# Shuts down dynamic instrumentation permanently.
|
|
205
275
|
#
|
|
206
276
|
# Removes all code hooks and stops background threads.
|
data/lib/datadog/di/remote.rb
CHANGED
|
@@ -98,6 +98,9 @@ module Datadog
|
|
|
98
98
|
# and DI.activate_tracking is defined.
|
|
99
99
|
DI.activate_tracking
|
|
100
100
|
was_started = component.started?
|
|
101
|
+
if repository && !was_started
|
|
102
|
+
remove_withdrawn_probes(repository, component)
|
|
103
|
+
end
|
|
101
104
|
component.start!
|
|
102
105
|
# A probe delivered in an earlier poll while the component was stopped
|
|
103
106
|
# was dropped by #receivers (which ignores changes while !started?) and
|
|
@@ -145,7 +148,9 @@ module Datadog
|
|
|
145
148
|
# DEV: Apply this refactor to AppSec as well if implemented.
|
|
146
149
|
|
|
147
150
|
component = DI.component
|
|
148
|
-
if
|
|
151
|
+
if changes.empty?
|
|
152
|
+
remove_withdrawn_probes(repository, component) if component
|
|
153
|
+
elsif component&.started?
|
|
149
154
|
changes.each do |change|
|
|
150
155
|
case change.type
|
|
151
156
|
when :insert
|
|
@@ -176,7 +181,7 @@ module Datadog
|
|
|
176
181
|
|
|
177
182
|
def receiver(products = [PRODUCT], &block)
|
|
178
183
|
matcher = Core::Remote::Dispatcher::Matcher::Product.new(products)
|
|
179
|
-
[Core::Remote::Dispatcher::Receiver.new(matcher, &block)]
|
|
184
|
+
[Core::Remote::Dispatcher::Receiver.new(matcher, initial_reconcile: true, &block)]
|
|
180
185
|
end
|
|
181
186
|
|
|
182
187
|
private
|
|
@@ -266,6 +271,45 @@ module Datadog
|
|
|
266
271
|
end
|
|
267
272
|
end
|
|
268
273
|
|
|
274
|
+
# Removes every probe the component tracks whose id is absent from all
|
|
275
|
+
# LIVE_DEBUGGING contents in the repository. Reconciles backend deletions that
|
|
276
|
+
# produced no delete change for {.receivers}. Skips removal while a
|
|
277
|
+
# LIVE_DEBUGGING content fails to parse, because that content can hold any
|
|
278
|
+
# tracked probe.
|
|
279
|
+
def remove_withdrawn_probes(repository, component)
|
|
280
|
+
# @type var present_probe_ids: Array[String]
|
|
281
|
+
present_probe_ids = []
|
|
282
|
+
content_failed_to_parse = false
|
|
283
|
+
repository.contents.each do |content|
|
|
284
|
+
next unless content.path.product == PRODUCT
|
|
285
|
+
|
|
286
|
+
begin
|
|
287
|
+
present_probe_ids << parse_content(content)["id"]
|
|
288
|
+
rescue => exc
|
|
289
|
+
component.logger.debug do
|
|
290
|
+
"di: skipping withdrawn-probe reconcile, a LIVE_DEBUGGING content failed to parse: " \
|
|
291
|
+
"#{exc.class}: #{exc.message}"
|
|
292
|
+
end
|
|
293
|
+
content_failed_to_parse = true
|
|
294
|
+
break
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
return if content_failed_to_parse
|
|
298
|
+
|
|
299
|
+
probe_repository = component.probe_manager.probe_repository
|
|
300
|
+
tracked_probe_ids = probe_repository.installed_probes.keys | probe_repository.pending_probes.keys
|
|
301
|
+
(tracked_probe_ids - present_probe_ids).each do |probe_id|
|
|
302
|
+
component.logger.trace { "di: removing probe #{probe_id} withdrawn from remote configuration" }
|
|
303
|
+
component.probe_manager.remove_probe(probe_id)
|
|
304
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
305
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
306
|
+
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
307
|
+
|
|
308
|
+
component.logger.debug { "di: error removing withdrawn probe #{probe_id}: #{exc.class}: #{exc.message}" }
|
|
309
|
+
component.telemetry&.report(exc, description: "Error removing withdrawn probe")
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
269
313
|
def probe_known?(probe_id, component)
|
|
270
314
|
repo = component.probe_manager.probe_repository
|
|
271
315
|
!!(repo.find_installed(probe_id) ||
|
|
@@ -96,8 +96,25 @@ module Datadog
|
|
|
96
96
|
# hex string, or UTF-8 with replacement characters) before returning
|
|
97
97
|
# it from the custom serializer.
|
|
98
98
|
@@flat_registry = []
|
|
99
|
-
|
|
99
|
+
# Registers a custom serializer. The block serializes each value
|
|
100
|
+
# for which +condition+ returns a truthy value.
|
|
101
|
+
#
|
|
102
|
+
# @param condition [Proc] receives the value being serialized and
|
|
103
|
+
# returns whether this serializer applies to it
|
|
104
|
+
# @param block [Proc] serializer for the values +condition+ accepts
|
|
105
|
+
# @raise [ArgumentError] if +condition+ is not a Proc or +block+ is absent
|
|
106
|
+
# @return [void]
|
|
107
|
+
def self.register(condition:, &block)
|
|
108
|
+
unless condition.is_a?(Proc)
|
|
109
|
+
raise ArgumentError, "Serializer.register condition must be a Proc, got #{condition.class}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
unless block
|
|
113
|
+
raise ArgumentError, "Serializer.register requires a serializer block"
|
|
114
|
+
end
|
|
115
|
+
|
|
100
116
|
@@flat_registry << {condition: condition, proc: block}
|
|
117
|
+
nil
|
|
101
118
|
end
|
|
102
119
|
|
|
103
120
|
# Initializes the serializer.
|
|
@@ -159,7 +176,7 @@ module Datadog
|
|
|
159
176
|
# @param args [Array] positional arguments passed to the probed method
|
|
160
177
|
# @param kwargs [Hash{Symbol => Object}] keyword arguments passed to the probed method
|
|
161
178
|
# @param target_self [any] receiver of the probed method invocation
|
|
162
|
-
# @param deadline [Float] absolute capture deadline in
|
|
179
|
+
# @param deadline [Float] absolute capture deadline in thread CPU clock seconds
|
|
163
180
|
# @return [Hash{Symbol => Hash}] serialized arguments keyed by argument name
|
|
164
181
|
def serialize_args(args, kwargs, target_self,
|
|
165
182
|
deadline:,
|
|
@@ -178,7 +195,7 @@ module Datadog
|
|
|
178
195
|
# of executed code.
|
|
179
196
|
#
|
|
180
197
|
# @param vars [Hash{Symbol => Object}] variables to serialize, keyed by variable name
|
|
181
|
-
# @param deadline [Float] absolute capture deadline in
|
|
198
|
+
# @param deadline [Float] absolute capture deadline in thread CPU clock seconds
|
|
182
199
|
# @return [Hash{Symbol => Hash}] serialized variables keyed by variable name
|
|
183
200
|
def serialize_vars(vars,
|
|
184
201
|
deadline:,
|
|
@@ -207,7 +224,7 @@ module Datadog
|
|
|
207
224
|
#
|
|
208
225
|
# @param value [any] value to serialize
|
|
209
226
|
# @param name [Symbol, String, nil] name the value is bound to, used for redaction
|
|
210
|
-
# @param deadline [Float] absolute capture deadline in
|
|
227
|
+
# @param deadline [Float] absolute capture deadline in thread CPU clock seconds
|
|
211
228
|
# @return [Hash{Symbol => Object}] serialized value; the {type:, notCapturedReason: "timeout"} stub when the deadline is exceeded
|
|
212
229
|
def serialize_value(value, deadline:,
|
|
213
230
|
name: nil,
|
|
@@ -541,21 +558,20 @@ module Datadog
|
|
|
541
558
|
"#<#{class_name(value.class)}: serialization error>"
|
|
542
559
|
end
|
|
543
560
|
|
|
544
|
-
# Computes the absolute
|
|
545
|
-
# point from max_time_to_serialize_ms.
|
|
546
|
-
# share across all values serialized on the built-in paths so the
|
|
547
|
-
# budget is not exceeded.
|
|
561
|
+
# Computes the absolute thread CPU time deadline (in seconds) for a
|
|
562
|
+
# capture point from max_time_to_serialize_ms.
|
|
548
563
|
#
|
|
549
|
-
# @return [Float] absolute
|
|
564
|
+
# @return [Float] absolute thread CPU clock reading, in seconds, at
|
|
550
565
|
# which the capture budget is exhausted
|
|
551
566
|
def serialization_deadline
|
|
552
|
-
|
|
567
|
+
Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second) +
|
|
568
|
+
settings.dynamic_instrumentation.max_time_to_serialize_ms / 1000.0
|
|
553
569
|
end
|
|
554
570
|
|
|
555
571
|
private
|
|
556
572
|
|
|
557
573
|
def deadline_exceeded?(deadline)
|
|
558
|
-
|
|
574
|
+
Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second) >= deadline
|
|
559
575
|
end
|
|
560
576
|
|
|
561
577
|
MAX_MESSAGE_COLLECTION_SIZE = 3
|
|
@@ -78,6 +78,12 @@ module Datadog
|
|
|
78
78
|
o.default true
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
option :evaluation_telemetry_per_subject_opt_out_enabled do |o|
|
|
82
|
+
o.type :bool
|
|
83
|
+
o.env "DD_FEATURE_FLAGS_EVALUATION_TELEMETRY_PER_SUBJECT_OPT_OUT_ENABLED"
|
|
84
|
+
o.default false
|
|
85
|
+
end
|
|
86
|
+
|
|
81
87
|
option :configuration_source do |o|
|
|
82
88
|
o.type :string
|
|
83
89
|
o.env "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module OpenFeature
|
|
5
|
+
module Hooks
|
|
6
|
+
class EvaluationTelemetryOptOutHook
|
|
7
|
+
PARAMETER = "dd_evaluation_telemetry_opt_out"
|
|
8
|
+
|
|
9
|
+
def self.opted_out?(evaluation_context)
|
|
10
|
+
evaluation_context&.fields&.[](PARAMETER).equal?(true)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(hook)
|
|
14
|
+
@hook = hook
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def finally(hook_context:, evaluation_details:, **options)
|
|
18
|
+
return if self.class.opted_out?(hook_context.evaluation_context)
|
|
19
|
+
|
|
20
|
+
@hook.finally(hook_context: hook_context, evaluation_details: evaluation_details, **options)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "ext"
|
|
4
4
|
require_relative "../open_feature"
|
|
5
5
|
require_relative "../core/utils/time"
|
|
6
|
+
require_relative "hooks/evaluation_telemetry_opt_out_hook"
|
|
6
7
|
require "open_feature/sdk"
|
|
7
8
|
|
|
8
9
|
module Datadog
|
|
@@ -67,7 +68,15 @@ module Datadog
|
|
|
67
68
|
|
|
68
69
|
attr_reader :metadata
|
|
69
70
|
|
|
70
|
-
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
|
|
71
80
|
@metadata = ::OpenFeature::SDK::Provider::ProviderMetadata.new(name: NAME).freeze
|
|
72
81
|
@initialization_mutex = Mutex.new
|
|
73
82
|
@initializing = false
|
|
@@ -168,10 +177,13 @@ module Datadog
|
|
|
168
177
|
|
|
169
178
|
def hooks
|
|
170
179
|
component = Datadog.send(:components).open_feature
|
|
171
|
-
[
|
|
180
|
+
hooks = [
|
|
172
181
|
component&.flag_eval_metrics_hook,
|
|
173
182
|
component&.flag_eval_evp_hook,
|
|
174
183
|
].compact
|
|
184
|
+
return hooks unless @enable_evaluation_telemetry_opt_out_parameter
|
|
185
|
+
|
|
186
|
+
hooks.map { |hook| Hooks::EvaluationTelemetryOptOutHook.new(hook) }
|
|
175
187
|
end
|
|
176
188
|
|
|
177
189
|
def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
|
|
@@ -406,7 +418,9 @@ module Datadog
|
|
|
406
418
|
# Drive APM span enrichment directly from the evaluation path. This is the only reliable
|
|
407
419
|
# way to attach `ffe_*` tags across all supported OpenFeature SDK versions, since older
|
|
408
420
|
# SDKs do not dispatch provider hooks. Guarded so enrichment can never break evaluation.
|
|
409
|
-
|
|
421
|
+
unless evaluation_telemetry_opted_out?(evaluation_context)
|
|
422
|
+
enrich_span(flag_key, result, evaluation_context)
|
|
423
|
+
end
|
|
410
424
|
|
|
411
425
|
sdk_success_details(result, flag_meta)
|
|
412
426
|
rescue => e
|
|
@@ -495,6 +509,11 @@ module Datadog
|
|
|
495
509
|
Datadog.send(:components).open_feature&.span_enrichment_hook
|
|
496
510
|
end
|
|
497
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
|
+
|
|
498
517
|
def component_not_configured_default(value, eval_time_ms)
|
|
499
518
|
::OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
500
519
|
value: value,
|
|
@@ -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
|
-
#
|
|
13
|
-
#
|
|
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 @
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
-
#
|
|
431
|
-
#
|
|
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
|
-
#
|
|
436
|
-
#
|
|
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
|
-
#
|
|
439
|
-
#
|
|
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
|
-
#
|
|
442
|
-
#
|
|
443
|
-
#
|
|
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
|
-
#
|
|
446
|
-
#
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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 :
|
|
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
|
-
|
|
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
|
|
|
@@ -76,7 +76,12 @@ module Datadog
|
|
|
76
76
|
nil
|
|
77
77
|
end
|
|
78
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.
|
|
79
82
|
def build_sampler(settings)
|
|
83
|
+
return build_test_mode_sampler if settings.tracing.test_mode.enabled
|
|
84
|
+
|
|
80
85
|
# A custom sampler is provided
|
|
81
86
|
if (sampler = settings.tracing.sampler)
|
|
82
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
|
|
@@ -568,6 +568,17 @@ module Datadog
|
|
|
568
568
|
o.default 512
|
|
569
569
|
end
|
|
570
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
|
+
|
|
571
582
|
# HTTP error statuses configuration
|
|
572
583
|
# @public_api
|
|
573
584
|
settings :http_error_statuses do
|
|
@@ -21,6 +21,14 @@ module Datadog
|
|
|
21
21
|
def context=(ctx)
|
|
22
22
|
ctx.otel_thread_context = @otel_thread_context if ctx.respond_to?(:otel_thread_context=)
|
|
23
23
|
|
|
24
|
+
if (otel_thread_context = @otel_thread_context)
|
|
25
|
+
if (trace = ctx&.active_trace)
|
|
26
|
+
otel_thread_context.update_from_trace_op(trace)
|
|
27
|
+
else
|
|
28
|
+
otel_thread_context.clear
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
24
32
|
@context.local = ctx
|
|
25
33
|
end
|
|
26
34
|
|
|
@@ -7,6 +7,7 @@ require_relative "../../metadata/ext"
|
|
|
7
7
|
require_relative "../http"
|
|
8
8
|
require_relative "ext"
|
|
9
9
|
require_relative "../http_annotation_helper"
|
|
10
|
+
require_relative "../utils/quantization/http"
|
|
10
11
|
|
|
11
12
|
module Datadog
|
|
12
13
|
module Tracing
|
|
@@ -111,7 +112,7 @@ module Datadog
|
|
|
111
112
|
)
|
|
112
113
|
datadog_trace = Tracing.active_trace
|
|
113
114
|
|
|
114
|
-
datadog_tag_request
|
|
115
|
+
datadog_tag_request(uri)
|
|
115
116
|
|
|
116
117
|
if Tracing::Distributed::PropagationPolicy.enabled?(
|
|
117
118
|
global_config: datadog_configuration,
|
|
@@ -131,12 +132,17 @@ module Datadog
|
|
|
131
132
|
|
|
132
133
|
attr_reader :datadog_configuration
|
|
133
134
|
|
|
134
|
-
def datadog_tag_request
|
|
135
|
+
def datadog_tag_request(uri)
|
|
135
136
|
span = @datadog_span
|
|
136
137
|
span.set_tag(Tracing::Metadata::Ext::TAG_SVC_SRC, Ext::TAG_COMPONENT)
|
|
137
138
|
method = Ext::NOT_APPLICABLE_METHOD
|
|
138
139
|
method = @datadog_method.to_s if instance_variable_defined?(:@datadog_method) && !@datadog_method.nil?
|
|
139
|
-
|
|
140
|
+
path = uri&.path
|
|
141
|
+
span.resource = Contrib::Utils::Quantization::HTTP.client_resource(
|
|
142
|
+
method,
|
|
143
|
+
path,
|
|
144
|
+
enabled: Datadog.configuration.tracing.http_client_resource_name_quantize
|
|
145
|
+
)
|
|
140
146
|
|
|
141
147
|
if datadog_configuration[:peer_service]
|
|
142
148
|
span.set_tag(
|
|
@@ -153,10 +159,9 @@ module Datadog
|
|
|
153
159
|
|
|
154
160
|
span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
|
|
155
161
|
|
|
156
|
-
uri = try_parse_uri
|
|
157
162
|
return unless uri
|
|
158
163
|
|
|
159
|
-
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL,
|
|
164
|
+
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, path)
|
|
160
165
|
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, method)
|
|
161
166
|
span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, uri.host)
|
|
162
167
|
span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, uri.port)
|
|
@@ -8,6 +8,7 @@ require_relative "../http"
|
|
|
8
8
|
require_relative "../analytics"
|
|
9
9
|
require_relative "ext"
|
|
10
10
|
require_relative "../http_annotation_helper"
|
|
11
|
+
require_relative "../utils/quantization/http"
|
|
11
12
|
|
|
12
13
|
module Datadog
|
|
13
14
|
module Tracing
|
|
@@ -117,7 +118,13 @@ module Datadog
|
|
|
117
118
|
end
|
|
118
119
|
|
|
119
120
|
def annotate!(span, datum)
|
|
120
|
-
|
|
121
|
+
http_method = datum[:method].to_s.upcase
|
|
122
|
+
path = datum[:path]
|
|
123
|
+
span.resource = Contrib::Utils::Quantization::HTTP.client_resource(
|
|
124
|
+
http_method,
|
|
125
|
+
path,
|
|
126
|
+
enabled: Datadog.configuration.tracing.http_client_resource_name_quantize
|
|
127
|
+
)
|
|
121
128
|
span.service = service_name(datum[:host], @options)
|
|
122
129
|
span.set_tag(Tracing::Metadata::Ext::TAG_SVC_SRC, Ext::TAG_COMPONENT)
|
|
123
130
|
span.type = Tracing::Metadata::Ext::HTTP::TYPE_OUTBOUND
|
|
@@ -139,8 +146,8 @@ module Datadog
|
|
|
139
146
|
# Set analytics sample rate
|
|
140
147
|
Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled?
|
|
141
148
|
|
|
142
|
-
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL,
|
|
143
|
-
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD,
|
|
149
|
+
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, path)
|
|
150
|
+
span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, http_method)
|
|
144
151
|
span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, datum[:host])
|
|
145
152
|
span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, datum[:port])
|
|
146
153
|
span.set_tags(
|