datadog 2.46.0 → 2.48.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 (286) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -2
  3. data/LICENSE-3rdparty.csv +1 -0
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +143 -86
  5. data/ext/datadog_profiling_native_extension/collectors_stack.h +9 -1
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +6 -1
  7. data/ext/datadog_profiling_native_extension/extconf.rb +7 -13
  8. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -3
  9. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +67 -14
  10. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +8 -1
  11. data/ext/datadog_profiling_native_extension/profiling.c +1 -1
  12. data/ext/libdatadog_api/extconf.rb +9 -7
  13. data/ext/libdatadog_api/trace_exporter.c +34 -7
  14. data/ext/libdatadog_extconf_helpers.rb +2 -2
  15. data/lib/datadog/ai_guard/component.rb +1 -1
  16. data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +1 -9
  17. data/lib/datadog/ai_guard/contrib/rails/patcher.rb +1 -1
  18. data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +1 -1
  19. data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +3 -3
  20. data/lib/datadog/ai_guard/evaluation/client.rb +3 -3
  21. data/lib/datadog/ai_guard/evaluation.rb +2 -2
  22. data/lib/datadog/ai_guard/http_client.rb +3 -2
  23. data/lib/datadog/ai_guard/redaction.rb +1 -1
  24. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +1 -1
  25. data/lib/datadog/appsec/assets/waf_rules/README.md +3 -3
  26. data/lib/datadog/appsec/assets/waf_rules/recommended.json +5366 -1517
  27. data/lib/datadog/appsec/assets/waf_rules/strict.json +2 -2
  28. data/lib/datadog/appsec/autoload.rb +1 -1
  29. data/lib/datadog/appsec/component.rb +1 -1
  30. data/lib/datadog/appsec/compressed_json.rb +1 -1
  31. data/lib/datadog/appsec/configuration.rb +3 -3
  32. data/lib/datadog/appsec/context.rb +1 -1
  33. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +11 -1
  34. data/lib/datadog/appsec/contrib/active_record/patcher.rb +12 -4
  35. data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +2 -2
  36. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +1 -1
  37. data/lib/datadog/appsec/contrib/devise/configuration.rb +1 -1
  38. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +1 -1
  39. data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +1 -1
  40. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +1 -1
  41. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +1 -1
  42. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +1 -1
  43. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +7 -7
  44. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
  45. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +3 -3
  46. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +5 -5
  47. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +31 -4
  48. data/lib/datadog/appsec/contrib/rails/patcher.rb +2 -2
  49. data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +8 -7
  50. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +1 -1
  51. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +17 -5
  52. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +1 -1
  53. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +8 -7
  54. data/lib/datadog/appsec/metrics/collector.rb +5 -5
  55. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +3 -3
  56. data/lib/datadog/appsec/monitor/gateway/watcher.rb +2 -2
  57. data/lib/datadog/appsec/processor/rule_loader.rb +1 -1
  58. data/lib/datadog/appsec/response.rb +1 -1
  59. data/lib/datadog/appsec/security_engine/engine.rb +8 -7
  60. data/lib/datadog/appsec/security_engine/runner.rb +2 -2
  61. data/lib/datadog/appsec/trace_keeper.rb +1 -1
  62. data/lib/datadog/appsec.rb +1 -1
  63. data/lib/datadog/core/configuration/agent_settings_resolver.rb +14 -13
  64. data/lib/datadog/core/configuration/agentless_settings_resolver.rb +2 -2
  65. data/lib/datadog/core/configuration/components.rb +12 -11
  66. data/lib/datadog/core/configuration/components_state.rb +8 -1
  67. data/lib/datadog/core/configuration/settings.rb +5 -2
  68. data/lib/datadog/core/configuration/supported_configurations.rb +3 -0
  69. data/lib/datadog/core/configuration.rb +2 -2
  70. data/lib/datadog/core/crashtracking/component.rb +1 -1
  71. data/lib/datadog/core/diagnostics/environment_logger.rb +1 -1
  72. data/lib/datadog/core/environment/cgroup.rb +2 -2
  73. data/lib/datadog/core/environment/container.rb +3 -3
  74. data/lib/datadog/core/metrics/client.rb +6 -6
  75. data/lib/datadog/core/process_discovery.rb +1 -1
  76. data/lib/datadog/core/remote/client.rb +44 -3
  77. data/lib/datadog/core/remote/dispatcher.rb +34 -3
  78. data/lib/datadog/core/remote/tie/tracing.rb +4 -3
  79. data/lib/datadog/core/remote/transport/http.rb +2 -2
  80. data/lib/datadog/core/runtime/metrics.rb +14 -14
  81. data/lib/datadog/core/semaphore.rb +3 -1
  82. data/lib/datadog/core/telemetry/event/app_started.rb +9 -9
  83. data/lib/datadog/core/telemetry/logger.rb +1 -1
  84. data/lib/datadog/core/telemetry/logging.rb +1 -1
  85. data/lib/datadog/core/telemetry/request.rb +9 -3
  86. data/lib/datadog/core/telemetry/transport/http.rb +2 -2
  87. data/lib/datadog/core/telemetry/worker.rb +1 -1
  88. data/lib/datadog/core/transport/http/adapters/net.rb +1 -1
  89. data/lib/datadog/core/transport/http/adapters/unix_socket.rb +1 -1
  90. data/lib/datadog/core/transport/http/builder.rb +1 -1
  91. data/lib/datadog/core/transport/http.rb +3 -3
  92. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +2 -2
  93. data/lib/datadog/core/workers/async.rb +1 -1
  94. data/lib/datadog/core/workers/runtime_metrics.rb +2 -2
  95. data/lib/datadog/data_streams/pathway_context.rb +1 -1
  96. data/lib/datadog/data_streams/processor.rb +48 -27
  97. data/lib/datadog/data_streams/transport/http.rb +1 -1
  98. data/lib/datadog/data_streams.rb +16 -0
  99. data/lib/datadog/di/capture_expression_evaluator.rb +48 -11
  100. data/lib/datadog/di/component.rb +73 -2
  101. data/lib/datadog/di/configuration.rb +19 -0
  102. data/lib/datadog/di/contrib/active_record.rb +1 -1
  103. data/lib/datadog/di/el/compiler.rb +4 -4
  104. data/lib/datadog/di/el/evaluator.rb +247 -43
  105. data/lib/datadog/di/el/expression.rb +36 -6
  106. data/lib/datadog/di/error.rb +16 -0
  107. data/lib/datadog/di/guardrails_telemetry/reason.rb +3 -2
  108. data/lib/datadog/di/instrumenter.rb +39 -10
  109. data/lib/datadog/di/probe_notification_builder.rb +58 -10
  110. data/lib/datadog/di/probe_notifier_worker.rb +1 -1
  111. data/lib/datadog/di/remote.rb +46 -2
  112. data/lib/datadog/di/serializer.rb +47 -13
  113. data/lib/datadog/di.rb +27 -0
  114. data/lib/datadog/error_tracking/component.rb +1 -1
  115. data/lib/datadog/error_tracking/configuration.rb +1 -1
  116. data/lib/datadog/kit/appsec/events/v2.rb +6 -6
  117. data/lib/datadog/kit/appsec/events.rb +1 -1
  118. data/lib/datadog/kit/enable_core_dumps.rb +1 -1
  119. data/lib/datadog/kit/identity.rb +1 -1
  120. data/lib/datadog/open_feature/activation.rb +26 -1
  121. data/lib/datadog/open_feature/agentless/configuration_source.rb +94 -15
  122. data/lib/datadog/open_feature/component.rb +22 -2
  123. data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +6 -0
  124. data/lib/datadog/open_feature/configuration.rb +10 -4
  125. data/lib/datadog/open_feature/evaluation_engine.rb +62 -4
  126. data/lib/datadog/open_feature/exposures/buffer.rb +4 -0
  127. data/lib/datadog/open_feature/exposures/reporter.rb +17 -4
  128. data/lib/datadog/open_feature/exposures/worker.rb +37 -6
  129. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +5 -5
  130. data/lib/datadog/open_feature/flag_evaluation/writer.rb +1 -1
  131. data/lib/datadog/open_feature/hooks/debug_logging_hook.rb +56 -0
  132. data/lib/datadog/open_feature/hooks/evaluation_telemetry_opt_out_hook.rb +25 -0
  133. data/lib/datadog/open_feature/hooks/span_enrichment_hook.rb +2 -2
  134. data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +1 -1
  135. data/lib/datadog/open_feature/native_evaluator.rb +1 -1
  136. data/lib/datadog/open_feature/noop_evaluator.rb +1 -1
  137. data/lib/datadog/open_feature/provider.rb +69 -12
  138. data/lib/datadog/open_feature/remote.rb +8 -1
  139. data/lib/datadog/open_feature/resolution_details.rb +2 -2
  140. data/lib/datadog/open_feature/transport.rb +6 -6
  141. data/lib/datadog/opentelemetry/api/context.rb +3 -3
  142. data/lib/datadog/opentelemetry/logs.rb +5 -5
  143. data/lib/datadog/opentelemetry/metrics.rb +3 -3
  144. data/lib/datadog/opentelemetry/sdk/propagator.rb +3 -3
  145. data/lib/datadog/opentelemetry/sdk/span_processor.rb +2 -2
  146. data/lib/datadog/opentelemetry/sdk/trace/span.rb +1 -1
  147. data/lib/datadog/profiling/collectors/code_provenance.rb +3 -3
  148. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +4 -4
  149. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  150. data/lib/datadog/profiling/collectors/info.rb +37 -6
  151. data/lib/datadog/profiling/collectors/thread_context.rb +1 -1
  152. data/lib/datadog/profiling/component.rb +41 -39
  153. data/lib/datadog/profiling/exporter.rb +4 -6
  154. data/lib/datadog/profiling/http_transport.rb +3 -3
  155. data/lib/datadog/profiling/profiler.rb +2 -2
  156. data/lib/datadog/profiling/scheduler.rb +2 -2
  157. data/lib/datadog/profiling/tag_builder.rb +1 -1
  158. data/lib/datadog/symbol_database/extractor.rb +8 -8
  159. data/lib/datadog/symbol_database/uploader.rb +1 -1
  160. data/lib/datadog/tracing/buffer.rb +3 -3
  161. data/lib/datadog/tracing/client_ip.rb +1 -1
  162. data/lib/datadog/tracing/component.rb +15 -10
  163. data/lib/datadog/tracing/configuration/dynamic/option.rb +2 -2
  164. data/lib/datadog/tracing/configuration/ext.rb +2 -0
  165. data/lib/datadog/tracing/configuration/settings.rb +14 -3
  166. data/lib/datadog/tracing/context.rb +1 -1
  167. data/lib/datadog/tracing/context_provider.rb +8 -0
  168. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +2 -2
  169. data/lib/datadog/tracing/contrib/active_job/data_streams.rb +27 -2
  170. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +2 -2
  171. data/lib/datadog/tracing/contrib/active_record/configuration/settings.rb +1 -1
  172. data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
  173. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +1 -1
  174. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +1 -1
  175. data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +2 -2
  176. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +2 -2
  177. data/lib/datadog/tracing/contrib/aws/configuration/settings.rb +1 -1
  178. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +1 -1
  179. data/lib/datadog/tracing/contrib/dalli/configuration/settings.rb +1 -1
  180. data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +1 -1
  181. data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +2 -2
  182. data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +1 -1
  183. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +3 -3
  184. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +1 -1
  185. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +17 -12
  186. data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +1 -1
  187. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +1 -1
  188. data/lib/datadog/tracing/contrib/excon/middleware.rb +16 -9
  189. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +1 -1
  190. data/lib/datadog/tracing/contrib/faraday/middleware.rb +14 -8
  191. data/lib/datadog/tracing/contrib/grape/endpoint.rb +4 -4
  192. data/lib/datadog/tracing/contrib/graphql/patcher.rb +1 -1
  193. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +3 -3
  194. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +1 -1
  195. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +2 -2
  196. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +1 -1
  197. data/lib/datadog/tracing/contrib/grpc.rb +1 -1
  198. data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +1 -1
  199. data/lib/datadog/tracing/contrib/hanami/plugin.rb +1 -1
  200. data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +1 -1
  201. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +1 -1
  202. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +1 -1
  203. data/lib/datadog/tracing/contrib/http/instrumentation.rb +21 -6
  204. data/lib/datadog/tracing/contrib/http.rb +1 -1
  205. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +1 -1
  206. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +14 -8
  207. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +1 -1
  208. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +14 -8
  209. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +2 -2
  210. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +2 -2
  211. data/lib/datadog/tracing/contrib/karafka/monitor.rb +1 -1
  212. data/lib/datadog/tracing/contrib/karafka/patcher.rb +1 -1
  213. data/lib/datadog/tracing/contrib/karafka.rb +1 -1
  214. data/lib/datadog/tracing/contrib/lograge/patcher.rb +1 -1
  215. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +1 -1
  216. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +1 -1
  217. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +1 -1
  218. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +1 -1
  219. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +3 -3
  220. data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +2 -2
  221. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +3 -3
  222. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +1 -1
  223. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -4
  224. data/lib/datadog/tracing/contrib/presto/configuration/settings.rb +1 -1
  225. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +4 -4
  226. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +1 -1
  227. data/lib/datadog/tracing/contrib/que/tracer.rb +1 -1
  228. data/lib/datadog/tracing/contrib/racecar/configuration/settings.rb +1 -1
  229. data/lib/datadog/tracing/contrib/racecar/events/batch.rb +1 -1
  230. data/lib/datadog/tracing/contrib/racecar/events/message.rb +1 -1
  231. data/lib/datadog/tracing/contrib/rack/middlewares.rb +3 -3
  232. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  233. data/lib/datadog/tracing/contrib/rails/framework.rb +7 -7
  234. data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
  235. data/lib/datadog/tracing/contrib/rails/runner.rb +4 -4
  236. data/lib/datadog/tracing/contrib/redis/configuration/settings.rb +1 -1
  237. data/lib/datadog/tracing/contrib/redis/quantize.rb +1 -1
  238. data/lib/datadog/tracing/contrib/redis/tags.rb +1 -1
  239. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +1 -1
  240. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +17 -10
  241. data/lib/datadog/tracing/contrib/sequel/database.rb +1 -1
  242. data/lib/datadog/tracing/contrib/sequel/dataset.rb +1 -1
  243. data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +1 -1
  244. data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +2 -2
  245. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +2 -2
  246. data/lib/datadog/tracing/contrib/sidekiq.rb +1 -1
  247. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +1 -1
  248. data/lib/datadog/tracing/contrib/status_range_env_parser.rb +1 -1
  249. data/lib/datadog/tracing/contrib/sucker_punch/instrumentation.rb +1 -1
  250. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +1 -1
  251. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +3 -3
  252. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +102 -0
  253. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +2 -2
  254. data/lib/datadog/tracing/contrib/waterdrop.rb +1 -1
  255. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  256. data/lib/datadog/tracing/distributed/b3_multi.rb +1 -1
  257. data/lib/datadog/tracing/distributed/b3_single.rb +1 -1
  258. data/lib/datadog/tracing/distributed/baggage.rb +3 -3
  259. data/lib/datadog/tracing/distributed/datadog.rb +3 -3
  260. data/lib/datadog/tracing/distributed/propagation.rb +7 -7
  261. data/lib/datadog/tracing/distributed/trace_context.rb +1 -1
  262. data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +1 -1
  263. data/lib/datadog/tracing/otel_thread_context.rb +3 -3
  264. data/lib/datadog/tracing/pipeline.rb +1 -1
  265. data/lib/datadog/tracing/remote.rb +50 -19
  266. data/lib/datadog/tracing/sampling/priority_sampler.rb +1 -1
  267. data/lib/datadog/tracing/sampling/rule.rb +1 -1
  268. data/lib/datadog/tracing/sampling/rule_sampler.rb +1 -1
  269. data/lib/datadog/tracing/sampling/span/rule_parser.rb +2 -2
  270. data/lib/datadog/tracing/span_operation.rb +1 -1
  271. data/lib/datadog/tracing/trace_digest.rb +1 -1
  272. data/lib/datadog/tracing/trace_operation.rb +6 -6
  273. data/lib/datadog/tracing/tracer.rb +6 -6
  274. data/lib/datadog/tracing/transport/http/telemetry.rb +1 -1
  275. data/lib/datadog/tracing/transport/http.rb +2 -2
  276. data/lib/datadog/tracing/transport/io.rb +1 -1
  277. data/lib/datadog/tracing/transport/native.rb +3 -3
  278. data/lib/datadog/tracing/transport/statistics.rb +2 -2
  279. data/lib/datadog/tracing/transport/telemetry.rb +3 -1
  280. data/lib/datadog/tracing/transport/trace_formatter.rb +12 -12
  281. data/lib/datadog/tracing/transport/traces.rb +1 -1
  282. data/lib/datadog/tracing/utils.rb +1 -1
  283. data/lib/datadog/tracing/workers.rb +1 -1
  284. data/lib/datadog/version.rb +1 -1
  285. metadata +9 -8
  286. data/lib/datadog/appsec/utils/hash_coercion.rb +0 -23
