datadog 2.45.0 → 2.46.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -2
  3. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +4 -0
  4. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +2 -0
  5. data/ext/datadog_profiling_native_extension/profiling.c +6 -0
  6. data/ext/libdatadog_api/di.c +30 -5
  7. data/ext/libdatadog_api/extconf.rb +2 -0
  8. data/ext/libdatadog_api/otel_thread_context.c +20 -14
  9. data/ext/libdatadog_api/process_discovery.c +15 -1
  10. data/ext/libdatadog_api/trace_exporter.c +105 -25
  11. data/ext/libdatadog_extconf_helpers.rb +1 -1
  12. data/lib/datadog/core/configuration/components.rb +29 -8
  13. data/lib/datadog/core/configuration/supported_configurations.rb +5 -2
  14. data/lib/datadog/core/process_discovery.rb +5 -1
  15. data/lib/datadog/core/remote/client/capabilities.rb +34 -29
  16. data/lib/datadog/core/remote/component.rb +28 -12
  17. data/lib/datadog/core/remote/dispatcher.rb +13 -3
  18. data/lib/datadog/di/boot.rb +1 -0
  19. data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
  20. data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
  21. data/lib/datadog/di/component.rb +38 -3
  22. data/lib/datadog/di/configuration.rb +53 -2
  23. data/lib/datadog/di/context.rb +7 -2
  24. data/lib/datadog/di/contrib/active_record.rb +8 -2
  25. data/lib/datadog/di/evaluation_failure.rb +28 -0
  26. data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
  27. data/lib/datadog/di/guardrails_telemetry.rb +203 -0
  28. data/lib/datadog/di/instrumenter.rb +65 -15
  29. data/lib/datadog/di/logger.rb +30 -8
  30. data/lib/datadog/di/probe_manager.rb +48 -5
  31. data/lib/datadog/di/probe_notification_builder.rb +28 -26
  32. data/lib/datadog/di/probe_notifier_worker.rb +56 -6
  33. data/lib/datadog/di/serializer.rb +130 -54
  34. data/lib/datadog/di/snapshot_event.rb +38 -0
  35. data/lib/datadog/di/transport/http.rb +13 -3
  36. data/lib/datadog/di/transport/input.rb +32 -7
  37. data/lib/datadog/open_feature/activation.rb +268 -0
  38. data/lib/datadog/open_feature/component.rb +31 -15
  39. data/lib/datadog/open_feature/provider.rb +282 -5
  40. data/lib/datadog/open_feature.rb +31 -1
  41. data/lib/datadog/tracing/component.rb +14 -0
  42. data/lib/datadog/tracing/configuration/settings.rb +3 -1
  43. data/lib/datadog/tracing/otel_thread_context.rb +3 -5
  44. data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
  45. data/lib/datadog/tracing/transport/http/traces.rb +2 -0
  46. data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
  47. data/lib/datadog/tracing/transport/native.rb +16 -1
  48. data/lib/datadog/tracing/transport/telemetry.rb +62 -0
  49. data/lib/datadog/tracing/transport/traces.rb +82 -15
  50. data/lib/datadog/version.rb +1 -1
  51. metadata +16 -7
@@ -23,7 +23,11 @@ module Datadog
23
23
  metadata = get_metadata(settings)
24
24
 
25
25
  shutdown!
26
- @file_descriptor = _native_store_tracer_metadata(Datadog.logger, **metadata)
26
+ @file_descriptor = _native_store_tracer_metadata(
27
+ Datadog.logger,
28
+ settings.tracing.otel_thread_context_enabled,
29
+ **metadata
30
+ )
27
31
  end
28
32
 
29
33
  def shutdown!
@@ -6,7 +6,6 @@ require_relative "../../../tracing/remote"
6
6
  require_relative "../../../di/remote"
7
7
  require_relative "../../../symbol_database"
8
8
  require_relative "../../../symbol_database/remote"
9
- require_relative "../../../open_feature/remote"
10
9
 
11
10
  module Datadog
12
11
  module Core
@@ -14,40 +13,57 @@ module Datadog
14
13
  class Client
15
14
  # Capabilities
16
15
  class Capabilities
17
- attr_reader :capabilities, :receivers, :base64_capabilities
18
-
19
- def initialize(settings, telemetry, open_feature_component_provider: nil)
20
- open_feature_component_provider ||= -> {}
16
+ def initialize(settings, telemetry:)
21
17
  @capabilities = []
