datadog 2.43.0 → 2.45.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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +136 -132
  4. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -5
  5. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +0 -2
  6. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +19 -20
  7. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +5 -1
  8. data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
  9. data/ext/datadog_profiling_native_extension/collectors_stack.h +1 -1
  10. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +146 -109
  11. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -2
  12. data/ext/datadog_profiling_native_extension/extconf.rb +6 -2
  13. data/ext/datadog_profiling_native_extension/heap_recorder.c +2 -2
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +122 -53
  15. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +6 -2
  16. data/ext/datadog_profiling_native_extension/profiling.c +22 -23
  17. data/ext/datadog_profiling_native_extension/ruby_helpers.c +3 -1
  18. data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -0
  19. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +0 -2
  20. data/ext/datadog_profiling_native_extension/stack_recorder.c +10 -28
  21. data/ext/datadog_profiling_native_extension/stack_recorder.h +0 -1
  22. data/ext/datadog_profiling_native_extension/time_helpers.c +2 -4
  23. data/ext/datadog_profiling_native_extension/time_helpers.h +13 -14
  24. data/ext/libdatadog_api/di.c +8 -3
  25. data/ext/libdatadog_api/extconf.rb +1 -1
  26. data/ext/libdatadog_api/feature_flags.c +8 -0
  27. data/ext/libdatadog_api/init.c +3 -1
  28. data/ext/libdatadog_api/otel_thread_context.c +38 -17
  29. data/ext/libdatadog_api/trace_exporter.c +77 -11
  30. data/ext/libdatadog_extconf_helpers.rb +14 -9
  31. data/lib/datadog/ai_guard/component.rb +17 -8
  32. data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
  33. data/lib/datadog/ai_guard/configuration.rb +13 -0
  34. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
  35. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
  36. data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
  37. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
  38. data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
  39. data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
  40. data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
  41. data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
  42. data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
  43. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
  44. data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
  45. data/lib/datadog/ai_guard/evaluation/request.rb +4 -60
  46. data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
  47. data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
  48. data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
  49. data/lib/datadog/ai_guard/evaluation.rb +25 -11
  50. data/lib/datadog/ai_guard/ext.rb +1 -0
  51. data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +6 -4
  52. data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
  53. data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
  54. data/lib/datadog/ai_guard/redaction/result.rb +29 -0
  55. data/lib/datadog/ai_guard/redaction.rb +80 -0
  56. data/lib/datadog/ai_guard.rb +58 -57
  57. data/lib/datadog/core/configuration/components.rb +9 -1
  58. data/lib/datadog/core/configuration/settings.rb +5 -2
  59. data/lib/datadog/core/configuration/supported_configurations.rb +8 -0
  60. data/lib/datadog/core/remote/client/capabilities.rb +10 -4
  61. data/lib/datadog/core/remote/component.rb +9 -2
  62. data/lib/datadog/core/remote/worker.rb +8 -0
  63. data/lib/datadog/di/instrumenter.rb +1 -1
  64. data/lib/datadog/di/probe_manager.rb +10 -5
  65. data/lib/datadog/di/proc_responder.rb +1 -1
  66. data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
  67. data/lib/datadog/open_feature/agentless/parser.rb +43 -0
  68. data/lib/datadog/open_feature/agentless/response.rb +19 -0
  69. data/lib/datadog/open_feature/agentless/transport.rb +103 -0
  70. data/lib/datadog/open_feature/component.rb +57 -1
  71. data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
  72. data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
  73. data/lib/datadog/open_feature/configuration/source.rb +43 -0
  74. data/lib/datadog/open_feature/configuration.rb +118 -0
  75. data/lib/datadog/open_feature/native_evaluator.rb +1 -14
  76. data/lib/datadog/open_feature/remote.rb +5 -5
  77. data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
  78. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +7 -2
  79. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
  80. data/lib/datadog/profiling/component.rb +2 -3
  81. data/lib/datadog/profiling/exporter.rb +7 -5
  82. data/lib/datadog/symbol_database/extractor.rb +84 -102
  83. data/lib/datadog/tracing/component.rb +3 -1
  84. data/lib/datadog/tracing/configuration/ext.rb +1 -0
  85. data/lib/datadog/tracing/configuration/settings.rb +9 -2
  86. data/lib/datadog/tracing/context.rb +24 -2
  87. data/lib/datadog/tracing/context_provider.rb +9 -5
  88. data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
  89. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +6 -3
  90. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -0
  91. data/lib/datadog/tracing/metadata/ext.rb +3 -0
  92. data/lib/datadog/tracing/otel_thread_context.rb +76 -11
  93. data/lib/datadog/tracing/tracer.rb +15 -2
  94. data/lib/datadog/tracing/transport/native.rb +23 -22
  95. data/lib/datadog/tracing/transport/trace_formatter.rb +13 -3
  96. data/lib/datadog/tracing/transport/traces.rb +1 -1
  97. data/lib/datadog/version.rb +1 -1
  98. metadata +23 -7
  99. data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "transport"