@@ -7,6 +7,7 @@ require_relative "evaluation_failure"
7
7
  require_relative "fatal_exceptions"
8
8
  require_relative "guardrails_telemetry"
9
9
  require_relative "../core/utils/time"
10
+ require_relative "el"
10
11
 
11
12
  module Datadog
12
13
  module DI
@@ -16,7 +17,7 @@ module Datadog
16
17
  # @param settings [Datadog::Core::Configuration::Settings] tracer settings
17
18
  # @param serializer [Serializer] serializer for captured expression values
18
19
  # @param logger [DI::Logger] logger for evaluation failure diagnostics
19
- # @param guardrails_telemetry [GuardrailsTelemetry] emitter for the canonical capture-timeout skip metric
20
+ # @param guardrails_telemetry [GuardrailsTelemetry] emitter for the canonical evaluation-timeout skip metric
20
21
  # @param telemetry [Datadog::Core::Telemetry::Component, nil] component evaluation failures are reported through
21
22
  # @return [void]
22
23
  def initialize(settings:, serializer:, logger:, guardrails_telemetry:, telemetry: nil)
@@ -42,8 +43,13 @@ module Datadog
42
43
  # Evaluates each of the probe's capture expressions against the given
43
44
  # context, serializing the captured values under one capture deadline