22
18
  @products = []
23
- @products_mutex = Mutex.new
24
19
  @receivers = []
25
20
  @telemetry = telemetry
21
+ @mutex = Mutex.new
22
+
23
+ register(settings)
26
24
 
27
- register(settings, open_feature_component_provider)
25
+ @base64_capabilities = capabilities_to_base64(@capabilities)
26
+ end
28
27
 
29
- @base64_capabilities = capabilities_to_base64
28
+ def capabilities
29
+ @mutex.synchronize { @capabilities.dup }
30
30
  end
31
31
 
32
32
  def products
33
- @products_mutex.synchronize { @products.dup }
33
+ @mutex.synchronize { @products.dup }
34
34
  end
35
35
 
36
- def add_products(*products)
37
- @products_mutex.synchronize do
38
- products.each { |product| @products << product unless @products.include?(product) }
36
+ def receivers
37
+ @mutex.synchronize { @receivers.dup }
38
+ end
39
+
40
+ def base64_capabilities
41
+ @mutex.synchronize { @base64_capabilities }
42
+ end
43
+
44
+ def register_runtime(capabilities:, products:, receivers:)
45
+ @mutex.synchronize do
46
+ register_capabilities(capabilities)
47
+ register_products(products)
48
+ register_receivers(receivers)
49
+ @base64_capabilities = capabilities_to_base64(@capabilities)
39
50
  end
40
51
  nil
41
52
  end
42
53
 
54
+ def add_products(*products)
55
+ @mutex.synchronize { register_products(products) }
56
+ nil
57
+ end
58
+
43
59
  def remove_products(*products)
44
- @products_mutex.synchronize { products.each { |product| @products.delete(product) } }
60
+ @mutex.synchronize { products.each { |product| @products.delete(product) } }
45
61
  nil
46
62
  end
47
63
 
48
64
  private
49
65
 
50
- def register(settings, open_feature_component_provider)
66
+ def register(settings)
51
67
  if settings.respond_to?(:appsec) && settings.appsec.enabled
52
68
  register_capabilities(Datadog::AppSec::Remote.capabilities)
53
69
  register_products(Datadog::AppSec::Remote.products)
@@ -95,32 +111,21 @@ module Datadog
95
111
  end
96
112
  end
97
113
  end
98
-
99
- if settings.respond_to?(:open_feature) && settings.open_feature.enabled
100
- register_capabilities(Datadog::OpenFeature::Remote.capabilities)
101
- register_products(Datadog::OpenFeature::Remote.products)
102
- register_receivers(
103
- Datadog::OpenFeature::Remote.receivers(
104
- @telemetry,
105
- component_provider: open_feature_component_provider,
106
- ),
107
- )
108
- end
109
114
  end
110
115
 
111
116
  def register_capabilities(capabilities)
112
- @capabilities.concat(capabilities)
117
+ capabilities.each { |capability| @capabilities << capability unless @capabilities.include?(capability) }
113
118
  end
114
119
 
115
120
  def register_receivers(receivers)
116
- @receivers.concat(receivers)
121
+ receivers.each { |receiver| @receivers << receiver unless @receivers.include?(receiver) }
117
122
  end
118
123
 
119
124
  def register_products(products)
120
- @products.concat(products)
125
+ products.each { |product| @products << product unless @products.include?(product) }
121
126
  end
122
127
 
123
- def capabilities_to_base64
128
+ def capabilities_to_base64(capabilities)
124
129
  return "" if capabilities.empty?
125
130
 
126
131
  cap_to_hexs = capabilities.reduce(:|).to_s(16).tap { |s| s.size.odd? && s.prepend("0") }.scan(/\h\h/)
@@ -15,7 +15,7 @@ module Datadog
15
15
  #
16
16
  # @api private
17
17
  class Component
18
- attr_reader :logger, :client, :healthy, :worker
18
+ attr_reader :logger, :healthy, :worker
19
19
 
20
20
  def initialize(settings, capabilities, agent_settings, logger:)
21
21
  @logger = logger
@@ -28,6 +28,7 @@ module Datadog
28
28
 
29
29
  @barrier = Barrier.new(settings.remote.boot_timeout_seconds)
30
30
 
31
+ @client_mutex = Mutex.new
31
32
  @client = Client.new(@transport, @capabilities, settings: settings, logger: logger)
