datadog 2.35.0 → 2.37.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 (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -1
  3. data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +5 -12
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +71 -31
  5. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  6. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +18 -4
  7. data/ext/datadog_profiling_native_extension/collectors_stack.c +37 -18
  8. data/ext/datadog_profiling_native_extension/collectors_stack.h +8 -2
  9. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +596 -341
  10. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +11 -7
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +7 -8
  12. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -12
  13. data/ext/datadog_profiling_native_extension/extconf.rb +13 -2
  14. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +4 -43
  15. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -47
  16. data/ext/datadog_profiling_native_extension/heap_recorder.c +44 -26
  17. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +14 -35
  18. data/ext/datadog_profiling_native_extension/profiling.c +41 -4
  19. data/ext/datadog_profiling_native_extension/ruby_helpers.c +40 -34
  20. data/ext/datadog_profiling_native_extension/stack_recorder.c +24 -3
  21. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
  22. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +4 -2
  23. data/ext/libdatadog_api/datadog_ruby_common.c +7 -8
  24. data/ext/libdatadog_api/datadog_ruby_common.h +0 -12
  25. data/ext/libdatadog_api/di.c +76 -0
  26. data/ext/libdatadog_api/extconf.rb +6 -0
  27. data/ext/libdatadog_extconf_helpers.rb +1 -1
  28. data/lib/datadog/ai_guard/api_client.rb +6 -4
  29. data/lib/datadog/appsec/api_security/route_extractor.rb +13 -4
  30. data/lib/datadog/appsec/component.rb +1 -1
  31. data/lib/datadog/appsec/configuration.rb +13 -0
  32. data/lib/datadog/appsec/context.rb +4 -2
  33. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +37 -4
  34. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +59 -4
  35. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +59 -5
  36. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +64 -19
  37. data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -0
  38. data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
  39. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +41 -3
  40. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +24 -7
  41. data/lib/datadog/appsec/contrib/rack/input_peeker.rb +79 -0
  42. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +21 -0
  43. data/lib/datadog/appsec/contrib/rails/gateway/request.rb +35 -1
  44. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +17 -1
  45. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +60 -7
  46. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +24 -3
  47. data/lib/datadog/appsec/default_header_tags.rb +10 -6
  48. data/lib/datadog/appsec/ext.rb +1 -0
  49. data/lib/datadog/appsec/metrics/collector.rb +18 -1
  50. data/lib/datadog/appsec/metrics/exporter.rb +9 -3
  51. data/lib/datadog/appsec/remote.rb +9 -3
  52. data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
  53. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
  54. data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
  55. data/lib/datadog/appsec/route_normalizer.rb +80 -0
  56. data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
  57. data/lib/datadog/core/configuration/components.rb +83 -2
  58. data/lib/datadog/core/configuration/components_state.rb +6 -1
  59. data/lib/datadog/core/configuration/settings.rb +1 -5
  60. data/lib/datadog/core/configuration/supported_configurations.rb +4 -0
  61. data/lib/datadog/core/evp.rb +11 -0
  62. data/lib/datadog/core/remote/client/capabilities.rb +34 -7
  63. data/lib/datadog/core/remote/component.rb +1 -1
  64. data/lib/datadog/core/telemetry/event/app_started.rb +8 -22
  65. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
  66. data/lib/datadog/core/utils/forking.rb +3 -1
  67. data/lib/datadog/core/utils/spawn_monkey_patch.rb +3 -1
  68. data/lib/datadog/core.rb +3 -0
  69. data/lib/datadog/di/base.rb +7 -2
  70. data/lib/datadog/di/code_tracker.rb +5 -2
  71. data/lib/datadog/di/component.rb +110 -36
  72. data/lib/datadog/di/error.rb +10 -0
  73. data/lib/datadog/di/fatal_exceptions.rb +26 -0
  74. data/lib/datadog/di/instrumenter.rb +381 -165
  75. data/lib/datadog/di/probe.rb +14 -0
  76. data/lib/datadog/di/probe_file_loader.rb +8 -3
  77. data/lib/datadog/di/probe_manager.rb +67 -9
  78. data/lib/datadog/di/probe_notification_builder.rb +4 -1
  79. data/lib/datadog/di/probe_notifier_worker.rb +52 -32
  80. data/lib/datadog/di/redactor.rb +16 -1
  81. data/lib/datadog/di/remote.rb +175 -14
  82. data/lib/datadog/di/serializer.rb +12 -5
  83. data/lib/datadog/di/transport/input.rb +8 -4
  84. data/lib/datadog/di.rb +63 -0
  85. data/lib/datadog/error_tracking/collector.rb +2 -1
  86. data/lib/datadog/error_tracking/component.rb +2 -2
  87. data/lib/datadog/kit/appsec/events/v2.rb +60 -2
  88. data/lib/datadog/kit/tracing/method_tracer.rb +4 -1
  89. data/lib/datadog/open_feature/component.rb +29 -6
  90. data/lib/datadog/open_feature/configuration.rb +8 -0
  91. data/lib/datadog/open_feature/ext.rb +2 -0
  92. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
  93. data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
  94. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
  95. data/lib/datadog/open_feature/hooks/{flag_eval_hook.rb → flag_eval_metrics_hook.rb} +1 -1
  96. data/lib/datadog/open_feature/native_evaluator.rb +22 -0
  97. data/lib/datadog/open_feature/provider.rb +59 -27
  98. data/lib/datadog/open_feature/transport.rb +55 -1
  99. data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +5 -12
  100. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +4 -11
  101. data/lib/datadog/opentelemetry/sdk/propagator.rb +9 -3
  102. data/lib/datadog/opentelemetry/sdk/span_processor.rb +4 -1
  103. data/lib/datadog/opentelemetry/sdk.rb +19 -0
  104. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
  105. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -3
  106. data/lib/datadog/profiling/collectors/thread_context.rb +1 -0
  107. data/lib/datadog/profiling/component.rb +13 -15
  108. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
  109. data/lib/datadog/ruby_version.rb +25 -0
  110. data/lib/datadog/symbol_database/component.rb +453 -128
  111. data/lib/datadog/symbol_database/configuration.rb +12 -4
  112. data/lib/datadog/symbol_database/extensions.rb +19 -0
  113. data/lib/datadog/symbol_database/extractor.rb +326 -150
  114. data/lib/datadog/symbol_database/file_hash.rb +3 -1
  115. data/lib/datadog/symbol_database/remote.rb +8 -3
  116. data/lib/datadog/symbol_database/scope_batcher.rb +7 -3
  117. data/lib/datadog/symbol_database/uploader.rb +9 -2
  118. data/lib/datadog/symbol_database.rb +22 -0
  119. data/lib/datadog/tracing/configuration/ext.rb +13 -0
  120. data/lib/datadog/tracing/configuration/settings.rb +17 -0
  121. data/lib/datadog/tracing/contrib/configuration/resolver.rb +7 -0
  122. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +2 -0
  123. data/lib/datadog/tracing/contrib/grpc.rb +1 -0
  124. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +2 -0
  125. data/lib/datadog/tracing/contrib/http.rb +1 -0
  126. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
  127. data/lib/datadog/tracing/contrib/karafka.rb +1 -0
  128. data/lib/datadog/tracing/contrib/rack/middlewares.rb +3 -1
  129. data/lib/datadog/tracing/contrib/rack/route_inference.rb +3 -1
  130. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +2 -0
  131. data/lib/datadog/tracing/contrib/sidekiq.rb +1 -0
  132. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +2 -0
  133. data/lib/datadog/tracing/contrib/waterdrop.rb +1 -0
  134. data/lib/datadog/tracing/distributed/baggage.rb +2 -1
  135. data/lib/datadog/tracing/distributed/propagation.rb +33 -1
  136. data/lib/datadog/tracing/distributed/trace_context.rb +11 -2
  137. data/lib/datadog/tracing/ext.rb +9 -0
  138. data/lib/datadog/tracing/remote.rb +34 -3
  139. data/lib/datadog/tracing/trace_digest.rb +7 -0
  140. data/lib/datadog/tracing/trace_operation.rb +4 -1
  141. data/lib/datadog/tracing/tracer.rb +1 -0
  142. data/lib/datadog/version.rb +1 -1
  143. data/lib/datadog.rb +5 -1
  144. metadata +22 -6
@@ -1,13 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'ext'
4
+ require_relative '../core/utils/time'
4
5
  require 'open_feature/sdk'
5
6
 
6
7
  module Datadog
7
8
  module OpenFeature
8
9
  # OpenFeature feature flagging provider backed by Datadog Remote Configuration.
9
10
  #
10
- # Requires openfeature-sdk >= 0.5.1 for flag evaluation metrics support.
11
+ # Requires openfeature-sdk >= 0.5.1 for flag evaluation metrics and EVP hook support.
12
+ #
13
+ # Hook lifecycle note: FlagEvalEVPHook is returned from #hooks so EVP uses the SDK-final
14
+ # EvaluationDetails. This matches FlagEvalMetricsHook and records defaults/errors produced
15
+ # by OpenFeature hook failures or post-provider type validation.
11
16
  #
12
17
  # Implementation follows the OpenFeature contract of Provider SDK.
13
18
  # For details see:
@@ -70,8 +75,10 @@ module Datadog
70
75
  end
71
76
 
72
77
  def hooks
73
- hook = Datadog.send(:components).open_feature&.flag_eval_hook
74
- [hook].compact
78
+ component = Datadog.send(:components).open_feature
79
+ otel_hook = component&.flag_eval_metrics_hook
80
+ evp_hook = component&.flag_eval_evp_hook
81
+ [otel_hook, evp_hook].compact
75
82
  end
76
83
 
77
84
  def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
@@ -101,57 +108,82 @@ module Datadog
101
108
  private
102
109
 
103
110
  def evaluate(flag_key, default_value:, expected_type:, evaluation_context:)
111
+ # Stamp evaluation entry time once, here on the eval thread. The EVP path uses this for
112
+ # accurate first/last_evaluation bounds instead of a later hook-fire clock read.
113
+ eval_time_ms = (Core::Utils::Time.now.to_f * 1000).to_i
114
+
104
115
  engine = OpenFeature.engine
105
- return component_not_configured_default(default_value) if engine.nil?
116
+ return component_not_configured_default(default_value, eval_time_ms) if engine.nil?
117
+
118
+ result = fetch_engine_value(engine, flag_key, default_value, expected_type, evaluation_context)
119
+
120
+ # Build metadata before branching so provider-returned details carry eval-entry time.
121
+ flag_meta = build_flag_metadata(result, eval_time_ms)
122
+
123
+ if result.error?
124
+ return sdk_error_details(default_value, result.error_code, result.error_message, result.reason, flag_meta)
125
+ end
126
+
127
+ sdk_success_details(result, flag_meta)
128
+ rescue => e
129
+ error_message = "#{e.class}: #{e.message}"
130
+ error_result = Datadog::OpenFeature::ResolutionDetails.build_error(
131
+ value: default_value,
132
+ error_code: Ext::GENERAL,
133
+ error_message: error_message
134
+ )
135
+ error_flag_meta = build_flag_metadata(error_result, eval_time_ms || (Core::Utils::Time.now.to_f * 1000).to_i)
106
136
 
107
- result = engine.fetch_value(
137
+ sdk_error_details(default_value, Ext::GENERAL, error_message, Ext::ERROR, error_flag_meta)
138
+ end
139
+
140
+ def fetch_engine_value(engine, flag_key, default_value, expected_type, evaluation_context)
141
+ engine.fetch_value(
108
142
  flag_key,
109
143
  default_value: default_value,
110
144
  expected_type: expected_type,
111
145
  evaluation_context: evaluation_context
112
146
  )
147
+ end
113
148
 
114
- if result.error?
115
- return ::OpenFeature::SDK::Provider::ResolutionDetails.new(
116
- value: default_value,
117
- error_code: result.error_code,
118
- error_message: result.error_message,
119
- reason: result.reason
120
- )
121
- end
122
-
149
+ def sdk_success_details(result, flag_meta)
123
150
  ::OpenFeature::SDK::Provider::ResolutionDetails.new(
124
151
  value: result.value,
125
152
  variant: result.variant,
126
153
  reason: result.reason,
127
- flag_metadata: build_flag_metadata(result),
154
+ flag_metadata: flag_meta,
128
155
  )
129
- rescue => e
156
+ end
157
+
158
+ def sdk_error_details(default_value, error_code, error_message, reason, flag_meta = {})
130
159
  ::OpenFeature::SDK::Provider::ResolutionDetails.new(
131
160
  value: default_value,
132
- error_code: Ext::GENERAL,
133
- error_message: "#{e.class}: #{e.message}",
134
- reason: Ext::ERROR
161
+ error_code: error_code,
162
+ error_message: error_message,
163
+ reason: reason,
164
+ flag_metadata: flag_meta
135
165
  )
136
166
  end
137
167
 
138
- def build_flag_metadata(result)
139
- metadata = result.flag_metadata || {}
168
+ def build_flag_metadata(result, eval_time_ms)
169
+ metadata = result.flag_metadata&.dup || {}
140
170
  allocation_key = result.allocation_key
141
- if allocation_key && !allocation_key.empty?
142
- metadata = metadata.dup
143
- metadata['__dd_allocation_key'] = allocation_key
144
- end
171
+ metadata['__dd_allocation_key'] = allocation_key if allocation_key && !allocation_key.empty?
172
+
173
+ # Eval-time stamped at provider entry; the EVP hook reads 'dd.eval.timestamp_ms' for
174
+ # accurate first/last_evaluation bounds (it falls back to hook-fire time when absent).
175
+ metadata['dd.eval.timestamp_ms'] = eval_time_ms
145
176
 
146
177
  metadata
147
178
  end
148
179
 
149
- def component_not_configured_default(value)
180
+ def component_not_configured_default(value, eval_time_ms)
150
181
  ::OpenFeature::SDK::Provider::ResolutionDetails.new(
151
182
  value: value,
152
183
  error_code: Ext::PROVIDER_FATAL,
153
184
  error_message: "Datadog's OpenFeature component must be configured",
154
- reason: Ext::ERROR
185
+ reason: Ext::ERROR,
186
+ flag_metadata: {'dd.eval.timestamp_ms' => eval_time_ms}
155
187
  )
156
188
  end
157
189
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../core/encoding'
4
+ require_relative '../core/evp'
4
5
  require_relative '../core/transport/http'
5
6
  require_relative '../core/transport/http/env'
6
7
  require_relative '../core/transport/http/api/endpoint'
@@ -12,6 +13,7 @@ module Datadog
12
13
  module OpenFeature
13
14
  module Transport
14
15
  class HTTP
16
+ # API spec for the EVP exposures endpoint.
15
17
  class Spec
16
18
  def initialize
17
19
  @endpoint = Core::Transport::HTTP::API::Endpoint.new(
@@ -23,7 +25,30 @@ module Datadog
23
25
  def call(env, &block)
24
26
  @endpoint.call(env) do |request_env|
25
27
  request_env.headers['Content-Type'] = env.request.parcel.content_type
26
- request_env.headers['X-Datadog-EVP-Subdomain'] = 'event-platform-intake'
28
+ request_env.headers[Core::EVP::SUBDOMAIN_HEADER_NAME] =
29
+ Core::EVP::EVENT_PLATFORM_INTAKE_SUBDOMAIN
30
+ request_env.body = env.request.parcel.data
31
+
32
+ block.call(request_env)
33
+ end
34
+ end
35
+ end
36
+
37
+ # API spec for the EVP flagevaluation endpoint.
38
+ # Path: /evp_proxy/v2/api/v2/flagevaluation
39
+ # Header: X-Datadog-EVP-Subdomain: event-platform-intake (same as exposures)
40
+ class FlagevaluationsSpec
41
+ def initialize
42
+ @endpoint = Core::Transport::HTTP::API::Endpoint.new(
43
+ :post, '/evp_proxy/v2/api/v2/flagevaluation'
44
+ )
45
+ end
46
+
47
+ def call(env, &block)
48
+ @endpoint.call(env) do |request_env|
49
+ request_env.headers['Content-Type'] = env.request.parcel.content_type
50
+ request_env.headers[Core::EVP::SUBDOMAIN_HEADER_NAME] =
51
+ Core::EVP::EVENT_PLATFORM_INTAKE_SUBDOMAIN
27
52
  request_env.body = env.request.parcel.data
28
53
 
29
54
  block.call(request_env)
@@ -38,6 +63,14 @@ module Datadog
38
63
  ) { |t| t.api('exposures', HTTP::Spec.new) }.to_transport(self)
39
64
  end
40
65
 
66
+ # Build a transport instance for the flagevaluation EVP endpoint.
67
+ def self.build_flagevaluations(agent_settings:, logger:)
68
+ Core::Transport::HTTP.build(
69
+ agent_settings: agent_settings,
70
+ logger: logger
71
+ ) { |t| t.api('flagevaluations', HTTP::FlagevaluationsSpec.new) }.to_transport(self)
72
+ end
73
+
41
74
  def initialize(apis, default_api, logger:)
42
75
  @api = apis[default_api]
43
76
  @logger = logger
@@ -61,6 +94,27 @@ module Datadog
61
94
 
62
95
  Core::Transport::InternalErrorResponse.new(e)
63
96
  end
97
+
98
+ # POST a flag evaluations batch to /evp_proxy/v2/api/v2/flagevaluation.
99
+ # Mirrors send_exposures; uses the FlagevaluationsSpec endpoint.
100
+ def send_flag_evaluations(payload)
101
+ encoder = Core::Encoding::JSONEncoder
102
+ parcel = Core::Transport::Parcel.new(
103
+ encoder.encode(payload),
104
+ content_type: encoder.content_type
105
+ )
106
+ request = Core::Transport::Request.new(parcel)
107
+
108
+ @api.endpoint.call(Core::Transport::HTTP::Env.new(request)) do |env|
109
+ @api.call(env)
110
+ end
111
+ rescue => e
112
+ message = "Internal error during request. Cause: #{e.class}: #{e.message} " \
113
+ "Location: #{Array(e.backtrace).first}"
114
+ @logger.debug(message)
115
+
116
+ Core::Transport::InternalErrorResponse.new(e)
117
+ end
64
118
  end
65
119
  end
66
120
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'opentelemetry/exporter/otlp_logs'
4
+ require_relative '../sdk'
4
5
 
5
6
  module Datadog
6
7
  module OpenTelemetry
@@ -10,27 +11,19 @@ module Datadog
10
11
  METRIC_EXPORT_SUCCESSES = 'otel.logs_export_successes'
11
12
  METRIC_EXPORT_FAILURES = 'otel.logs_export_failures'
12
13
  METRIC_LOG_RECORDS = 'otel.log_records'
13
- TELEMETRY_NAMESPACE = 'tracers'
14
- TELEMETRY_TAGS = {'protocol' => 'http', 'encoding' => 'protobuf'}.freeze
15
14
 
16
15
  def export(log_records, timeout: nil)
17
- telemetry&.inc(TELEMETRY_NAMESPACE, METRIC_EXPORT_ATTEMPTS, 1, tags: TELEMETRY_TAGS)
18
- telemetry&.inc(TELEMETRY_NAMESPACE, METRIC_LOG_RECORDS, log_records.size, tags: TELEMETRY_TAGS)
16
+ SDK.telemetry_inc(METRIC_EXPORT_ATTEMPTS, 1)
17
+ SDK.telemetry_inc(METRIC_LOG_RECORDS, log_records.size)
19
18
  result = super
20
19
  metric_name = (result == 0) ? METRIC_EXPORT_SUCCESSES : METRIC_EXPORT_FAILURES
21
- telemetry&.inc(TELEMETRY_NAMESPACE, metric_name, 1, tags: TELEMETRY_TAGS)
20
+ SDK.telemetry_inc(metric_name, 1)
22
21
  result
23
22
  rescue => e
24
23
  Datadog.logger.warn("Failed to export OpenTelemetry Logs: #{e.class}: #{e.message}")
25
- telemetry&.inc(TELEMETRY_NAMESPACE, METRIC_EXPORT_FAILURES, 1, tags: TELEMETRY_TAGS)
24
+ SDK.telemetry_inc(METRIC_EXPORT_FAILURES, 1)
26
25
  raise
27
26
  end
28
-
29
- private
30
-
31
- def telemetry
32
- Datadog.send(:components).telemetry
33
- end
34
27
  end
35
28
  end
36
29
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'opentelemetry/exporter/otlp_metrics'
4
+ require_relative '../sdk'
4
5
 
5
6
  module Datadog
6
7
  module OpenTelemetry
@@ -9,26 +10,18 @@ module Datadog
9
10
  METRIC_EXPORT_ATTEMPTS = 'otel.metrics_export_attempts'
10
11
  METRIC_EXPORT_SUCCESSES = 'otel.metrics_export_successes'
11
12
  METRIC_EXPORT_FAILURES = 'otel.metrics_export_failures'
12
- TELEMETRY_NAMESPACE = 'tracers'
13
- TELEMETRY_TAGS = {'protocol' => "http", 'encoding' => 'protobuf'}
14
13
 
15
14
  def export(metrics, timeout: nil)
16
- telemetry&.inc(TELEMETRY_NAMESPACE, METRIC_EXPORT_ATTEMPTS, 1, tags: TELEMETRY_TAGS)
15
+ SDK.telemetry_inc(METRIC_EXPORT_ATTEMPTS, 1)
17
16
  result = super
18
17
  metric_name = (result == 0) ? METRIC_EXPORT_SUCCESSES : METRIC_EXPORT_FAILURES
19
- telemetry&.inc(TELEMETRY_NAMESPACE, metric_name, 1, tags: TELEMETRY_TAGS)
18
+ SDK.telemetry_inc(metric_name, 1)
20
19
  result
21
20
  rescue => e
22
21
  Datadog.logger.error("Failed to export OpenTelemetry Metrics: #{e.class}: #{e.message}")
23
- telemetry&.inc(TELEMETRY_NAMESPACE, METRIC_EXPORT_FAILURES, 1, tags: TELEMETRY_TAGS)
22
+ SDK.telemetry_inc(METRIC_EXPORT_FAILURES, 1)
24
23
  raise
25
24
  end
26
-
27
- private
28
-
29
- def telemetry
30
- Datadog.send(:components).telemetry
31
- end
32
25
  end
33
26
  end
34
27
  end
@@ -43,6 +43,15 @@ module Datadog
43
43
  digest = @datadog_propagator.extract(carrier)
44
44
  return context unless digest
45
45
 
46
+ # Always call continue_trace! so span links are created in restart mode
47
+ # (when digest.trace_id is nil) before we attempt to build the OTel SpanContext.
48
+ trace = Tracing.continue_trace!(digest)
49
+
50
+ # In restart mode DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart, the extracted
51
+ # digest carries no trace_id (fresh start). continue_trace! has already recorded
52
+ # the span link; return the current context so the caller's span starts a new root.
53
+ return context unless digest.trace_id
54
+
46
55
  # Converts the {Numeric} Datadog id object to OpenTelemetry's byte array format.
47
56
  # 128-bit unsigned, big-endian integer
48
57
  trace_id = [digest.trace_id >> 64, digest.trace_id & 0xFFFFFFFFFFFFFFFF].pack('Q>Q>')
@@ -70,9 +79,6 @@ module Datadog
70
79
  )
71
80
 
72
81
  span = ::OpenTelemetry::Trace.non_recording_span(span_context)
73
-
74
- trace = Tracing.continue_trace!(digest)
75
-
76
82
  span.datadog_trace = trace
77
83
 
78
84
  ::OpenTelemetry::Trace.context_with_span(span, parent_context: context)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'trace/span'
4
+ require_relative '../../core/utils'
4
5
  require_relative '../../tracing/span_link'
5
6
  require_relative '../../tracing/span_event'
6
7
  require_relative '../../tracing/trace_digest'
@@ -101,12 +102,14 @@ module Datadog
101
102
 
102
103
  unless span.links.nil?
103
104
  datadog_span.links = span.links.map do |link|
105
+ tracestate = link.span_context.tracestate&.to_s
106
+
104
107
  Datadog::Tracing::SpanLink.new(
105
108
  Datadog::Tracing::TraceDigest.new(
106
109
  trace_id: link.span_context.hex_trace_id.to_i(16),
107
110
  span_id: link.span_context.hex_span_id.to_i(16),
108
111
  trace_sampling_priority: (link.span_context.trace_flags&.sampled? ? 1 : 0),
109
- trace_state: link.span_context.tracestate&.to_s,
112
+ trace_state: tracestate && ::Datadog::Core::Utils.utf8_encode(tracestate, placeholder: nil),
110
113
  span_remote: link.span_context.remote?,
111
114
  ),
112
115
  attributes: link.attributes
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../tracing/ext'
4
+
5
+ module Datadog
6
+ module OpenTelemetry
7
+ module SDK
8
+ TELEMETRY_TAGS = {'protocol' => 'http', 'encoding' => 'protobuf'}.freeze
9
+
10
+ def self.telemetry_inc(metric_name, value)
11
+ telemetry&.inc(Datadog::Tracing::Ext::TELEMETRY_METRICS_NAMESPACE, metric_name, value, tags: TELEMETRY_TAGS)
12
+ end
13
+
14
+ def self.telemetry
15
+ Datadog.send(:components).telemetry
16
+ end
17
+ end
18
+ end
19
+ end
@@ -49,7 +49,7 @@ module Datadog
49
49
  # profiler overhead!
50
50
  dynamic_sampling_rate_enabled: true,
51
51
  skip_idle_samples_for_testing: false,
52
- idle_sampling_helper: IdleSamplingHelper.new
52
+ idle_sampling_helper: IdleSamplingHelper.new(thread_context_collector: thread_context_collector)
53
53
  )
54
54
  unless dynamic_sampling_rate_enabled
55
55
  Datadog.logger.warn(
@@ -10,6 +10,7 @@ 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?
13
14
 
14
15
  private
15
16
 
@@ -17,10 +18,11 @@ module Datadog
17
18
 
18
19
  public
19
20
 
20
- #: () -> void
21
- def initialize
21
+ #: (thread_context_collector: Datadog::Profiling::Collectors::ThreadContext) -> void
22
+ def initialize(thread_context_collector:)
22
23
  @worker_thread = nil
23
24
  @start_stop_mutex = Mutex.new
25
+ @thread_context_collector = thread_context_collector
24
26
  end
25
27
 
26
28
  #: () -> (nil | true)
@@ -37,7 +39,7 @@ module Datadog
37
39
  @worker_thread = Thread.new do
38
40
  Thread.current.name = self.class.name
39
41
 
40
- self.class._native_idle_sampling_loop(self)
42
+ self.class._native_idle_sampling_loop(self, @thread_context_collector)
41
43
 
42
44
  Datadog.logger.debug("IdleSamplingHelper thread stopping cleanly")
43
45
  rescue Exception => e # rubocop:disable Lint/RescueException
@@ -33,6 +33,7 @@ module Datadog
33
33
  waiting_for_gvl_threshold_ns: waiting_for_gvl_threshold_ns,
34
34
  otel_context_enabled: otel_context_enabled,
35
35
  native_filenames_enabled: validate_native_filenames(native_filenames_enabled),
36
+ overhead_filename: __FILE__,
36
37
  )
37
38
  end
38
39
 
@@ -141,15 +141,14 @@ module Datadog
141
141
  # that causes a segmentation fault during garbage collection of Ractors
142
142
  # (https://bugs.ruby-lang.org/issues/18464). We don't allow enabling gc profiling on such Rubies.
143
143
  # This bug is fixed on Ruby versions 3.1.4, 3.2.3 and 3.3.0.
144
- if RUBY_VERSION.start_with?("3.0.") ||
145
- (RUBY_VERSION.start_with?("3.1.") && RUBY_VERSION < "3.1.4") ||
146
- (RUBY_VERSION.start_with?("3.2.") && RUBY_VERSION < "3.2.3")
144
+ if RubyVersion.is?(">= 3", "< 3.1.4") ||
145
+ RubyVersion.is?(">= 3.2", "< 3.2.3")
147
146
  logger.warn(
148
147
  "Current Ruby version (#{RUBY_VERSION}) has a VM bug where enabling GC profiling would cause " \
149
148
  "crashes (https://bugs.ruby-lang.org/issues/18464). GC profiling has been disabled."
150
149
  )
151
150
  return false
152
- elsif RUBY_VERSION.start_with?("3.")
151
+ elsif RubyVersion.is?(">= 3", "< 4")
153
152
  logger.debug(
154
153
  "Using Ractors may result in GC profiling unexpectedly " \
155
154
  "stopping (https://bugs.ruby-lang.org/issues/19112). Note that this stop has no impact in your " \
@@ -177,7 +176,7 @@ module Datadog
177
176
  # Ruby 3.2.0 to 3.2.2 have a bug in the newobj tracepoint (https://bugs.ruby-lang.org/issues/19482,
178
177
  # https://github.com/ruby/ruby/pull/7464) that makes this crash in any configuration. This bug is
179
178
  # fixed on Ruby versions 3.2.3 and 3.3.0.
180
- if RUBY_VERSION.start_with?("3.2.") && RUBY_VERSION < "3.2.3"
179
+ if RubyVersion.is?(">= 3.2", "< 3.2.3")
181
180
  logger.warn(
182
181
  "Allocation profiling is not supported in Ruby versions 3.2.0, 3.2.1 and 3.2.2 and will be forcibly " \
183
182
  "disabled. This is due to a VM bug that can lead to crashes (https://bugs.ruby-lang.org/issues/19482). " \
@@ -191,9 +190,8 @@ module Datadog
191
190
  # that causes a segmentation fault during garbage collection of Ractors
192
191
  # (https://bugs.ruby-lang.org/issues/18464). We don't recommend using this feature on such Rubies.
193
192
  # This bug is fixed on Ruby versions 3.1.4, 3.2.3 and 3.3.0.
194
- if RUBY_VERSION.start_with?("3.0.") ||
195
- (RUBY_VERSION.start_with?("3.1.") && RUBY_VERSION < "3.1.4") ||
196
- (RUBY_VERSION.start_with?("3.2.") && RUBY_VERSION < "3.2.3")
193
+ if RubyVersion.is?(">= 3", "< 3.1.4") ||
194
+ RubyVersion.is?(">= 3.2", "< 3.2.3")
197
195
  logger.warn(
198
196
  "Current Ruby version (#{RUBY_VERSION}) has a VM bug where enabling allocation profiling while using " \
199
197
  "Ractors may cause unexpected issues, including crashes (https://bugs.ruby-lang.org/issues/18464). " \
@@ -202,7 +200,7 @@ module Datadog
202
200
  # ANNOYANCE - Only with Ractors
203
201
  # On all known versions of Ruby 3.x, due to https://bugs.ruby-lang.org/issues/19112, when a ractor gets
204
202
  # garbage collected, Ruby will disable all active tracepoints, which this feature internally relies on.
205
- elsif RUBY_VERSION.start_with?("3.")
203
+ elsif RubyVersion.is?(">= 3", "< 4")
206
204
  logger.debug(
207
205
  "Using Ractors may result in allocation profiling " \
208
206
  "stopping (https://bugs.ruby-lang.org/issues/19112). Note that this stop has no impact in your " \
@@ -220,7 +218,7 @@ module Datadog
220
218
 
221
219
  return false unless heap_profiling_enabled
222
220
 
223
- if RUBY_VERSION < "3.1"
221
+ if RubyVersion.is?("< 3.1")
224
222
  logger.warn(
225
223
  "Current Ruby version (#{RUBY_VERSION}) cannot support heap profiling due to VM limitations. " \
226
224
  "Please upgrade to Ruby >= 3.1 in order to use this feature. Heap profiling has been disabled."
@@ -262,7 +260,7 @@ module Datadog
262
260
  end
263
261
 
264
262
  if setting_value == false
265
- if RUBY_VERSION.start_with?("2.5.")
263
+ if RubyVersion.is?("< 2.6")
266
264
  logger.warn(
267
265
  'The profiling "no signals" workaround has been disabled via configuration on Ruby 2.5. ' \
268
266
  "This is not recommended " \
@@ -288,7 +286,7 @@ module Datadog
288
286
  # Setting is in auto mode. Let's probe to see if we should enable it:
289
287
 
290
288
  # We don't warn users in this situation because "upgrade your Ruby" is not a great warning
291
- return true if RUBY_VERSION.start_with?("2.5.")
289
+ return true if RubyVersion.is?("< 2.6")
292
290
 
293
291
  if Gem.loaded_specs["mysql2"] && incompatible_libmysqlclient_version?(settings, logger)
294
292
  logger.warn(
@@ -450,13 +448,13 @@ module Datadog
450
448
  end
451
449
 
452
450
  private_class_method def self.dir_interruption_workaround_enabled?(settings, no_signals_workaround_enabled)
453
- return false if no_signals_workaround_enabled || RUBY_VERSION >= "3.4"
451
+ return false if no_signals_workaround_enabled || RubyVersion.is?(">= 3.4")
454
452
 
455
453
  settings.profiling.advanced.dir_interruption_workaround_enabled
456
454
  end
457
455
 
458
456
  private_class_method def self.can_apply_exec_monkey_patch?(settings)
459
- return false if RUBY_VERSION < "2.7"
457
+ return false if RubyVersion.is?("< 2.7")
460
458
 
461
459
  # This file is 2.7+ only so we only require it here once we've checked the Ruby version
462
460
  require "datadog/profiling/ext/exec_monkey_patch"
@@ -465,7 +463,7 @@ module Datadog
465
463
  end
466
464
 
467
465
  private_class_method def self.enable_gvl_profiling?(settings, logger)
468
- RUBY_VERSION >= "3.2" && settings.profiling.advanced.gvl_enabled
466
+ RubyVersion.is?(">= 3.2") && settings.profiling.advanced.gvl_enabled
469
467
  end
470
468
  end
471
469
  end
@@ -28,7 +28,7 @@ module Datadog
28
28
  end
29
29
  end
30
30
 
31
- if RUBY_VERSION.start_with?("2.")
31
+ if RubyVersion.is?("< 3")
32
32
  # Monkey patches for Dir.singleton_class (Ruby 2 version). See DirMonkeyPatches above for more details.
33
33
  module DirClassMonkeyPatches
34
34
  # Steep: Workaround that defines args and block only for Ruby 2.x.
@@ -270,7 +270,7 @@ module Datadog
270
270
  end
271
271
  end
272
272
 
273
- if RUBY_VERSION.start_with?("2.")
273
+ if RubyVersion.is?("< 3")
274
274
  # Monkey patches for Dir (Ruby 2 version). See DirMonkeyPatches above for more details.
275
275
  module DirInstanceMonkeyPatches
276
276
  # Steep: Workaround that defines args and block only for Ruby 2.x.
@@ -300,7 +300,7 @@ module Datadog
300
300
  end
301
301
  end
302
302
 
303
- unless RUBY_VERSION.start_with?("2.5.") # This is Ruby 2.6+
303
+ if RubyVersion.is?(">= 2.6.0") # This method is Ruby 2.6+
304
304
  # See note on methods that yield above.
305
305
  def each_child(*args, &block)
306
306
  if block
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ # Compares the running Ruby against version requirements.
5
+ #
6
+ # Lexical comparisons against `RUBY_VERSION` are subtly wrong: `RUBY_VERSION < "3.2.3"` is also
7
+ # `true` on 3.2.10 and 3.2.11, since those sort *before* "3.2.3" as strings.
8
+ #
9
+ # @example
10
+ # RubyVersion.is?(">= 3.2", "< 3.2.3") # => true on Ruby 3.2.0, 3.2.1, 3.2.2 (NOT 3.2.3+, NOT 3.2.10+)
11
+ module RubyVersion
12
+ extend RubyVersion # steep currently needs this (instead of extend self or def self.is?) for the inline rbs to work
13
+
14
+ CURRENT_RUBY_VERSION = Gem::Version.new(RUBY_VERSION) #: ::Gem::Version
15
+ private_constant :CURRENT_RUBY_VERSION
16
+
17
+ # Returns `true` when the running Ruby satisfies ALL of the given requirements. Each uses the
18
+ # same syntax as a gem dependency (e.g. `">= 3.1"`, `">= 3.2"`, `"< 3.2.3"`).
19
+ #
20
+ # @rbs (*String requirements, ?ruby_version: ::Gem::Version) -> bool
21
+ def is?(*requirements, ruby_version: CURRENT_RUBY_VERSION)
22
+ Gem::Requirement.new(*requirements).satisfied_by?(ruby_version)
23
+ end
24
+ end
25
+ end