44
45
  # 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.
46
+ # Each expression resolves its own evaluation deadline immediately
47
+ # before its evaluation and is evaluated under the earlier of that
48
+ # deadline and the capture deadline, so the capture expressions
49
+ # together stay within the capture budget. An expression the capture
50
+ # budget stops is reported as not captured; an expression that fails
51
+ # to evaluate or serialize otherwise contributes an entry to the
52
+ # returned errors.
47
53
  #
48
54
  # @param probe [Probe] the probe whose capture expressions are evaluated
49
55
  # @param context [Context] evaluation context the expressions are evaluated against
@@ -59,18 +65,14 @@ module Datadog
59
65
  probe.capture_expressions.each do |capture_expression|
60
66
  name = capture_expression.name
61
67
 
62
- if Core::Utils::Time.get_time >= deadline
63
- output[name] = {notCapturedReason: "timeout"}
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
- )
68
+ if capture_deadline_passed?(deadline)
69
+ record_capture_timeout(output, name, probe)
69
70
  next
70
71
  end
71
72
 
72
73
  begin
73
- value = capture_expression.expr.evaluate(context)
74
+ evaluation_deadline = [serializer.evaluation_deadline, deadline].min
75
+ value = capture_expression.expr.evaluate(context, evaluation_deadline: evaluation_deadline)
74
76
  limits = CaptureLimits.resolve(
75
77
  expr_limits: capture_expression.limits,
76
78
  probe: probe,
@@ -86,6 +88,14 @@ module Datadog
86
88
  )