32
33
  @healthy = false
33
34
  logger.debug { "new remote configuration client: #{@client.id} products: #{@capabilities.products.sort.join(", ")}" }
@@ -40,7 +41,7 @@ module Datadog
40
41
  end
41
42
 
42
43
  begin
43
- @client.sync
44
+ client.sync
44
45
  @healthy ||= true
45
46
  rescue Client::SyncError => e
46
47
  # Transient errors due to network or agent. Logged the error but not via telemetry
@@ -60,9 +61,11 @@ module Datadog
60
61
  end
61
62
 
62
63
  # client state is unknown, state might be corrupted
63
- @client = Client.new(@transport, @capabilities, settings: settings, logger: logger)
64
+ new_client = @client_mutex.synchronize do
65
+ @client = Client.new(@transport, @capabilities, settings: settings, logger: logger)
66
+ end
64
67
  @healthy = false
65
- logger.debug { "new remote configuration client: #{@client.id} products: #{@capabilities.products.sort.join(", ")}" }
68
+ logger.debug { "new remote configuration client: #{new_client.id} products: #{@capabilities.products.sort.join(", ")}" }
66
69
 
67
70
  # TODO: bail out if too many errors?
68
71
  end
@@ -81,6 +84,10 @@ module Datadog
81
84
  @worker.started?
82
85
  end
83
86
 
87
+ def client
88
+ @client_mutex.synchronize { @client }
89
+ end
90
+
84
91
  # If the worker is not initialized, initialize it.
85
92
  #
86
93
  # Then, waits for one client sync to be executed if `kind` is `:once`.
@@ -98,9 +105,11 @@ module Datadog
98
105
  def after_fork
99
106
  @worker.after_fork
100
107
  @barrier = Barrier.new(@settings.remote.boot_timeout_seconds)
101
- @client = Client.new(@transport, @capabilities, settings: @settings, logger: @logger)
108
+ new_client = @client_mutex.synchronize do
109
+ @client = Client.new(@transport, @capabilities, settings: @settings, logger: @logger)
110
+ end
102
111
  @healthy = false
103
- logger.debug { "remote configuration client recreated after fork: #{@client.id} products: #{@capabilities.products.sort.join(", ")}" }
112
+ logger.debug { "remote configuration client recreated after fork: #{new_client.id} products: #{@capabilities.products.sort.join(", ")}" }
104
113
  end
105
114
 
106
115
  def add_products(*products)
@@ -111,6 +120,17 @@ module Datadog
111
120
  @capabilities.remove_products(*products)
112
121
  end
113
122
 
123
+ def register(capabilities:, products:, receivers:)
124
+ @client_mutex.synchronize do
125
+ @capabilities.register_runtime(
126
+ capabilities: capabilities,
127
+ products: products,
128
+ receivers: receivers,
129
+ )
130
+ @client.dispatcher.add_receivers(*receivers)
131
+ end
132
+ end
133
+
114
134
  # Barrier provides a mechanism to fence execution until a condition happens
115
135
  class Barrier
116
136
  def initialize(timeout = nil)
@@ -189,14 +209,10 @@ module Datadog
189
209
  #
190
210
  # Those checks are instead performed inside the worker loop.
191
211
  # This allows users to upgrade their agent while keeping their application running.
192
- def build(settings, agent_settings, logger:, telemetry:, open_feature_component_provider: nil)
212
+ def build(settings, agent_settings, logger:, telemetry:)
193
213
  return unless settings.remote.enabled
194
214
 
195
- capabilities = Client::Capabilities.new(
196
- settings,
197
- telemetry,
198
- open_feature_component_provider: open_feature_component_provider,
199
- )
215
+ capabilities = Client::Capabilities.new(settings, telemetry: telemetry)
200
216
  new(settings, capabilities, agent_settings, logger: logger)
201
217
  end
202
218
  end
@@ -5,10 +5,20 @@ module Datadog
5
5
  module Remote
6
6
  # Repository update dispatcher
7
7
  class Dispatcher
8
- attr_reader :receivers
9
-
10
8
  def initialize(receivers)
11
- @receivers = receivers
9
+ @receivers = receivers.dup
10
+ @mutex = Mutex.new
11
+ end
12
+
13
+ def receivers
14
+ @mutex.synchronize { @receivers.dup }
15
+ end
16
+
17
+ def add_receivers(*receivers)
18
+ @mutex.synchronize do
19
+ receivers.each { |receiver| @receivers << receiver unless @receivers.include?(receiver) }
20
+ end
21
+ nil
12
22
  end