4
4
  require_relative "evaluation_engine"
5
+ require_relative "../core/utils/time"
5
6
  require_relative "exposures/buffer"
6
7
  require_relative "exposures/worker"
7
8
  require_relative "exposures/deduplicator"
@@ -16,6 +17,10 @@ module Datadog
16
17
  module OpenFeature
17
18
  # This class is the entry point for the OpenFeature component
18
19
  class Component
20
+ CONFIGURATION_READY = :ready
21
+ CONFIGURATION_TIMEOUT = :timeout
22
+ CONFIGURATION_SHUTDOWN = :shutdown
23
+
19
24
  attr_reader :engine, :flag_eval_metrics_hook, :flag_eval_evp_hook, :span_enrichment_hook
20
25
 
21
26
  def self.build(settings, agent_settings, logger:, telemetry:)
@@ -29,7 +34,7 @@ module Datadog
29
34
  return
30
35
  end
31
36
 
32
- if RUBY_ENGINE != "ruby"
37
+ unless ["ruby", "truffleruby"].include?(RUBY_ENGINE)
33
38
  message = "OpenFeature could not be enabled as MRI is required, " \
34
39
  "but running on #{RUBY_ENGINE.inspect}"
35
40
 
@@ -63,9 +68,56 @@ module Datadog
63
68
  @flag_eval_metrics_hook = create_flag_eval_metrics_hook
64
69
  @flag_eval_evp_hook = create_flag_eval_evp_hook
65
70
  @span_enrichment_hook = create_span_enrichment_hook
71
+
72
+ @configuration_mutex = Mutex.new
73
+ @reconfiguration_mutex = Mutex.new
74
+ @configuration_condition = ConditionVariable.new
75
+ @configuration_received = false
76
+ @configuration_shutdown = false
77
+ end
78
+
79
+ def reconfigure!(configuration)
80
+ @reconfiguration_mutex.synchronize do
81
+ return if configuration_shutdown?
82
+
83
+ @engine.reconfigure!(configuration)
84
+
85
+ @configuration_mutex.synchronize do
86
+ return if @configuration_shutdown
87
+
88
+ @configuration_received = !configuration.nil?
89
+ @configuration_condition.broadcast
90
+ end
91
+ end
92
+ end
93
+
94
+ def wait_for_configuration
95
+ timeout_seconds = @settings.feature_flags.initialization_timeout_ms / 1000.0
96
+ deadline = Core::Utils::Time.get_time + timeout_seconds
97
+
98
+ @configuration_mutex.synchronize do
99
+ loop do
100
+ return CONFIGURATION_SHUTDOWN if @configuration_shutdown
101
+ return CONFIGURATION_READY if @configuration_received
102
+
103
+ remaining = deadline - Core::Utils::Time.get_time
104
+ return CONFIGURATION_TIMEOUT unless remaining.positive?
105
+
106
+ @configuration_condition.wait(@configuration_mutex, remaining)
107
+ end
108
+ end
109
+ end
110
+
111
+ def configuration_received?
112
+ @configuration_mutex.synchronize { @configuration_received }
66
113
  end