87
89
  rescue Exception => exc # standard:disable Lint/RescueException
88
90
  Datadog::DI.reraise_if_fatal(exc)
91
+ if exc.is_a?(DI::Error::EvaluationTimeout) && capture_deadline_passed?(deadline)
92
+ record_capture_timeout(output, name, probe)
93
+ next
94
+ end
95
+ DI.report_evaluation_timeout(
96
+ telemetry: telemetry, guardrails_telemetry: guardrails_telemetry, logger: logger,
97
+ probe: probe, kind: "capture expression", exc: exc,
98
+ )
89
99
  evaluation_errors << EvaluationFailure.new(expr: name, message: "#{exc.class}: #{exc.message}")
90
100
  logger.debug do
91
101
  "di: probe #{probe.id}: capture expression #{name}: evaluation failed: #{exc.class}: #{exc.message}"
@@ -96,6 +106,33 @@ module Datadog
96
106
 
97
107
  Result.new(serialized_values: output, errors: evaluation_errors)
98
108
  end
109
+
110
+ private
111
+
112
+ # Returns whether the thread CPU clock has reached +capture_deadline+.
113
+ #
114
+ # @param capture_deadline [Float] the capture deadline, as
115
+ # CLOCK_THREAD_CPUTIME_ID float seconds.
116
+ # @return [Boolean]
117
+ def capture_deadline_passed?(capture_deadline)
118
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second) >= capture_deadline
119
+ end
120
+
121
+ # Reports capture expression +name+ as not captured because the
122
+ # capture budget ran out.
123
+ #
124
+ # @param output [Hash{String => Object}] serialized values by name.
125
+ # @param name [String] the capture expression's name.
126
+ # @param probe [Probe] the probe whose capture expression is stopped.
127
+ # @return [void]
128
+ def record_capture_timeout(output, name, probe)
129
+ output[name] = {notCapturedReason: "timeout"}
130
+ telemetry&.inc(DI::TELEMETRY_NAMESPACE, "capture_expressions_skipped_by_timeout", 1)
131
+ guardrails_telemetry.skipped(
132
+ reason: GuardrailsTelemetry::Reason::EVALUATION_TIMEOUT,
133
+ probe: probe,
134
+ )
135
+ end
99
136
  end
100
137
  end
101
138
  end
@@ -100,10 +100,11 @@ module Datadog
100
100
  )
101
101
  @instrumenter = Instrumenter.new(settings, serializer, logger, code_tracker: code_tracker, telemetry: telemetry,
102
102
  guardrails_telemetry: guardrails_telemetry,
103
- capture_expression_evaluator: capture_expression_evaluator,)
103
+ capture_expression_evaluator: capture_expression_evaluator)
104
104
  @probe_repository = ProbeRepository.new