13
23
 
14
24
  def dispatch(changes, repository)
@@ -7,6 +7,7 @@ require_relative "code_tracker"
7
7
  require_relative "component"
8
8
  require_relative "context"
9
9
  require_relative "capture_limits"
10
+ require_relative "guardrails_telemetry"
10
11
  require_relative "capture_expression"
11
12
  require_relative "capture_expression_evaluator"
12
13
  require_relative "instrumenter"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module DI
5
+ class CaptureExpressionEvaluator
6
+ # The result of evaluating a probe's capture expressions: the
7
+ # serialized values and the evaluation errors.
8
+ class Result
9
+ # Initializes the result.
10
+ #
11
+ # @param serialized_values [Hash{String => untyped}] serialized values keyed by capture expression name
12
+ # @param errors [Array<EvaluationFailure>] evaluation errors of the expressions that failed to evaluate or serialize
13
+ # @return [void]
14
+ def initialize(serialized_values:, errors:)
15
+ @serialized_values = serialized_values
16
+ @errors = errors
17
+ end
18
+
19
+ # The serialized values keyed by capture expression name.
20
+ # @return [Hash{String => untyped}]
21
+ attr_reader :serialized_values
22
+
23
+ # The evaluation errors; one entry per expression that failed to
24
+ # evaluate or serialize.
25
+ # @return [Array<EvaluationFailure>]
26
+ attr_reader :errors
27
+ end
28
+ end
29
+ end
30
+ end
@@ -2,17 +2,28 @@
2
2
 
3
3
  require_relative "capture_expression"
4
4
  require_relative "capture_limits"
5
+ require_relative "capture_expression_evaluator/result"
6
+ require_relative "evaluation_failure"
5
7
  require_relative "fatal_exceptions"
8
+ require_relative "guardrails_telemetry"
9
+ require_relative "../core/utils/time"
6
10
 
7
11
  module Datadog
8
12
  module DI
9
13
  class CaptureExpressionEvaluator
10
- TELEMETRY_NAMESPACE = "dynamic_instrumentation"
11
-
12
- def initialize(settings:, serializer:, logger:, telemetry: nil)
14
+ # Initializes the evaluator.
15
+ #
16
+ # @param settings [Datadog::Core::Configuration::Settings] tracer settings
17
+ # @param serializer [Serializer] serializer for captured expression values
18
+ # @param logger [DI::Logger] logger for evaluation failure diagnostics
19
+ # @param guardrails_telemetry [GuardrailsTelemetry] emitter for the canonical capture-timeout skip metric
20
+ # @param telemetry [Datadog::Core::Telemetry::Component, nil] component evaluation failures are reported through
21
+ # @return [void]
22
+ def initialize(settings:, serializer:, logger:, guardrails_telemetry:, telemetry: nil)
13
23
  @settings = settings
14
24
  @serializer = serializer
15
25
  @logger = logger
26
+ @guardrails_telemetry = guardrails_telemetry
16
27
  @telemetry = telemetry
17
28
  end
18
29
 
@@ -22,11 +33,25 @@ module Datadog
22
33
 
23
34
  attr_reader :logger
24
35
 
36
+ # The guardrails metric emitter.
37
+ # @return [GuardrailsTelemetry]
38
+ attr_reader :guardrails_telemetry
39
+
25
40
  attr_reader :telemetry
26
41
 
42
+ # Evaluates each of the probe's capture expressions against the given
43
+ # context, serializing the captured values under one capture deadline
44
+ # resolved through the serializer and shared across all expressions.
45
+ # An expression that fails to evaluate or serialize contributes an entry
46
+ # to the returned errors.
47
+ #
48
+ # @param probe [Probe] the probe whose capture expressions are evaluated
49
+ # @param context [Context] evaluation context the expressions are evaluated against
50
+ # @return [Result] the evaluation result: the serialized values keyed by
51
+ # capture expression name and the errors of the expressions that failed
52
+ # to evaluate or serialize
27
53
  def evaluate(probe, context)
28
- budget_ns = settings.dynamic_instrumentation.max_time_to_serialize_ms * 1_000_000
29
- deadline_ns = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :nanosecond) + budget_ns
54
+ deadline = serializer.serialization_deadline
30
55
 