67
114
 
68
115
  def shutdown!
116
+ @configuration_mutex.synchronize do
117
+ @configuration_shutdown = true
118
+ @configuration_condition.broadcast
119
+ end
120
+
69
121
  @worker.graceful_shutdown
70
122
  @flag_eval_evp_writer&.stop
71
123
  # Symmetric teardown: drop any accumulated span-enrichment state and
@@ -75,6 +127,10 @@ module Datadog
75
127
 
76
128
  private
77
129
 
130
+ def configuration_shutdown?
131
+ @configuration_mutex.synchronize { @configuration_shutdown }
132
+ end
133
+
78
134
  def create_flag_eval_metrics_hook
79
135
  return unless Hooks::FlagEvalMetricsHook.available?
80
136
 
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ module Configuration
8
+ # Validated endpoint for agentless Feature Flags configuration delivery.
9
+ class AgentlessEndpoint
10
+ DEFAULT_SITE = "datadoghq.com"
11
+ AGENTLESS_SUBDOMAIN = "ufc-server.ff-cdn"
12
+ CONFIGURATION_PATH = "/api/v2/feature-flagging/config/rules-based/server"
13
+ INVALID_SITE_CHARACTERS = /[\s\/?#@:]/
14
+ INTERNAL_WHITESPACE = /\s/
15
+
16
+ class << self
17
+ def build(site:, environment:, base_url: nil, logger: Datadog.logger)
18
+ if base_url
19
+ build_custom(base_url, logger)
20
+ else
21
+ build_managed(site, environment, logger)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def build_custom(base_url, logger)
28
+ unless base_url.ascii_only?
29
+ logger.warn("Feature Flags agentless base URL is invalid; agentless delivery is disabled")
30
+ return
31
+ end
32
+
33
+ if base_url.match?(INTERNAL_WHITESPACE)
34
+ logger.warn("Feature Flags agentless base URL contains whitespace; agentless delivery is disabled")
35
+ return
36
+ end
37
+
38
+ uri = URI.parse(base_url)
39
+ unless uri.is_a?(URI::HTTP) && uri.absolute? && uri.host
40
+ logger.warn("Feature Flags agentless base URL must be an absolute HTTP or HTTPS URL; agentless delivery is disabled")
41
+ return
42
+ end
43
+
44
+ uri.path = CONFIGURATION_PATH if uri.path.to_s.empty? || uri.path == "/"
45
+ new(uri, managed: false)
46
+ rescue URI::InvalidURIError
47
+ logger.warn("Feature Flags agentless base URL is invalid; agentless delivery is disabled")
48
+ nil
49
+ end
50
+
51
+ def build_managed(site, environment, logger)
52
+ string_site = site.to_s
53
+ unless string_site.ascii_only?
54
+ logger.warn("Feature Flags site is invalid; agentless delivery is disabled")
55
+ return
56
+ end
57
+
58
+ normalized_site = string_site.strip.downcase
59
+ normalized_site = DEFAULT_SITE if normalized_site.empty?
60
+ if normalized_site.match?(INVALID_SITE_CHARACTERS)
61
+ logger.warn("Feature Flags site is invalid; agentless delivery is disabled")
62
+ return
63
+ end
64
+
65
+ uri = URI::HTTPS.build(
66
+ host: "#{AGENTLESS_SUBDOMAIN}.#{normalized_site}",
67
+ path: CONFIGURATION_PATH,
68
+ query: environment.nil? ? nil : URI.encode_www_form(dd_env: environment),
69
+ )
70
+ new(uri, managed: true)
71
+ rescue URI::InvalidComponentError
72
+ logger.warn("Feature Flags site is invalid; agentless delivery is disabled")
73
+ nil
74
+ end
75
+ end
76
+
77
+ attr_reader :uri
78
+
79
+ def initialize(uri, managed:)
80
+ @uri = uri
81
+ @managed = managed
82
+ end
83
+
84
+ def managed?
85
+ @managed
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module OpenFeature
5
+ module Configuration
6
+ module Source
7
+ class Resolution
8
+ attr_reader :source
9
+
10
+ def initialize(source, enabled:)
11
+ @source = source
12
+ @enabled = enabled
13
+ end
14
+
15
+ def enabled?
16
+ @enabled
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "source/resolution"
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ module Configuration
8
+ module Source
9
+ AGENTLESS = "agentless"
10
+ REMOTE_CONFIG = "remote_config"
11
+ OFFLINE = "offline"
12
+
13
+ class << self
14
+ def resolve(settings, logger: Datadog.logger)
15
+ feature_flags = settings.feature_flags
16
+ source = resolve_source(feature_flags, legacy: settings.open_feature, logger: logger)
17
+ enabled = feature_flags.enabled && source != OFFLINE
18
+
19
+ Resolution.new(source, enabled: enabled)
20
+ end
21
+
22
+ private
23
+
24
+ def resolve_source(settings, legacy:, logger:)
25
+ configured_source = settings.configuration_source
26
+ if !settings.using_default?(:configuration_source) && !configured_source.empty?
27
+ return configured_source if configured_source == AGENTLESS ||
28
+ configured_source == REMOTE_CONFIG ||
29
+ configured_source == OFFLINE
30
+
31
+ logger.warn("Unsupported Feature Flags configuration source; Feature Flags are disabled")
32
+ OFFLINE
33
+ elsif !legacy.using_default?(:enabled)
34
+ legacy.enabled ? REMOTE_CONFIG : OFFLINE
35
+ else
36
+ AGENTLESS
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -5,6 +5,25 @@ module Datadog
5
5
  module Configuration
6
6
  # A settings class for the OpenFeature component.
7
7
  module Settings
8
+ DEFAULT_POLL_INTERVAL_SECONDS = 30
9
+ DEFAULT_REQUEST_TIMEOUT_SECONDS = 5
10
+ DEFAULT_INITIALIZATION_TIMEOUT_MS = 30_000
11
+
12
+ MAX_REQUEST_TIMEOUT_SECONDS = 300
13
+ MAX_INITIALIZATION_TIMEOUT_MS = 2_147_483_647
14
+
15
+ def self.configure_integer_option(option, environment_variable:, default:, setting_name:)
16
+ option.type :int
17
+ option.env environment_variable
18
+ option.env_parser do |value|
19
+ Integer(value, 10)
20
+ rescue ArgumentError
21
+ Datadog.logger.warn("#{setting_name} must be an integer; using the default")
22
+ default
23
+ end
24
+ option.default default
25
+ end
26
+
8
27
  def self.extended(base)
9
28
  base = base.singleton_class unless base.is_a?(Class)
10
29
  add_settings!(base)
@@ -15,10 +34,19 @@ module Datadog
15
34
  # Steep does not update `self` for this `class_eval` block.
16
35
  # @type self: Datadog::Core::Configuration::Base::_DslContext
17
36
  settings :open_feature do
37
+ # Deprecated switch retained for existing Remote Configuration adopters.
18
38
  option :enabled do |o|
19
39
  o.type :bool
20
40
  o.env "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED"
21
41
  o.default false
42
+ o.after_set do |_, _, precedence|
43
+ next if precedence == Core::Configuration::Option::Precedence::DEFAULT
44
+
45
+ Datadog.logger.warn(
46
+ "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED is deprecated; use " \
47
+ "DD_FEATURE_FLAGS_ENABLED and DD_FEATURE_FLAGS_CONFIGURATION_SOURCE instead"
48
+ )
49
+ end
22
50
  end
23
51
 
24
52
  # Opt-in gate for APM feature-flag span enrichment. When enabled,
@@ -42,6 +70,96 @@ module Datadog
42
70
  o.default true
43
71
  end
44
72
  end
73
+
74
+ settings :feature_flags do
75
+ option :enabled do |o|
76
+ o.type :bool
77
+ o.env "DD_FEATURE_FLAGS_ENABLED"
78
+ o.default true
79
+ end
80
+
81
+ option :configuration_source do |o|
82
+ o.type :string
83
+ o.env "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE"
84
+ o.default "agentless"
85
+ o.setter do |value|
86
+ value.to_s.strip.downcase
87
+ end
88
+ end
89
+
90
+ settings :agentless do
91
+ option :base_url do |o|
92
+ o.type :string, nilable: true
93
+ o.env "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL"
94
+ o.skip_telemetry true
95
+ o.setter do |value|
96
+ next unless value
97
+
98
+ stripped_value = value.to_s.strip
99
+ stripped_value.empty? ? nil : stripped_value
100
+ end
101
+ end
102
+
103
+ option :poll_interval_seconds do |o|
104
+ Settings.configure_integer_option(
105
+ o,
106
+ environment_variable: "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS",
107
+ default: Settings::DEFAULT_POLL_INTERVAL_SECONDS,
108
+ setting_name: "Feature Flags agentless poll interval",
109
+ )
110
+ o.setter do |value|
111
+ if value.is_a?(Integer) && value > 0
112
+ value
113
+ else
114
+ Datadog.logger.warn(
115
+ "Feature Flags agentless poll interval must be positive; using the default"
116
+ )
117
+ Settings::DEFAULT_POLL_INTERVAL_SECONDS
118
+ end
119
+ end
120
+ end
121
+
122
+ option :request_timeout_seconds do |o|
123
+ Settings.configure_integer_option(
124
+ o,
125
+ environment_variable: "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS",
126
+ default: Settings::DEFAULT_REQUEST_TIMEOUT_SECONDS,
127
+ setting_name: "Feature Flags agentless request timeout",
128
+ )
129
+ o.setter do |value|
130
+ if value.is_a?(Integer) && value > 0 && value <= Settings::MAX_REQUEST_TIMEOUT_SECONDS
131
+ value
132
+ else
133
+ Datadog.logger.warn(
134
+ "Feature Flags agentless request timeout must be within (0, " \
135
+ "#{Settings::MAX_REQUEST_TIMEOUT_SECONDS}]; using the default"
136
+ )
137
+ Settings::DEFAULT_REQUEST_TIMEOUT_SECONDS
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ option :initialization_timeout_ms do |o|
144
+ Settings.configure_integer_option(
145
+ o,
146
+ environment_variable: "DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS",
147
+ default: Settings::DEFAULT_INITIALIZATION_TIMEOUT_MS,
148
+ setting_name: "Feature Flags provider initialization timeout",
149
+ )
150
+ o.setter do |value|
151
+ if value.is_a?(Integer) && value > 0 && value <= Settings::MAX_INITIALIZATION_TIMEOUT_MS
152
+ value
153
+ else
154
+ Datadog.logger.warn(
155
+ "Feature Flags provider initialization timeout must be within (0, " \
156
+ "#{Settings::MAX_INITIALIZATION_TIMEOUT_MS}]; using the default"
157
+ )
158
+ Settings::DEFAULT_INITIALIZATION_TIMEOUT_MS
159
+ end
160
+ end
161
+ end
162
+ end
45
163
  end
46
164
  end
47
165
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
3
  require_relative "../core/feature_flags"
5
4
  require_relative "ext"
6
5
  require_relative "resolution_details"
@@ -16,7 +15,7 @@ module Datadog
16
15
  # in the format expected by `libdatadog` without any modifications
17
16
  def initialize(configuration)
18
17
  @configuration = Core::FeatureFlags::Configuration.new(configuration)
19
- @observe_full_evaluation_data = parse_observe_full_evaluation_data(configuration)
18
+ @observe_full_evaluation_data = @configuration.observe_full_evaluation_data
20
19
  end
21
20
 
22
21
  attr_reader :observe_full_evaluation_data
@@ -42,18 +41,6 @@ module Datadog
42
41
 
43
42
  private
44
43
 
45
- # Parse observe_full_evaluation_data from the top level of the UFC JSON (a sibling
46
- # of `environment`). Absent, null, or wrong-typed values return false.
47
- def parse_observe_full_evaluation_data(configuration)
48
- return false unless configuration.is_a?(String) && !configuration.empty?
49
-
50
- parsed = JSON.parse(configuration)
51
- parsed.is_a?(Hash) && parsed["observeFullEvaluationData"] == true
52
- rescue
53
- # This secondary policy parse must not reject configuration accepted by the native evaluator.
54
- false
55
- end
56
-
57
44
  def build_resolution_details(result, default_value)
58
45
  ResolutionDetails.new(
59
46
  value: result.variant.nil? ? default_value : result.value,
@@ -19,11 +19,11 @@ module Datadog
19
19
  FFE_PRODUCTS
20
20
  end
21
21
 
22
- def receivers(telemetry)
22
+ def receivers(telemetry, component_provider:)
23
23
  matcher = Core::Remote::Dispatcher::Matcher::Product.new(FFE_PRODUCTS)
24
24
  receiver = Core::Remote::Dispatcher::Receiver.new(matcher) do |repository, changes|
25
- engine = OpenFeature.engine
26
- next unless engine
25
+ component = component_provider.call
26
+ next unless component
27
27
 
28
28
  changes.each do |change|
29
29
  content = repository[change.path]
@@ -38,7 +38,7 @@ module Datadog
38
38
  when :insert, :update
39
39
  begin
40
40
  # @type var content: Core::Remote::Configuration::Content
41
- engine.reconfigure!(read_content(content))
41
+ component.reconfigure!(read_content(content))
42
42
  content.applied
43
43
  rescue EvaluationEngine::ReconfigurationError => e
44
44
  content.errored("Error applying OpenFeature configuration: #{e.class}: #{e.message}")
@@ -46,7 +46,7 @@ module Datadog
46
46
  when :delete
47
47
  # NOTE: For now, we treat deletion as clearing the configuration
48
48
  # In a multi-config scenario, we might track configs per path
49
- engine.reconfigure!(nil)
49
+ component.reconfigure!(nil)
50
50
  end
51
51
  end
52
52
  end
@@ -12,23 +12,24 @@ module Datadog
12
12
  def create_resource
13
13
  resource_attributes = {}
14
14
 
15
- @settings.tags&.each do |key, value| # steep:ignore
15
+ @settings.tags&.each do |key, value|
16
16
  otel_key = case key
17
17
  when "service" then "service.name"
18
- when "env" then "deployment.environment"
18
+ when "env", "deployment.environment.name", "deployment.environment" then next
19
19
  when "version" then "service.version"
20
20
  else key
21
21
  end
22
22
  resource_attributes[otel_key] = value
23
23
  end
24
24
 
25
- resource_attributes["service.name"] = @settings.service_without_fallback || resource_attributes["service.name"] || Datadog::Core::Environment::Ext::FALLBACK_SERVICE_NAME # steep:ignore
26
- resource_attributes["deployment.environment"] = @settings.env if @settings.env # steep:ignore
27
- resource_attributes["service.version"] = @settings.version if @settings.version # steep:ignore
25
+ resource_attributes["service.name"] = @settings.service_without_fallback || resource_attributes["service.name"] || Datadog::Core::Environment::Ext::FALLBACK_SERVICE_NAME
26
+ environment = @settings.env || @settings.tags["env"] || @settings.tags["deployment.environment.name"] || @settings.tags["deployment.environment"]
27
+ resource_attributes["deployment.environment.name"] = environment if environment
28
+ resource_attributes["service.version"] = @settings.version if @settings.version
28
29
 
29
- hostname = Datadog::Core::Environment::Socket.resolved_hostname(@settings) # steep:ignore
30
+ hostname = Datadog::Core::Environment::Socket.resolved_hostname(@settings)
30
31
  if hostname
31
- if hostname == @settings.hostname # steep:ignore
32
+ if hostname == @settings.hostname
32
33
  resource_attributes["host.name"] = hostname
33
34
  elsif !resource_attributes.key?("host.name")
34
35
  resource_attributes["host.name"] = hostname
@@ -41,9 +42,12 @@ module Datadog
41
42
  # Returns the signal-specific option value when explicitly set,
42
43
  # otherwise falls back to the general OTLP exporter config or computed_default.
43
44
  def config_or_exporter_fallback(signal:, option_name:, computed_default: nil)
44
- signal_settings = @settings.opentelemetry.public_send(signal) # steep:ignore
45
+ signal_settings = case signal
46
+ when :logs then @settings.opentelemetry.logs
47
+ when :metrics then @settings.opentelemetry.metrics
48
+ end
45
49
  if signal_settings.using_default?(option_name)
46
- @settings.opentelemetry.exporter.public_send(option_name) || computed_default # steep:ignore
50
+ @settings.opentelemetry.exporter.public_send(option_name) || computed_default
47
51
  else
48
52
  signal_settings.public_send(option_name)
49
53
  end
@@ -51,7 +51,7 @@ module Datadog
51
51
  # profiler overhead!
52
52
  dynamic_sampling_rate_enabled: true,
53
53
  skip_idle_samples_for_testing: false,
54
- idle_sampling_helper: IdleSamplingHelper.new(thread_context_collector: thread_context_collector)
54
+ idle_sampling_helper: IdleSamplingHelper.new
55
55
  )
56
56
  unless dynamic_sampling_rate_enabled
57
57
  Datadog.logger.warn(
@@ -98,7 +98,7 @@ module Datadog
98
98
 
99
99
  Datadog.logger.debug { "Starting thread for: #{self}" }
100
100
 
101
- @idle_sampling_helper.start
101
+ @idle_sampling_helper.start(self)
102
102
 
103
103
  @worker_thread = Thread.new do
104
104
  Thread.current.name = self.class.name
@@ -147,6 +147,11 @@ module Datadog
147
147
  end
148
148
  end
149
149
 
150
+ # @rbs return: Datadog::Profiling::StackRecorder
151
+ def prepare_serialize
152
+ self.class._native_prepare_serialize(self)
153
+ end
154
+
150
155
  #: () -> true
151
156
  def reset_after_fork
152
157
  self.class._native_reset_after_fork(self)
@@ -10,7 +10,6 @@ module Datadog
10
10
  class IdleSamplingHelper
11
11
  # @rbs @worker_thread: untyped
12
12
  # @rbs @start_stop_mutex: ::Thread::Mutex
13
- # @rbs @thread_context_collector: Datadog::Profiling::Collectors::ThreadContext?
14
13
 
15
14
  private
16
15
 
@@ -18,15 +17,14 @@ module Datadog
18
17
 
19
18
  public
20
19
 
21
- #: (thread_context_collector: Datadog::Profiling::Collectors::ThreadContext) -> void
22
- def initialize(thread_context_collector:)
20
+ #: () -> void
21
+ def initialize
23
22
  @worker_thread = nil
24
23
  @start_stop_mutex = Mutex.new
25
- @thread_context_collector = thread_context_collector
26
24
  end
27
25
 
28
- #: () -> (nil | true)
29
- def start
26
+ #: (CpuAndWallTimeWorker cpu_and_wall_time_worker) -> true?
27
+ def start(cpu_and_wall_time_worker)
30
28
  @start_stop_mutex.synchronize do
31
29
  return if @worker_thread&.alive?
32
30
 
@@ -39,7 +37,7 @@ module Datadog
39
37
  @worker_thread = Thread.new do
40
38
  Thread.current.name = self.class.name
41
39
 
42
- self.class._native_idle_sampling_loop(self, @thread_context_collector)
40
+ self.class._native_idle_sampling_loop(self, cpu_and_wall_time_worker)
43
41
 
44
42
  Datadog.logger.debug("IdleSamplingHelper thread stopping cleanly")
45
43
  rescue Exception => e # rubocop:disable Lint/RescueException
@@ -73,7 +73,7 @@ module Datadog
73
73
  heap_sample_every: heap_sample_every,
74
74
  }.freeze
75
75
 
76
- exporter = build_profiler_exporter(settings, recorder, worker, internal_metadata: internal_metadata)
76
+ exporter = build_profiler_exporter(settings, worker, internal_metadata: internal_metadata)
77
77
  transport = build_profiler_transport(settings, agent_settings)
78
78
  scheduler = Profiling::Scheduler.new(exporter: exporter, transport: transport, interval: upload_period_seconds)
79
79
  profiler = Profiling::Profiler.new(worker: worker, scheduler: scheduler)
@@ -109,13 +109,12 @@ module Datadog
109
109
  )