105
105
  @probe_notification_builder = ProbeNotificationBuilder.new(settings, serializer, logger,
106
- capture_expression_evaluator: capture_expression_evaluator,)
106
+ capture_expression_evaluator: capture_expression_evaluator, guardrails_telemetry: guardrails_telemetry,
107
+ telemetry: telemetry)
107
108
  @probe_notifier_worker = ProbeNotifierWorker.new(
108
109
  settings, logger,
109
110
  agent_settings: agent_settings,
@@ -122,6 +123,7 @@ module Datadog
122
123
  # cannot race a foreground start! with a background stop!.
123
124
  @lifecycle_mutex = Mutex.new
124
125
  @started = false
126
+ @rc_enablement_signal_paths = []
125
127
  end
126
128
 
127
129
  attr_reader :settings
@@ -201,6 +203,75 @@ module Datadog
201
203
  @started
202
204
  end
203
205
 
206
+ # Whether the component is started while the customer left
207
+ # DD_DYNAMIC_INSTRUMENTATION_ENABLED at its default, the state produced
208
+ # by remote-configuration enablement.
209
+ #
210
+ # An explicit `enabled = false` takes precedence over a prior RC
211
+ # enablement: once the customer sets the option, this returns false
212
+ # even while the component is still started.
213
+ #
214
+ # @return [Boolean]
215
+ def implicitly_enabled?
216
+ started? && settings.dynamic_instrumentation.using_default?(:enabled)
217
+ end
218
+
219
+ # Whether an APM_TRACING merge that omitted dynamic_instrumentation_enabled
220
+ # withdraws this component's RC enable signal, so the component must stop.
221
+ #
222
+ # Returns true when the component is implicitly enabled and no unparseable
223
+ # content can be the signal carrier. A content at a recorded signal path can be
224
+ # the carrier. When no paths are recorded, every unparseable content can be the
225
+ # carrier.
226
+ #
227
+ # @param unparseable_contents [Array<Core::Remote::Configuration::Content>] the APM_TRACING contents that failed to parse at this merge
228
+ # @return [Boolean]
229
+ def rc_enablement_signal_withdrawn?(unparseable_contents)
230
+ return false unless implicitly_enabled?
231
+
232
+ recorded_signal_paths = rc_enablement_signal_paths
233
+ signal_source_unparseable =
234
+ if recorded_signal_paths.empty?
235
+ unparseable_contents.any?
236
+ else
237
+ unparseable_contents.any? { |content| recorded_signal_paths.include?(content.path) }
238
+ end
239
+ if signal_source_unparseable
240
+ if recorded_signal_paths.empty?
241
+ logger.trace do
242
+ "APM_TRACING RC: dynamic_instrumentation_enabled absent while an APM_TRACING config " \
243
+ "failed to parse; the enable signal carrier is unknown (no signal paths recorded), " \
244
+ "keeping the DI state"
245
+ end
246
+ else
247
+ logger.trace do
248
+ "APM_TRACING RC: dynamic_instrumentation_enabled absent while the config that carried " \
249
+ "the enable signal failed to parse; keeping the DI state"
250
+ end
251
+ end
252
+ false
253
+ else
254
+ logger.trace { "APM_TRACING RC: dynamic_instrumentation_enabled withdrawn; treating as false" }
255
+ true
256
+ end
257
+ end
258
+
259
+ # The repository paths of the APM_TRACING contents that supplied the
260
+ # dynamic_instrumentation_enabled value at the last merge where the field was
261
+ # present. The component holds the paths so that they outlive the RC client.
262
+ #
263
+ # @return [Array<Core::Remote::Configuration::Path>]
264
+ attr_reader :rc_enablement_signal_paths
265
+
266
+ # Replaces the recorded enable-signal carrier paths.
267
+ #
268
+ # @param paths [Array<Core::Remote::Configuration::Path>] the carrier paths, empty when the merged field was absent
269
+ # @return [void]
270
+ def record_rc_enablement_signal_paths(paths)
271
+ @rc_enablement_signal_paths = paths.dup
272
+ nil
273
+ end
274
+
204
275
  # Shuts down dynamic instrumentation permanently.
205
276
  #
206
277
  # Removes all code hooks and stops background threads.
@@ -169,6 +169,25 @@ module Datadog
169
169
  end
170
170
  end
171
171
 
172
+ # Maximum thread CPU time, in milliseconds, allowed for
173
+ # evaluating expressions. The env var key is shared with
174
+ # other tracers, which measure wall time for it.
175
+ #
176
+ # @return [Integer]
177
+ option :max_time_to_evaluate_ms do |o|
178
+ o.type :int
179
+ o.default 50
180
+ o.env "DD_DYNAMIC_INSTRUMENTATION_EVALUATION_TIMEOUT_MS"
181
+ o.setter do |value, _old|
182
+ if value < 0
183
+ raise ArgumentError,
184
+ "The setting `dynamic_instrumentation.max_time_to_evaluate_ms` must not be negative, " \
185
+ "but `#{value.inspect}` was provided."
186
+ end
187
+ value
188
+ end
189
+ end
190
+
172
191
  # Settings in the 'internal' group are for internal Datadog
173
192
  # use only, and are needed to test dynamic instrumentation or
174
193
  # experiment with features not released to customers.
@@ -9,7 +9,7 @@ Datadog::DI::Serializer.register(
9
9
  # An alternative could have been to make DI specifically provide lookup
10
10
  # logic for "instances of classes derived from X", but a condition Proc
11
11
  # is more universal.
12
- condition: lambda { |value| ActiveRecord::Base === value }
12
+ condition: lambda { |value| ActiveRecord::Base === value },
13
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.
@@ -133,7 +133,7 @@ module Datadog
133
133
  raise DI::Error::BadVariableName, "Bad variable name: #{target}"
134
134
  end
135
135
  method_name = (($1 == "@") ? "iref" : "ref")
136
- "#{method_name}('#{target}')"
136
+ "#{method_name}(context, '#{target}')"
137
137
  end
138
138
  when *SINGLE_ARG_METHODS
139
139
  method_name = op.gsub(/[A-Z]/) { |m| "_#{m.downcase}" }
@@ -146,10 +146,10 @@ module Datadog
146
146
  if String === second
147
147
  # Match against a literal regular expression (string).
148
148
  index = precompile_regexp(second, regexps)
149
- "matches_compiled(#{compile_partial(first, regexps)}, #{index})"
149
+ "matches_compiled(#{compile_partial(first, regexps)}, #{index}, evaluation_deadline)"
150
150
  else
151
151
  # Match against a (complex) expression.
152
- "matches(#{compile_partial(first, regexps)}, (#{compile_partial(second, regexps)}))"
152
+ "matches(#{compile_partial(first, regexps)}, (#{compile_partial(second, regexps)}), evaluation_deadline)"
153
153
  end
154
154
  when *TWO_ARG_METHODS
155
155
  method_name = op.gsub(/[A-Z]/) { |m| "_#{m.downcase}" }
@@ -185,7 +185,7 @@ module Datadog
185
185
  unless Array === target && target.length == 2
186
186
  raise DI::Error::InvalidExpression, "Improper #{op} syntax"
187
187
  end
188
- "#{op}(#{compile_partial(target.first, regexps)}) { |current_item, current_key, current_value| #{compile_partial(target.last, regexps)} }"
188
+ "#{op}(#{compile_partial(target.first, regexps)}, evaluation_deadline) { |current_item, current_key, current_value| #{compile_partial(target.last, regexps)} }"
189
189
  else
190
190
  raise DI::Error::InvalidExpression, "Unknown operation: #{op}"
191
191
  end
@@ -12,8 +12,29 @@ module Datadog
12
12
  # @api private
13
13
  class Evaluator
14
14
  # Maximum wall-clock time allowed for evaluating a single `matches`
15
- # operator.
16
- MATCHES_TIMEOUT_SECONDS = 0.5
15
+ # operator. Wall clock, while the evaluation budget is thread CPU
16
+ # time: the regexp engine's `timeout:` keyword and Timeout.timeout
17
+ # offer no CPU-time mode.
18
+ MATCHES_TIMEOUT_SECONDS = 0.05
19
+
20
+ MATCHES_TIMEOUT_MESSAGE = "matches operator timeout"
21
+
22
+ # Number of collection items processed between cooperative
23
+ # evaluation-deadline checks; evaluation aborts within one
24
+ # interval's worth of items past the deadline. A `matches`
25
+ # invocation can spend MATCHES_TIMEOUT_SECONDS of wall time, so
26
+ # #matches and #matches_compiled check the deadline before each
27
+ # match; a predicate that runs `matches` per item therefore
28
+ # adds at most the one match in flight when the deadline
29
+ # passes to the overshoot.
30
+ EVALUATION_DEADLINE_CHECK_INTERVAL = 64
31
+
32
+ # Built-in Array#each and Hash#each, resolved once at load and
33
+ # bound per invocation: the collection operators' iteration then
34
+ # dispatches no customer each override during probe evaluation.
35
+ ARRAY_EACH = Array.instance_method(:each)
36
+ HASH_EACH = Hash.instance_method(:each)
37
+ private_constant :ARRAY_EACH, :HASH_EACH
17
38
 
18
39
  # @param regexps [Array<Regexp>] Regexps precompiled from literal
19
40
  # `matches` patterns, looked up by index by #matches_compiled.
@@ -25,12 +46,110 @@ module Datadog
25
46
 
26
47
  attr_reader :regexps
27
48
 
28
- def ref(var)
29
- @context.fetch(var)
49
+ # Raises DI::Error::EvaluationTimeout when the evaluation deadline
50
+ # has been exceeded, aborting evaluation cooperatively.
51
+ #
52
+ # @param evaluation_deadline [Float] invocation-local evaluation
53
+ # deadline, passed from the compiled #evaluate into the operator
54
+ # calls.
55
+ # @return [void]
56
+ # @raise [DI::Error::EvaluationTimeout] the deadline has been exceeded.
57
+ def raise_if_evaluation_deadline_exceeded(evaluation_deadline)
58
+ if self.class.evaluation_deadline_exceeded?(evaluation_deadline)
59
+ raise DI::Error::EvaluationTimeout
60
+ end
61
+ end
62
+ private :raise_if_evaluation_deadline_exceeded
63
+
64
+ # Raises DI::Error::EvaluationTimeout when +item_index+ falls on
65
+ # a cooperative check boundary and the evaluation deadline has
66
+ # been exceeded. The thread CPU clock is read only at every
67
+ # EVALUATION_DEADLINE_CHECK_INTERVAL-th item, so an already-exceeded
68
+ # deadline raises at the first boundary at or after it passes.
69
+ #
70
+ # @param item_index [Integer] zero-based index of the collection
71
+ # element being processed.
72
+ # @param evaluation_deadline [Float] invocation-local evaluation
73
+ # deadline, passed from the compiled #evaluate into the operator
74
+ # calls.
75
+ # @return [void]
76
+ # @raise [DI::Error::EvaluationTimeout] a check boundary was reached
77
+ # with the deadline exceeded.
78
+ def raise_if_deadline_check_due_and_exceeded(item_index, evaluation_deadline)
79
+ if (item_index % EVALUATION_DEADLINE_CHECK_INTERVAL).zero?
80
+ raise_if_evaluation_deadline_exceeded(evaluation_deadline)
81
+ end
82
+ end
83
+ private :raise_if_deadline_check_due_and_exceeded
84
+
85
+ # Yields the evaluation values for one collection element — the
86
+ # item alone for an Array element, or the [key, value] pair as
87
+ # @it together with the @key and the @value for a Hash element —
88
+ # iterating through the built-in iteration methods bound per
89
+ # invocation and performing the interval-gated evaluation-deadline
90
+ # check and its counter advance between elements.
91
+ #
92
+ # @param collection [Array, Hash] collection whose elements the
93
+ # block receives.
94
+ # @param evaluation_deadline [Float] invocation-local evaluation deadline.
95
+ # @param operator_name [String] name of the invoking collection
96
+ # operator, reported in the bad-collection-type error.
97
+ # @yield [current_item, current_key, current_value] evaluation
98
+ # values for one element.
99
+ # @return [void]
100
+ # @raise [DI::Error::EvaluationTimeout] the evaluation deadline was
101
+ # exceeded mid-iteration.
102
+ # @raise [DI::Error::ExpressionEvaluationError] +collection+ is
103
+ # not an Array or a Hash.
104
+ def each_collection_item(collection, evaluation_deadline, operator_name)
105
+ case collection
106
+ when Array
107
+ i = 0
108
+ ARRAY_EACH.bind(collection).call do |item|
109
+ raise_if_deadline_check_due_and_exceeded(i, evaluation_deadline)
110
+ i += 1
111
+ yield item, nil, nil
112
+ end
113
+ when Hash
114
+ i = 0
115
+ HASH_EACH.bind(collection).call do |key, value|
116
+ raise_if_deadline_check_due_and_exceeded(i, evaluation_deadline)
117
+ i += 1
118
+ yield [key, value], key, value
119
+ end
120
+ else
121
+ raise DI::Error::ExpressionEvaluationError, "Bad collection type for #{operator_name}: #{collection.class}"
122
+ end
123
+ nil
124
+ end
125
+ private :each_collection_item
126
+
127
+ # Returns whether the thread CPU clock has passed +evaluation_deadline+.
128
+ #
129
+ # @param evaluation_deadline [Float] the deadline to check.
130
+ # @return [Boolean] whether the deadline has been exceeded.
131
+ def self.evaluation_deadline_exceeded?(evaluation_deadline)
132
+ Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second) >= evaluation_deadline
133
+ end
134
+
135
+ # Resolves the value of the local variable or argument +var+ from
136
+ # the evaluation +context+.
137
+ #
138
+ # @param context [Context] context holding the evaluation locals.
139
+ # @param var [Symbol, String] name of the local variable or argument.
140
+ # @return [Object] value of the local variable or argument.
141
+ def ref(context, var)
142
+ context.fetch(var)
30
143
  end
31
144
 
32
- def iref(var)
33
- @context.fetch_ivar(var)
145
+ # Resolves the value of the instance variable +var+ from the
146
+ # receiver of the evaluation +context+.
147
+ #
148
+ # @param context [Context] context holding the evaluation receiver.
149
+ # @param var [Symbol, String] name of the instance variable.
150
+ # @return [Object] value of the instance variable.
151
+ def iref(context, var)
152
+ context.fetch_ivar(var)
34
153
  end
35
154
 
36
155
  def len(var, var_name)
@@ -92,10 +211,14 @@ module Datadog
92
211
  #
93
212
  # @param haystack [String] string to match against.
94
213
  # @param needle [String] regexp source.
214
+ # @param evaluation_deadline [Float] invocation-local evaluation
215
+ # deadline.
95
216
  # @return [Boolean] whether the haystack matches the regexp.
96
- # @raise [Regexp::TimeoutError] (Ruby 3.2+) regexp engine exceeded MATCHES_TIMEOUT_SECONDS.
97
- # @raise [Timeout::Error] (Ruby < 3.2) Timeout.timeout fired.
98
- def matches(haystack, needle)
217
+ # @raise [DI::Error::EvaluationTimeout] the evaluation deadline was
218
+ # exceeded before the match started, or the match exceeded
219
+ # MATCHES_TIMEOUT_SECONDS.
220
+ def matches(haystack, needle, evaluation_deadline)
221
+ raise_if_evaluation_deadline_exceeded(evaluation_deadline)
99
222
  bounded_match?(Evaluator.compile_regexp(needle), haystack)
100
223
  end
101
224
 
@@ -104,10 +227,14 @@ module Datadog
104
227
  #
105
228
  # @param haystack [String] string to match against.
106
229
  # @param index [Integer] position of the precompiled Regexp in +regexps+.
230
+ # @param evaluation_deadline [Float] invocation-local evaluation
231
+ # deadline.
107
232
  # @return [Boolean] whether the haystack matches the regexp.
108
- # @raise [Regexp::TimeoutError] (Ruby 3.2+) regexp engine exceeded MATCHES_TIMEOUT_SECONDS.
109
- # @raise [Timeout::Error] (Ruby < 3.2) Timeout.timeout fired.
110
- def matches_compiled(haystack, index)
233
+ # @raise [DI::Error::EvaluationTimeout] the evaluation deadline was
234
+ # exceeded before the match started, or the match exceeded
235
+ # MATCHES_TIMEOUT_SECONDS.
236
+ def matches_compiled(haystack, index, evaluation_deadline)
237
+ raise_if_evaluation_deadline_exceeded(evaluation_deadline)
111
238
  bounded_match?(regexps.fetch(index), haystack)
112
239
  end
113
240
 
@@ -117,18 +244,32 @@ module Datadog
117
244
  # @param re [Regexp] regexp to match against.
118
245
  # @param haystack [String] string to match against.
119
246
  # @return [Boolean] whether the haystack matches the regexp.
247
+ # @raise [DI::Error::EvaluationTimeout] the match exceeded
248
+ # MATCHES_TIMEOUT_SECONDS.
120
249
  if Datadog::RubyVersion.is?(">= 3.2")
121
250
  def bounded_match?(re, haystack)
122
251
  # Uses Regexp#match? rather than =~ so that the
123
252
  # thread-local match data ($~, $1, ...) is not mutated as a side
124
253
  # effect of DI expression evaluation.
125
254
  re.match?(haystack)
255
+ rescue Regexp::TimeoutError
256
+ raise DI::Error::EvaluationTimeout.new(message: MATCHES_TIMEOUT_MESSAGE)
126
257
  end
127
258
  else
259
+ # Raised by Timeout.timeout in the matching thread. A dedicated
260
+ # class because Timeout.timeout passes a positional message,
261
+ # which EvaluationTimeout does not accept, and because rescuing
262
+ # Timeout::Error would also catch an enclosing customer timeout.
263
+ class MatchTimeoutSignal < StandardError
264
+ end
265
+ private_constant :MatchTimeoutSignal
266
+
128
267
  def bounded_match?(re, haystack)
129
- Timeout.timeout(MATCHES_TIMEOUT_SECONDS) do
268
+ Timeout.timeout(MATCHES_TIMEOUT_SECONDS, MatchTimeoutSignal) do
130
269
  re.match?(haystack)
131
270
  end
271
+ rescue MatchTimeoutSignal
272
+ raise DI::Error::EvaluationTimeout.new(message: MATCHES_TIMEOUT_MESSAGE)
132
273
  end
133
274
  end
134
275
  private :bounded_match?
@@ -171,50 +312,113 @@ module Datadog
171
312
  String === haystack && haystack.end_with?(needle)
172
313
  end
173
314
 
174
- def all(collection, &block)
175
- case collection
176
- when Array
177
- collection.all? do |item|
178
- block.call(item)
179
- end
180
- when Hash
181
- # For hashes, the expression language has both @it and
182
- # @key/@value. Manufacture @it from the key and value.
183
- collection.all? do |key, value|
184
- block.call([key, value], key, value)
315
+ # Returns true when +block+ returns a truthy value for every element
316
+ # of +collection+.
317
+ #
318
+ # @param collection [Array, Hash] collection to test.
319
+ # @param evaluation_deadline [Float] invocation-local evaluation deadline.
320
+ # @return [Boolean] whether the block is satisfied by every element.
321
+ # @raise [DI::Error::EvaluationTimeout] the evaluation deadline was
322
+ # exceeded mid-iteration.
323
+ # @raise [DI::Error::ExpressionEvaluationError] +collection+ is not
324
+ # an Array or a Hash.
325
+ def all(collection, evaluation_deadline, &block)
326
+ result = true
327
+ each_collection_item(collection, evaluation_deadline, "all") do |current_item, current_key, current_value|
328
+ unless block.call(current_item, current_key, current_value)
329
+ result = false
330
+ break
185
331
  end
186
- else
187
- raise DI::Error::ExpressionEvaluationError, "Bad collection type for all: #{collection.class}"
188
332
  end
333
+ result
189
334
  end
190
335
 
191
- def any(collection, &block)
192
- case collection
193
- when Array
194
- collection.any? do |item|
195
- block.call(item)
336
+ # Returns true when +block+ returns a truthy value for any element
337
+ # of +collection+.
338
+ #
339
+ # @param collection [Array, Hash] collection to test.
340
+ # @param evaluation_deadline [Float] invocation-local evaluation deadline.
341
+ # @return [Boolean] whether the block is satisfied by any element.
342
+ # @raise [DI::Error::EvaluationTimeout] the evaluation deadline was
343
+ # exceeded mid-iteration.
344
+ # @raise [DI::Error::ExpressionEvaluationError] +collection+ is not
345
+ # an Array or a Hash.
346
+ def any(collection, evaluation_deadline, &block)
347
+ result = false
348
+ each_collection_item(collection, evaluation_deadline, "any") do |current_item, current_key, current_value|
349
+ if block.call(current_item, current_key, current_value)
350
+ result = true
351
+ break
196
352
  end
197
- when Hash
198
- collection.any? do |key, value|
199
- # For hashes, the expression language has both @it and
200
- # @key/@value. Manufacture @it from the key and value.
353
+ end
354
+ result
355
+ end
356
+
357
+ # Returns the key-value pairs of the Hash +collection+ for which
358
+ # +block+ returns a truthy value.
359
+ #
360
+ # Ruby 3.1 changed Hash#select to reuse the stored key hashes
361
+ # instead of invoking the keys' hash methods and to keep a
362
+ # compare_by_identity hash's distinct-but-eql? keys and identity
363
+ # flag, so this Ruby filters through the built-in Hash#select
364
+ # bound per invocation. Earlier Rubies invoke the keys' hash
365
+ # methods and collapse identity hashes, so they keep the manual
366
+ # iteration through #each_collection_item, preserving the source
367
+ # hash's identity mode in the result.
368
+ if Datadog::RubyVersion.is?(">= 3.1")
369
+ # Built-in Hash#select, resolved once at load and bound per
370
+ # invocation: #filter's Hash iteration then dispatches no
371
+ # customer select override during probe evaluation.
372
+ HASH_SELECT = Hash.instance_method(:select)
373
+ private_constant :HASH_SELECT
374
+
375
+ def filter_hash(collection, evaluation_deadline, &block)
376
+ i = 0
377
+ HASH_SELECT.bind(collection).call do |key, value|
378
+ raise_if_deadline_check_due_and_exceeded(i, evaluation_deadline)
379
+ i += 1
201
380
  block.call([key, value], key, value)
381
+ end.to_h
382
+ end
383
+ else
384
+ # Core identity-mode check, resolved once at load and bound per
385
+ # invocation so that #filter dispatches no customer
386
+ # compare_by_identity? override during probe evaluation.
387
+ IDENTITY_MODE_CHECK = Hash.instance_method(:compare_by_identity?)
388
+ private_constant :IDENTITY_MODE_CHECK
389
+
390
+ def filter_hash(collection, evaluation_deadline, &block)
391
+ result = {}
392
+ result.compare_by_identity if IDENTITY_MODE_CHECK.bind(collection).call
393
+ each_collection_item(collection, evaluation_deadline, "filter") do |_current_item, current_key, current_value|
394
+ result[current_key] = current_value if block.call([current_key, current_value], current_key, current_value)
202
395
  end
203
- else
204
- raise DI::Error::ExpressionEvaluationError, "Bad collection type for any: #{collection.class}"
396
+ result
205
397
  end
206
398
  end
399
+ private :filter_hash
207
400
 
208
- def filter(collection, &block)
401
+ # Returns the elements of +collection+ for which +block+ returns a
402
+ # truthy value: the selected items of an Array collection, and a
403
+ # Hash of the selected key-value pairs of a Hash collection.
404
+ #
405
+ # @param collection [Array, Hash] collection to filter.
406
+ # @param evaluation_deadline [Float] invocation-local evaluation deadline.
407
+ # @return [Array, Hash] the selected elements.
408
+ # @raise [DI::Error::EvaluationTimeout] the evaluation deadline was
409
+ # exceeded mid-iteration.
410
+ # @raise [DI::Error::ExpressionEvaluationError] +collection+ is not
411
+ # an Array or a Hash.
412
+ def filter(collection, evaluation_deadline, &block)
209
413
  case collection
210
414
  when Array
211
- collection.select do |item|
212
- block.call(item)
415
+ result = []
416
+ each_collection_item(collection, evaluation_deadline, "filter") do |current_item, current_key, current_value|
417
+ result << current_item if block.call(current_item, current_key, current_value)
213
418
  end
419
+ result
214
420
  when Hash
215
- collection.select do |key, value|
216
- block.call([key, value], key, value)
217
- end.to_h
421
+ filter_hash(collection, evaluation_deadline, &block)
218
422
  else
219
423
  raise DI::Error::ExpressionEvaluationError, "Bad collection type for filter: #{collection.class}"
220
424
  end