31
56
  output = {}
32
57
  evaluation_errors = []
@@ -34,9 +59,13 @@ module Datadog
34
59
  probe.capture_expressions.each do |capture_expression|
35
60
  name = capture_expression.name
36
61
 
37
- if ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :nanosecond) >= deadline_ns
62
+ if Core::Utils::Time.get_time >= deadline
38
63
  output[name] = {notCapturedReason: "timeout"}
39
- telemetry&.inc(TELEMETRY_NAMESPACE, "capture_expressions_skipped_by_timeout", 1)
64
+ telemetry&.inc(DI::TELEMETRY_NAMESPACE, "capture_expressions_skipped_by_timeout", 1)
65
+ guardrails_telemetry.skipped(
66
+ reason: GuardrailsTelemetry::Reason::EVALUATION_TIMEOUT,
67
+ probe: probe,
68
+ )
40
69
  next
41
70
  end
42
71
 
@@ -53,10 +82,11 @@ module Datadog
53
82
  attribute_count: limits[:attribute_count],
54
83
  length: limits[:length],
55
84
  collection_size: limits[:collection_size],
85
+ deadline: deadline,
56
86
  )
57
87
  rescue Exception => exc # standard:disable Lint/RescueException
58
88
  Datadog::DI.reraise_if_fatal(exc)
59
- evaluation_errors << {expr: name, message: "#{exc.class}: #{exc.message}"}
89
+ evaluation_errors << EvaluationFailure.new(expr: name, message: "#{exc.class}: #{exc.message}")
60
90
  logger.debug do
61
91
  "di: probe #{probe.id}: capture expression #{name}: evaluation failed: #{exc.class}: #{exc.message}"
62
92
  end
@@ -64,7 +94,7 @@ module Datadog
64
94
  end
65
95
  end
66
96
 
67
- [output, evaluation_errors]
97
+ Result.new(serialized_values: output, errors: evaluation_errors)
68
98
  end
69
99
  end
70
100
  end
@@ -16,6 +16,16 @@ module Datadog
16
16
  # resources and installed tracepoints upon shutdown.
17
17
  class Component
18
18
  class << self
19
+ # Builds and registers the DI component, or returns nil without
20
+ # building one when the settings have no dynamic_instrumentation
21
+ # section, DI is explicitly disabled, the runtime is unsupported,
22
+ # or Remote Configuration is disabled.
23
+ #
24
+ # @param settings [Datadog::Core::Configuration::Settings]
25
+ # @param agent_settings [Datadog::Core::Configuration::AgentSettings]
26
+ # @param logger [Core::Logger]
27
+ # @param telemetry [Core::Telemetry::Component, nil]
28
+ # @return [Component, nil] the built component, or nil when no component was built
19
29
  def build(settings, agent_settings, logger, telemetry: nil)
20
30
  return unless settings.respond_to?(:dynamic_instrumentation)
21
31
 
@@ -65,28 +75,47 @@ module Datadog
65
75
  end
66
76
  end
67
77
 
78
+ # Initializes the DI component.
79
+ #
80
+ # @param settings [Datadog::Core::Configuration::Settings] tracer settings
81
+ # @param agent_settings [Datadog::Core::Configuration::AgentSettings] agent connection settings for the transports
82
+ # @param logger [Datadog::Core::Logger] diagnostics logger, wrapped in the DI logger facade
83
+ # @param code_tracker [CodeTracker, nil] code tracker for line probe installation
84
+ # @param telemetry [Datadog::Core::Telemetry::Component, nil] component DI errors and guardrails metrics are emitted through
85
+ # @return [void]
68
86
  def initialize(settings, agent_settings, logger, code_tracker: nil, telemetry: nil)
69
87
  @settings = settings
70
88
  @agent_settings = agent_settings
71
89
  logger = DI::Logger.new(settings, logger)
72
90
  @logger = logger
73
91
  @telemetry = telemetry
92
+ @guardrails_telemetry = GuardrailsTelemetry.new(settings: settings, logger: logger, telemetry: telemetry)
74
93
  @code_tracker = code_tracker
75
94
  @redactor = Redactor.new(settings)