110
110
  end
111
111
 
112
- private_class_method def self.build_profiler_exporter(settings, recorder, worker, internal_metadata:)
112
+ private_class_method def self.build_profiler_exporter(settings, worker, internal_metadata:)
113
113
  info_collector = Profiling::Collectors::Info.new(settings)
114
114
  code_provenance_collector =
115
115
  (Profiling::Collectors::CodeProvenance.new if settings.profiling.advanced.code_provenance_enabled)
116
116
 
117
117
  Profiling::Exporter.new(
118
- pprof_recorder: recorder,
119
118
  worker: worker,
120
119
  info_collector: info_collector,
121
120
  code_provenance_collector: code_provenance_collector,
@@ -20,7 +20,6 @@ module Datadog
20
20
 
21
21
  private
22
22
 
23
- attr_reader :pprof_recorder #: Datadog::Profiling::StackRecorder
24
23
  # The code provenance collector acts both as collector and as a recorder
25
24
  attr_reader :code_provenance_collector #: Datadog::Profiling::Collectors::CodeProvenance?
26
25
  attr_reader :minimum_duration_seconds #: ::Integer
@@ -33,7 +32,6 @@ module Datadog
33
32
 
34
33
  public
35
34
 
36
- # @rbs pprof_recorder: Datadog::Profiling::StackRecorder
37
35
  # @rbs worker: Datadog::Profiling::Collectors::CpuAndWallTimeWorker
38
36
  # @rbs info_collector: Datadog::Profiling::Collectors::Info
39
37
  # @rbs code_provenance_collector: Datadog::Profiling::Collectors::CodeProvenance?
@@ -43,7 +41,6 @@ module Datadog
43
41
  # @rbs sequence_tracker: singleton(Datadog::Profiling::SequenceTracker)
44
42
  # @rbs return: void
45
43
  def initialize(
46
- pprof_recorder:,
47
44
  worker:,
48
45
  info_collector:,
49
46
  code_provenance_collector:,
@@ -52,7 +49,6 @@ module Datadog
52
49
  time_provider: Time,
53
50
  sequence_tracker: Datadog::Profiling::SequenceTracker
54
51
  )
55
- @pprof_recorder = pprof_recorder
56
52
  @worker = worker
57
53
  @code_provenance_collector = code_provenance_collector
58
54
  @minimum_duration_seconds = minimum_duration_seconds
@@ -68,6 +64,7 @@ module Datadog
68
64
 
69
65
  #: () -> Datadog::Profiling::Flush?
70
66
  def flush
67
+ pprof_recorder = @worker.prepare_serialize
71
68
  worker_stats = @worker.stats_and_reset_not_thread_safe
72
69
  serialization_result = pprof_recorder.serialize
73
70
  return if serialization_result.nil?
@@ -111,7 +108,7 @@ module Datadog
111
108
  worker_stats: worker_stats,
112
109
  profile_stats: profile_stats,
113
110
  recorder_stats: pprof_recorder.stats,
114
- gc: GC.stat,
111
+ gc: gc_stats,
115
112
  }
116
113
  ),
117
114
  info_json: info_json,
@@ -131,6 +128,11 @@ module Datadog
131
128
 
132
129
  private
133
130
 
131
+ # @rbs return: Hash[Symbol, Integer | bool]
132
+ def gc_stats
133
+ GC.stat.merge(auto_compact: GC.respond_to?(:auto_compact) && GC.auto_compact)
134
+ end
135
+
134
136
  #: (::Time, ::Time) -> bool
135
137
  def duration_below_threshold?(start, finish)
136
138
  (finish - start) < minimum_duration_seconds