76
- @serializer = Serializer.new(settings, redactor, telemetry: telemetry)
77
- @instrumenter = Instrumenter.new(settings, serializer, logger, code_tracker: code_tracker, telemetry: telemetry)
95
+ @serializer = Serializer.new(settings, redactor, logger: logger,
96
+ guardrails_telemetry: guardrails_telemetry, telemetry: telemetry)
97
+ @capture_expression_evaluator = CaptureExpressionEvaluator.new(
98
+ settings: settings, serializer: serializer, logger: logger,
99
+ guardrails_telemetry: guardrails_telemetry, telemetry: telemetry,
100
+ )
101
+ @instrumenter = Instrumenter.new(settings, serializer, logger, code_tracker: code_tracker, telemetry: telemetry,
102
+ guardrails_telemetry: guardrails_telemetry,
103
+ capture_expression_evaluator: capture_expression_evaluator,)
78
104
  @probe_repository = ProbeRepository.new
79
- @probe_notification_builder = ProbeNotificationBuilder.new(settings, serializer, logger, telemetry: telemetry)
105
+ @probe_notification_builder = ProbeNotificationBuilder.new(settings, serializer, logger,
106
+ capture_expression_evaluator: capture_expression_evaluator,)
80
107
  @probe_notifier_worker = ProbeNotifierWorker.new(
81
108
  settings, logger,
82
109
  agent_settings: agent_settings,
83
110
  probe_repository: probe_repository,
84
111
  probe_notification_builder: probe_notification_builder,
85
112
  telemetry: telemetry,
113
+ guardrails_telemetry: guardrails_telemetry,
86
114
  )
87
115
  @probe_manager = ProbeManager.new(
88
116
  settings, instrumenter, probe_notification_builder, probe_notifier_worker, logger, probe_repository,
89
117
  telemetry: telemetry,
118
+ guardrails_telemetry: guardrails_telemetry,
90
119
  )
91
120
  # @started transitions are serialized by @lifecycle_mutex so that
92
121
  # concurrent RC callbacks (which run on the remote-config thread)
@@ -99,6 +128,12 @@ module Datadog
99
128
  attr_reader :agent_settings
100
129
  attr_reader :logger
101
130
  attr_reader :telemetry
131
+ # The guardrails metric emitter.
132
+ # @return [GuardrailsTelemetry]
133
+ attr_reader :guardrails_telemetry
134
+ # The capture expression evaluator.
135
+ # @return [CaptureExpressionEvaluator]
136
+ attr_reader :capture_expression_evaluator
102
137
  attr_reader :code_tracker
103
138
  attr_reader :instrumenter
104
139
  attr_reader :probe_repository
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../core/configuration/stable_config"
4
+
3
5
  module Datadog
4
6
  module DI
5
7
  # Configuration for DI
@@ -136,8 +138,35 @@ module Datadog
136
138
 
137
139
  option :max_time_to_serialize_ms do |o|
138
140
  o.type :int
139
- o.default 200
140
- o.env "DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE"
141
+ o.env "DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS"
142
+ o.default do
143
+ legacy = Settings.legacy_capture_timeout_value
144
+ if legacy
145
+ Datadog::Core.log_deprecation(key: :di_legacy_max_time_to_serialize) do
146
+ "The DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE environment variable is deprecated " \
147
+ "and will be removed in a future release. " \
148
+ "Please use DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS instead."
149
+ end
150
+
151
+ begin
152
+ Integer(legacy, 10)
153
+ rescue ArgumentError
154
+ raise ArgumentError,
155
+ "Expected environment variable DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE " \
156
+ "to be an int, but '#{legacy}' was provided."
157
+ end
158
+ else
159
+ 150
160
+ end
161
+ end
162
+ o.setter do |value, _old|
163
+ if value < 0
164
+ raise ArgumentError,
165
+ "The setting `dynamic_instrumentation.max_time_to_serialize_ms` must not be negative, " \
166
+ "but `#{value.inspect}` was provided."
167
+ end
168
+ value
169
+ end
141
170
  end
142
171
 
143
172
  # Settings in the 'internal' group are for internal Datadog
@@ -244,6 +273,28 @@ module Datadog
244
273
  end
245
274
  end
246
275
  end
276
+
277
+ # Resolves the deprecated capture-timeout name for the setting's
278
+ # default. The legacy value applies only when the canonical variable
279
+ # is configured nowhere (environment with its registry alias, local or
280
+ # fleet stable configuration), and the legacy itself resolves in the
281
+ # option machinery's precedence order: fleet stable configuration,
282
+ # environment, local stable configuration.
283
+ def self.legacy_capture_timeout_value
284
+ canonical_name = "DD_DYNAMIC_INSTRUMENTATION_CAPTURE_TIMEOUT_MS"
285
+ legacy_name = "DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE"
286
+
287
+ stable = Datadog::Core::Configuration::StableConfig.configuration
288
+ local = stable&.dig(:local, :config)
289
+ fleet = stable&.dig(:fleet, :config)
290
+
291
+ canonical = (fleet && DATADOG_ENV.get_environment_variable(canonical_name, source_env: fleet)) ||
292
+ DATADOG_ENV[canonical_name] ||
293
+ (local && DATADOG_ENV.get_environment_variable(canonical_name, source_env: local))
294
+ return nil if canonical
295
+
296
+ (fleet && fleet[legacy_name]) || DATADOG_ENV[legacy_name] || (local && local[legacy_name])
297
+ end
247
298
  end
248
299
  end
249
300
  end
@@ -61,9 +61,14 @@ module Datadog
61
61
  # Exception raised by the method, if any, for a method probe
62
62
  attr_reader :exception
63
63
 
64
- def serialized_locals
64
+ # Returns the locals captured at the probe hit in serialized form,
65
+ # or nil when no locals were captured.
66
+ #
67
+ # @param serialization_deadline [Float] absolute capture deadline in monotonic clock seconds
68
+ # @return [Hash{Symbol => Hash}, nil] serialized locals keyed by variable name
69
+ def serialized_locals(serialization_deadline:)
65
70
  # TODO cache?
66
- locals && serializer.serialize_vars(locals, **probe.snapshot_serializer_limits(settings))
71
+ locals && serializer.serialize_vars(locals, deadline: serialization_deadline, **probe.snapshot_serializer_limits(settings))
67
72
  end
68
73
 
69
74
  def fetch(var_name)
@@ -10,7 +10,7 @@ Datadog::DI::Serializer.register(
10
10
  # logic for "instances of classes derived from X", but a condition Proc
11
11
  # is more universal.
12
12
  condition: lambda { |value| ActiveRecord::Base === value }
13
- ) do |serializer, value, name:, depth:|
13
+ ) do |serializer, value, name:, depth:, deadline:|
14
14
  # +serializer+ is an instance of DI::Serializer.
15
15
  # Use it to perform the serialization to primitive values.
16
16
  #
@@ -30,12 +30,18 @@ Datadog::DI::Serializer.register(
30
30
  # nesting levels in the output.
31
31
  # This serializer could also potentially do its own depth limiting.
32
32
  #
33
+ # +deadline+ is the absolute monotonic-clock deadline (in seconds) of the
34
+ # capture point that is being serialized. Pass it through to
35
+ # +serialize_value+ so the custom-serialized subtree draws down the same
36
+ # capture budget as the rest of the snapshot.
37
+ #
33
38
  # Steep: steep thinks all of the arguments are nil here
34
39
  # Looks like it cannot handle kwargs in lambdas
35
40
  # @type var depth: Integer
41
+ # @type var deadline: Float
36
42
  value_to_serialize = {
37
43
  attributes: value.attributes,
38
44
  new_record: value.new_record?,
39
45
  }
40
- serializer.serialize_value(value_to_serialize, depth: depth, type: value.class)
46
+ serializer.serialize_value(value_to_serialize, depth: depth, type: value.class, deadline: deadline)
41
47
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module DI
5
+ # An expression evaluation failure reported in a snapshot's evaluation
6
+ # errors: the expression that failed and the error description.
7
+ # @api private
8
+ class EvaluationFailure
9
+ # Initializes the failure.
10
+ #
11
+ # @param expr [String] name of the capture expression, or source of the condition or template expression, that failed to evaluate or serialize
12
+ # @param message [String] error description of the failed evaluation
13
+ # @return [void]
14
+ def initialize(expr:, message:)
15
+ @expr = expr
16
+ @message = message
17
+ end
18
+
19
+ # The name of the capture expression, or the source of the condition or
20
+ # template expression, that failed to evaluate or serialize.
21
+ # @return [String]
22
+ attr_reader :expr
23
+
24
+ # @return [String] the error description of the failed evaluation
25
+ attr_reader :message
26
+ end
27
+ end
28
+ end