datadog 2.20.0 → 2.26.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 (310) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +212 -1
  3. data/README.md +0 -1
  4. data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
  5. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +93 -23
  6. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  7. data/ext/datadog_profiling_native_extension/collectors_stack.c +21 -5
  8. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
  9. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +1 -1
  10. data/ext/datadog_profiling_native_extension/extconf.rb +9 -4
  11. data/ext/datadog_profiling_native_extension/heap_recorder.c +1 -1
  12. data/ext/datadog_profiling_native_extension/http_transport.c +1 -0
  13. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +12 -0
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +4 -0
  15. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  16. data/ext/libdatadog_api/datadog_ruby_common.h +1 -1
  17. data/ext/libdatadog_api/ddsketch.c +106 -0
  18. data/ext/libdatadog_api/feature_flags.c +554 -0
  19. data/ext/libdatadog_api/feature_flags.h +5 -0
  20. data/ext/libdatadog_api/init.c +5 -0
  21. data/ext/libdatadog_api/library_config.c +34 -25
  22. data/ext/libdatadog_api/process_discovery.c +24 -18
  23. data/ext/libdatadog_extconf_helpers.rb +1 -1
  24. data/lib/datadog/ai_guard/api_client.rb +82 -0
  25. data/lib/datadog/ai_guard/component.rb +42 -0
  26. data/lib/datadog/ai_guard/configuration/ext.rb +17 -0
  27. data/lib/datadog/ai_guard/configuration/settings.rb +98 -0
  28. data/lib/datadog/ai_guard/configuration.rb +11 -0
  29. data/lib/datadog/ai_guard/evaluation/message.rb +25 -0
  30. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +34 -0
  31. data/lib/datadog/ai_guard/evaluation/request.rb +81 -0
  32. data/lib/datadog/ai_guard/evaluation/result.rb +43 -0
  33. data/lib/datadog/ai_guard/evaluation/tool_call.rb +18 -0
  34. data/lib/datadog/ai_guard/evaluation.rb +72 -0
  35. data/lib/datadog/ai_guard/ext.rb +16 -0
  36. data/lib/datadog/ai_guard.rb +153 -0
  37. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
  38. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +59 -0
  39. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +29 -0
  40. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
  41. data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
  42. data/lib/datadog/appsec/api_security/route_extractor.rb +26 -5
  43. data/lib/datadog/appsec/api_security/sampler.rb +7 -4
  44. data/lib/datadog/appsec/assets/blocked.html +8 -0
  45. data/lib/datadog/appsec/assets/blocked.json +1 -1
  46. data/lib/datadog/appsec/assets/blocked.text +3 -1
  47. data/lib/datadog/appsec/assets/waf_rules/README.md +30 -36
  48. data/lib/datadog/appsec/assets/waf_rules/recommended.json +359 -4
  49. data/lib/datadog/appsec/assets/waf_rules/strict.json +43 -2
  50. data/lib/datadog/appsec/assets.rb +1 -1
  51. data/lib/datadog/appsec/autoload.rb +1 -1
  52. data/lib/datadog/appsec/compressed_json.rb +1 -1
  53. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  54. data/lib/datadog/appsec/context.rb +2 -1
  55. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
  56. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +3 -2
  57. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +3 -1
  58. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +3 -1
  59. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -4
  60. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +5 -1
  61. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +7 -2
  62. data/lib/datadog/appsec/contrib/rails/patcher.rb +30 -0
  63. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +3 -1
  64. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -4
  65. data/lib/datadog/appsec/event.rb +12 -14
  66. data/lib/datadog/appsec/metrics/collector.rb +19 -3
  67. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +2 -1
  68. data/lib/datadog/appsec/monitor/gateway/watcher.rb +4 -4
  69. data/lib/datadog/appsec/remote.rb +34 -25
  70. data/lib/datadog/appsec/response.rb +18 -4
  71. data/lib/datadog/appsec/security_engine/engine.rb +3 -3
  72. data/lib/datadog/appsec/security_engine/result.rb +29 -9
  73. data/lib/datadog/appsec/security_engine/runner.rb +19 -9
  74. data/lib/datadog/appsec/security_event.rb +5 -7
  75. data/lib/datadog/core/configuration/agent_settings_resolver.rb +4 -4
  76. data/lib/datadog/core/configuration/components.rb +59 -11
  77. data/lib/datadog/core/configuration/config_helper.rb +100 -0
  78. data/lib/datadog/core/configuration/deprecations.rb +36 -0
  79. data/lib/datadog/core/configuration/ext.rb +0 -1
  80. data/lib/datadog/core/configuration/option.rb +38 -43
  81. data/lib/datadog/core/configuration/option_definition.rb +4 -11
  82. data/lib/datadog/core/configuration/options.rb +9 -10
  83. data/lib/datadog/core/configuration/settings.rb +38 -9
  84. data/lib/datadog/core/configuration/stable_config.rb +10 -0
  85. data/lib/datadog/core/configuration/supported_configurations.rb +373 -0
  86. data/lib/datadog/core/configuration.rb +2 -2
  87. data/lib/datadog/core/ddsketch.rb +19 -0
  88. data/lib/datadog/core/deprecations.rb +2 -2
  89. data/lib/datadog/core/environment/cgroup.rb +52 -25
  90. data/lib/datadog/core/environment/container.rb +140 -46
  91. data/lib/datadog/core/environment/ext.rb +7 -2
  92. data/lib/datadog/core/environment/git.rb +2 -2
  93. data/lib/datadog/core/environment/process.rb +87 -0
  94. data/lib/datadog/core/environment/variable_helpers.rb +3 -3
  95. data/lib/datadog/core/environment/yjit.rb +2 -1
  96. data/lib/datadog/core/error.rb +6 -6
  97. data/lib/datadog/core/feature_flags.rb +61 -0
  98. data/lib/datadog/core/metrics/client.rb +2 -2
  99. data/lib/datadog/core/pin.rb +8 -8
  100. data/lib/datadog/core/process_discovery/tracer_memfd.rb +2 -4
  101. data/lib/datadog/core/process_discovery.rb +48 -23
  102. data/lib/datadog/core/rate_limiter.rb +9 -1
  103. data/lib/datadog/core/remote/client/capabilities.rb +7 -0
  104. data/lib/datadog/core/remote/client.rb +14 -6
  105. data/lib/datadog/core/remote/component.rb +10 -10
  106. data/lib/datadog/core/remote/configuration/content.rb +15 -2
  107. data/lib/datadog/core/remote/configuration/digest.rb +14 -7
  108. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  109. data/lib/datadog/core/remote/configuration/target.rb +13 -6
  110. data/lib/datadog/core/remote/transport/config.rb +4 -25
  111. data/lib/datadog/core/remote/transport/http/config.rb +10 -50
  112. data/lib/datadog/core/remote/transport/http/negotiation.rb +14 -44
  113. data/lib/datadog/core/remote/transport/http.rb +15 -24
  114. data/lib/datadog/core/remote/transport/negotiation.rb +8 -33
  115. data/lib/datadog/core/remote/worker.rb +25 -37
  116. data/lib/datadog/core/runtime/ext.rb +0 -1
  117. data/lib/datadog/core/runtime/metrics.rb +11 -1
  118. data/lib/datadog/core/semaphore.rb +1 -4
  119. data/lib/datadog/core/tag_builder.rb +0 -4
  120. data/lib/datadog/core/tag_normalizer.rb +84 -0
  121. data/lib/datadog/core/telemetry/component.rb +69 -15
  122. data/lib/datadog/core/telemetry/emitter.rb +6 -6
  123. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
  124. data/lib/datadog/core/telemetry/event/app_started.rb +89 -51
  125. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  126. data/lib/datadog/core/telemetry/event.rb +1 -0
  127. data/lib/datadog/core/telemetry/logger.rb +2 -2
  128. data/lib/datadog/core/telemetry/logging.rb +2 -8
  129. data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
  130. data/lib/datadog/core/telemetry/request.rb +17 -3
  131. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +3 -34
  132. data/lib/datadog/core/telemetry/transport/http.rb +21 -16
  133. data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -11
  134. data/lib/datadog/core/telemetry/worker.rb +88 -32
  135. data/lib/datadog/core/transport/ext.rb +2 -0
  136. data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
  137. data/lib/datadog/core/transport/http/api/instance.rb +4 -21
  138. data/lib/datadog/core/transport/http/builder.rb +9 -5
  139. data/lib/datadog/core/transport/http/client.rb +80 -0
  140. data/lib/datadog/core/transport/http.rb +22 -19
  141. data/lib/datadog/core/transport/response.rb +15 -1
  142. data/lib/datadog/core/transport/transport.rb +90 -0
  143. data/lib/datadog/core/utils/array.rb +29 -0
  144. data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -21
  145. data/lib/datadog/core/utils/network.rb +22 -1
  146. data/lib/datadog/core/utils/only_once_successful.rb +8 -2
  147. data/lib/datadog/core/utils/safe_dup.rb +2 -2
  148. data/lib/datadog/core/utils/sequence.rb +2 -0
  149. data/lib/datadog/core/utils/time.rb +1 -1
  150. data/lib/datadog/core/utils.rb +2 -0
  151. data/lib/datadog/core/workers/async.rb +10 -1
  152. data/lib/datadog/core/workers/interval_loop.rb +44 -3
  153. data/lib/datadog/core/workers/polling.rb +2 -0
  154. data/lib/datadog/core/workers/queue.rb +100 -1
  155. data/lib/datadog/core.rb +2 -0
  156. data/lib/datadog/data_streams/configuration/settings.rb +49 -0
  157. data/lib/datadog/data_streams/configuration.rb +11 -0
  158. data/lib/datadog/data_streams/ext.rb +11 -0
  159. data/lib/datadog/data_streams/extensions.rb +16 -0
  160. data/lib/datadog/data_streams/pathway_context.rb +169 -0
  161. data/lib/datadog/data_streams/processor.rb +509 -0
  162. data/lib/datadog/data_streams/transport/http/stats.rb +52 -0
  163. data/lib/datadog/data_streams/transport/http.rb +40 -0
  164. data/lib/datadog/data_streams/transport/stats.rb +46 -0
  165. data/lib/datadog/data_streams.rb +100 -0
  166. data/lib/datadog/di/boot.rb +7 -3
  167. data/lib/datadog/di/component.rb +14 -16
  168. data/lib/datadog/di/context.rb +70 -0
  169. data/lib/datadog/di/contrib/active_record.rb +30 -5
  170. data/lib/datadog/di/el/compiler.rb +168 -0
  171. data/lib/datadog/di/el/evaluator.rb +159 -0
  172. data/lib/datadog/di/el/expression.rb +42 -0
  173. data/lib/datadog/di/el.rb +5 -0
  174. data/lib/datadog/di/error.rb +34 -0
  175. data/lib/datadog/di/instrumenter.rb +189 -55
  176. data/lib/datadog/di/logger.rb +2 -2
  177. data/lib/datadog/di/probe.rb +55 -15
  178. data/lib/datadog/di/probe_builder.rb +41 -2
  179. data/lib/datadog/di/probe_file_loader/railtie.rb +1 -1
  180. data/lib/datadog/di/probe_file_loader.rb +1 -1
  181. data/lib/datadog/di/probe_manager.rb +50 -35
  182. data/lib/datadog/di/probe_notification_builder.rb +121 -70
  183. data/lib/datadog/di/probe_notifier_worker.rb +5 -5
  184. data/lib/datadog/di/proc_responder.rb +32 -0
  185. data/lib/datadog/di/remote.rb +89 -84
  186. data/lib/datadog/di/serializer.rb +151 -7
  187. data/lib/datadog/di/transport/diagnostics.rb +8 -36
  188. data/lib/datadog/di/transport/http/diagnostics.rb +1 -33
  189. data/lib/datadog/di/transport/http/input.rb +1 -33
  190. data/lib/datadog/di/transport/http.rb +32 -17
  191. data/lib/datadog/di/transport/input.rb +67 -34
  192. data/lib/datadog/di.rb +61 -5
  193. data/lib/datadog/error_tracking/filters.rb +2 -2
  194. data/lib/datadog/kit/appsec/events/v2.rb +2 -3
  195. data/lib/datadog/open_feature/component.rb +60 -0
  196. data/lib/datadog/open_feature/configuration.rb +27 -0
  197. data/lib/datadog/open_feature/evaluation_engine.rb +70 -0
  198. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  199. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  200. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  201. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  202. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  203. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  204. data/lib/datadog/open_feature/ext.rb +14 -0
  205. data/lib/datadog/open_feature/native_evaluator.rb +38 -0
  206. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  207. data/lib/datadog/open_feature/provider.rb +141 -0
  208. data/lib/datadog/open_feature/remote.rb +67 -0
  209. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  210. data/lib/datadog/open_feature/transport.rb +70 -0
  211. data/lib/datadog/open_feature.rb +19 -0
  212. data/lib/datadog/opentelemetry/api/baggage.rb +1 -1
  213. data/lib/datadog/opentelemetry/configuration/settings.rb +159 -0
  214. data/lib/datadog/opentelemetry/metrics.rb +117 -0
  215. data/lib/datadog/opentelemetry/sdk/configurator.rb +26 -2
  216. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +35 -0
  217. data/lib/datadog/opentelemetry.rb +3 -0
  218. data/lib/datadog/profiling/collectors/code_provenance.rb +41 -7
  219. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -2
  220. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  221. data/lib/datadog/profiling/collectors/info.rb +6 -5
  222. data/lib/datadog/profiling/component.rb +12 -11
  223. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +18 -0
  224. data/lib/datadog/profiling/ext.rb +2 -1
  225. data/lib/datadog/profiling/http_transport.rb +5 -2
  226. data/lib/datadog/profiling/profiler.rb +4 -0
  227. data/lib/datadog/profiling/tag_builder.rb +36 -3
  228. data/lib/datadog/profiling/tasks/exec.rb +2 -2
  229. data/lib/datadog/profiling.rb +1 -2
  230. data/lib/datadog/single_step_instrument.rb +1 -1
  231. data/lib/datadog/tracing/component.rb +6 -17
  232. data/lib/datadog/tracing/configuration/dynamic.rb +2 -2
  233. data/lib/datadog/tracing/configuration/ext.rb +9 -3
  234. data/lib/datadog/tracing/configuration/settings.rb +89 -10
  235. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +4 -4
  236. data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
  237. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
  238. data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
  239. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +4 -2
  240. data/lib/datadog/tracing/contrib/component.rb +2 -2
  241. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -1
  242. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +11 -3
  243. data/lib/datadog/tracing/contrib/extensions.rb +10 -2
  244. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +11 -7
  245. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
  246. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
  247. data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
  248. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +84 -43
  249. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +11 -3
  250. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +11 -3
  251. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +11 -3
  252. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  253. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  254. data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
  255. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  256. data/lib/datadog/tracing/contrib/karafka/monitor.rb +11 -0
  257. data/lib/datadog/tracing/contrib/karafka/patcher.rb +35 -4
  258. data/lib/datadog/tracing/contrib/rack/middlewares.rb +59 -27
  259. data/lib/datadog/tracing/contrib/rack/request_queue.rb +1 -0
  260. data/lib/datadog/tracing/contrib/rack/route_inference.rb +53 -0
  261. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +7 -1
  262. data/lib/datadog/tracing/contrib/rails/ext.rb +2 -1
  263. data/lib/datadog/tracing/contrib/rails/integration.rb +1 -1
  264. data/lib/datadog/tracing/contrib/rails/middlewares.rb +2 -2
  265. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +4 -1
  266. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +3 -1
  267. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +3 -1
  268. data/lib/datadog/tracing/contrib/span_attribute_schema.rb +1 -1
  269. data/lib/datadog/tracing/contrib/status_range_matcher.rb +9 -1
  270. data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +3 -1
  271. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
  272. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
  273. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
  274. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  275. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  276. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +49 -0
  277. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  278. data/lib/datadog/tracing/contrib/waterdrop.rb +41 -0
  279. data/lib/datadog/tracing/contrib.rb +1 -0
  280. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  281. data/lib/datadog/tracing/distributed/baggage.rb +3 -2
  282. data/lib/datadog/tracing/metadata/ext.rb +9 -1
  283. data/lib/datadog/tracing/remote.rb +1 -9
  284. data/lib/datadog/tracing/sampling/priority_sampler.rb +3 -1
  285. data/lib/datadog/tracing/span.rb +1 -1
  286. data/lib/datadog/tracing/span_event.rb +2 -2
  287. data/lib/datadog/tracing/span_operation.rb +20 -9
  288. data/lib/datadog/tracing/trace_operation.rb +44 -6
  289. data/lib/datadog/tracing/tracer.rb +42 -16
  290. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  291. data/lib/datadog/tracing/transport/http/traces.rb +2 -50
  292. data/lib/datadog/tracing/transport/http.rb +15 -9
  293. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  294. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  295. data/lib/datadog/tracing/transport/traces.rb +9 -71
  296. data/lib/datadog/tracing/workers/trace_writer.rb +5 -0
  297. data/lib/datadog/tracing/writer.rb +1 -0
  298. data/lib/datadog/version.rb +2 -2
  299. data/lib/datadog.rb +3 -0
  300. metadata +110 -24
  301. data/ext/libdatadog_api/macos_development.md +0 -26
  302. data/lib/datadog/core/remote/transport/http/api.rb +0 -53
  303. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  304. data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
  305. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  306. data/lib/datadog/core/transport/http/api/spec.rb +0 -36
  307. data/lib/datadog/di/transport/http/api.rb +0 -42
  308. data/lib/datadog/di/transport/http/client.rb +0 -47
  309. data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
  310. data/lib/datadog/tracing/transport/http/api.rb +0 -44
@@ -0,0 +1,509 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zlib'
4
+ require_relative 'pathway_context'
5
+ require_relative 'transport/http'
6
+ require_relative '../version'
7
+ require_relative '../core/worker'
8
+ require_relative '../core/workers/polling'
9
+ require_relative '../core/ddsketch'
10
+ require_relative '../core/buffer/cruby'
11
+ require_relative '../core/utils/time'
12
+
13
+ module Datadog
14
+ module DataStreams
15
+ # Raised when Data Streams Monitoring cannot be initialized due to missing dependencies
16
+ class UnsupportedError < StandardError; end
17
+
18
+ # Processor for Data Streams Monitoring
19
+ # This class is responsible for collecting and reporting pathway stats
20
+ # Periodically (every interval, 10 seconds by default) flushes stats to the Datadog agent.
21
+ class Processor < Core::Worker
22
+ include Core::Workers::Polling
23
+
24
+ PROPAGATION_KEY = 'dd-pathway-ctx-base64'
25
+
26
+ # Default buffer size for lock-free event queue
27
+ # Set to handle high-throughput scenarios (e.g., 10k events/sec for 10s interval)
28
+ DEFAULT_BUFFER_SIZE = 100_000
29
+
30
+ attr_reader :pathway_context, :buckets, :bucket_size_ns
31
+
32
+ # Initialize the Data Streams Monitoring processor
33
+ #
34
+ # @param interval [Float] Flush interval in seconds (e.g., 10.0 for 10 seconds)
35
+ # @param logger [Datadog::Core::Logger] Logger instance for debugging
36
+ # @param settings [Datadog::Core::Configuration::Settings] Global configuration settings
37
+ # @param agent_settings [Datadog::Core::Configuration::AgentSettings] Agent connection settings
38
+ # @param buffer_size [Integer] Size of the lock-free event buffer for async stat collection
39
+ # (default: DEFAULT_BUFFER_SIZE). Higher values support more throughput but use more memory.
40
+ # @raise [UnsupportedError] if DDSketch is not available on this platform
41
+ def initialize(interval:, logger:, settings:, agent_settings:, buffer_size: DEFAULT_BUFFER_SIZE)
42
+ raise UnsupportedError, 'DDSketch is not supported' unless Datadog::Core::DDSketch.supported?
43
+
44
+ @settings = settings
45
+ @agent_settings = agent_settings
46
+ @logger = logger
47
+
48
+ now = Core::Utils::Time.now
49
+ @pathway_context = PathwayContext.new(
50
+ hash_value: 0,
51
+ pathway_start: now,
52
+ current_edge_start: now
53
+ )
54
+ @bucket_size_ns = (interval * 1e9).to_i
55
+ @buckets = {}
56
+ @consumer_stats = []
57
+ @stats_mutex = Mutex.new
58
+ @event_buffer = Core::Buffer::CRuby.new(buffer_size)
59
+
60
+ super()
61
+ self.loop_base_interval = interval
62
+
63
+ perform
64
+ end
65
+
66
+ # Track Kafka produce offset for lag monitoring
67
+ # @param topic [String] The Kafka topic name
68
+ # @param partition [Integer] The partition number
69
+ # @param offset [Integer] The offset of the produced message
70
+ # @param now [Time] Timestamp
71
+ # @return [Boolean] true if tracking succeeded
72
+ def track_kafka_produce(topic, partition, offset, now)
73
+ @event_buffer.push(
74
+ {
75
+ type: :kafka_produce,
76
+ topic: topic,
77
+ partition: partition,
78
+ offset: offset,
79
+ timestamp_ns: (now.to_f * 1e9).to_i
80
+ }
81
+ )
82
+ true
83
+ end
84
+
85
+ # Track Kafka message consumption for consumer lag monitoring
86
+ # @param topic [String] The Kafka topic name
87
+ # @param partition [Integer] The partition number
88
+ # @param offset [Integer] The offset of the consumed message
89
+ # @param now [Time] Timestamp
90
+ # @return [Boolean] true if tracking succeeded
91
+ def track_kafka_consume(topic, partition, offset, now)
92
+ @event_buffer.push(
93
+ {
94
+ type: :kafka_consume,
95
+ topic: topic,
96
+ partition: partition,
97
+ offset: offset,
98
+ timestamp: now
99
+ }
100
+ )
101
+ true
102
+ end
103
+
104
+ # Set a produce checkpoint
105
+ # @param type [String] The type of the checkpoint (e.g., 'kafka', 'kinesis', 'sns')
106
+ # @param destination [String] The destination (e.g., topic, exchange, stream name)
107
+ # @param manual_checkpoint [Boolean] Whether this checkpoint was manually set (default: true)
108
+ # @param tags [Hash] Additional tags to include
109
+ # @yield [key, value] Block to inject context into carrier
110
+ # @return [String] Base64 encoded pathway context
111
+ def set_produce_checkpoint(type:, destination:, manual_checkpoint: true, tags: {}, &block)
112
+ checkpoint_tags = ["type:#{type}", "topic:#{destination}", 'direction:out']
113
+ checkpoint_tags << 'manual_checkpoint:true' if manual_checkpoint
114
+ checkpoint_tags.concat(tags.map { |k, v| "#{k}:#{v}" }) unless tags.empty?
115
+
116
+ span = Datadog::Tracing.active_span
117
+ pathway = set_checkpoint(tags: checkpoint_tags, span: span)
118
+
119
+ yield(PROPAGATION_KEY, pathway) if pathway && block
120
+
121
+ pathway
122
+ end
123
+
124
+ # Set a consume checkpoint
125
+ # @param type [String] The type of the checkpoint (e.g., 'kafka', 'kinesis', 'sns')
126
+ # @param source [String] The source (e.g., topic, exchange, stream name)
127
+ # @param manual_checkpoint [Boolean] Whether this checkpoint was manually set (default: true)
128
+ # @param tags [Hash] Additional tags to include
129
+ # @yield [key] Block to extract context from carrier
130
+ # @return [String] Base64 encoded pathway context
131
+ def set_consume_checkpoint(type:, source:, manual_checkpoint: true, tags: {}, &block)
132
+ if block
133
+ pathway_ctx = yield(PROPAGATION_KEY)
134
+ if pathway_ctx
135
+ decoded_ctx = decode_pathway_b64(pathway_ctx)
136
+ set_pathway_context(decoded_ctx)
137
+ end
138
+ end
139
+
140
+ checkpoint_tags = ["type:#{type}", "topic:#{source}", 'direction:in']
141
+ checkpoint_tags << 'manual_checkpoint:true' if manual_checkpoint
142
+ checkpoint_tags.concat(tags.map { |k, v| "#{k}:#{v}" }) unless tags.empty?
143
+
144
+ span = Datadog::Tracing.active_span
145
+ set_checkpoint(tags: checkpoint_tags, span: span)
146
+ end
147
+
148
+ # Called periodically by the worker to flush stats to the agent
149
+ def perform
150
+ process_events
151
+ flush_stats
152
+ true
153
+ end
154
+
155
+ private
156
+
157
+ # Drain event buffer and apply updates to shared data structures
158
+ # This runs in the background worker thread, not the critical path
159
+ def process_events
160
+ events = @event_buffer.pop
161
+ return if events.empty?
162
+
163
+ @stats_mutex.synchronize do
164
+ events.each do |event_obj|
165
+ # Buffer stores Objects; we know they're hashes with symbol keys
166
+ event = event_obj # : ::Hash[::Symbol, untyped]
167
+ case event[:type]
168
+ when :kafka_produce
169
+ process_kafka_produce_event(event)
170
+ when :kafka_consume
171
+ process_kafka_consume_event(event)
172
+ when :checkpoint
173
+ process_checkpoint_event(event)
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ def process_kafka_produce_event(event)
180
+ partition_key = "#{event[:topic]}:#{event[:partition]}"
181
+ bucket_time_ns = event[:timestamp_ns] - (event[:timestamp_ns] % @bucket_size_ns)
182
+ bucket = @buckets[bucket_time_ns] ||= create_bucket
183
+
184
+ bucket[:latest_produce_offsets][partition_key] = [
185
+ event[:offset],
186
+ bucket[:latest_produce_offsets][partition_key] || 0
187
+ ].max
188
+ end
189
+
190
+ def process_kafka_consume_event(event)
191
+ @consumer_stats << {
192
+ topic: event[:topic],
193
+ partition: event[:partition],
194
+ offset: event[:offset],
195
+ timestamp: event[:timestamp],
196
+ timestamp_sec: event[:timestamp].to_f
197
+ }
198
+
199
+ timestamp_ns = (event[:timestamp].to_f * 1e9).to_i
200
+ bucket_time_ns = timestamp_ns - (timestamp_ns % @bucket_size_ns)
201
+ @buckets[bucket_time_ns] ||= create_bucket
202
+
203
+ # Track offset gaps for lag detection
204
+ partition_key = "#{event[:topic]}:#{event[:partition]}"
205
+ @latest_consumer_offsets ||= {}
206
+ previous_offset = @latest_consumer_offsets[partition_key] || 0
207
+
208
+ if event[:offset] > previous_offset + 1
209
+ @consumer_lag_events ||= []
210
+ @consumer_lag_events << {
211
+ topic: event[:topic],
212
+ partition: event[:partition],
213
+ expected_offset: previous_offset + 1,
214
+ actual_offset: event[:offset],
215
+ gap_size: event[:offset] - previous_offset - 1,
216
+ timestamp_sec: event[:timestamp].to_f
217
+ }
218
+ end
219
+
220
+ @latest_consumer_offsets[partition_key] = [event[:offset], previous_offset].max
221
+ end
222
+
223
+ def process_checkpoint_event(event)
224
+ now_ns = (event[:timestamp_sec] * 1e9).to_i
225
+ bucket_time_ns = now_ns - (now_ns % @bucket_size_ns)
226
+ bucket = @buckets[bucket_time_ns] ||= create_bucket
227
+
228
+ aggr_key = [event[:tags].join(','), event[:hash], event[:parent_hash]]
229
+ stats = bucket[:pathway_stats][aggr_key] ||= create_pathway_stats
230
+
231
+ stats[:edge_latency].add(event[:edge_latency_sec])
232
+ stats[:full_pathway_latency].add(event[:full_pathway_latency_sec])
233
+ end
234
+
235
+ def encode_pathway_context
236
+ @pathway_context.encode_b64
237
+ end
238
+
239
+ def set_checkpoint(tags:, now: nil, payload_size: 0, span: nil)
240
+ now ||= Core::Utils::Time.now
241
+
242
+ current_context = get_current_context
243
+ tags = tags.sort
244
+
245
+ direction = nil #: ::String?
246
+ tags.each do |tag|
247
+ if tag.start_with?('direction:')
248
+ direction = tag
249
+ break
250
+ end
251
+ end
252
+
253
+ # Loop detection: consecutive same-direction checkpoints reuse the opposite direction's hash
254
+ if direction && direction == current_context.previous_direction
255
+ current_context.hash = current_context.closest_opposite_direction_hash
256
+ if current_context.hash == 0
257
+ current_context.current_edge_start = now
258
+ current_context.pathway_start = now
259
+ else
260
+ current_context.current_edge_start = current_context.closest_opposite_direction_edge_start
261
+ end
262
+ else
263
+ current_context.previous_direction = direction
264
+ current_context.closest_opposite_direction_hash = current_context.hash
265
+ current_context.closest_opposite_direction_edge_start = current_context.current_edge_start
266
+ end
267
+
268
+ parent_hash = current_context.hash
269
+ new_hash = compute_pathway_hash(parent_hash, tags)
270
+
271
+ # Tag the APM span with the pathway hash to link DSM and APM
272
+ span&.set_tag('pathway.hash', new_hash.to_s)
273
+
274
+ edge_latency_sec = [now - current_context.current_edge_start, 0.0].max
275
+ full_pathway_latency_sec = [now - current_context.pathway_start, 0.0].max
276
+
277
+ record_checkpoint_stats(
278
+ hash: new_hash,
279
+ parent_hash: parent_hash,
280
+ edge_latency_sec: edge_latency_sec,
281
+ full_pathway_latency_sec: full_pathway_latency_sec,
282
+ payload_size: payload_size,
283
+ tags: tags,
284
+ timestamp_sec: now.to_f
285
+ )
286
+
287
+ current_context.parent_hash = current_context.hash
288
+ current_context.hash = new_hash
289
+ current_context.current_edge_start = now
290
+
291
+ current_context.encode_b64
292
+ end
293
+
294
+ def decode_pathway_context(encoded_ctx)
295
+ PathwayContext.decode_b64(encoded_ctx)
296
+ end
297
+
298
+ def decode_pathway_b64(encoded_ctx)
299
+ PathwayContext.decode_b64(encoded_ctx)
300
+ end
301
+
302
+ def flush_stats
303
+ payload = nil # : ::Hash[::String, untyped]?
304
+
305
+ @stats_mutex.synchronize do
306
+ return if @buckets.empty? && @consumer_stats.empty?
307
+
308
+ stats_buckets = serialize_buckets
309
+
310
+ payload = {
311
+ 'Service' => @settings.service,
312
+ 'TracerVersion' => Datadog::VERSION::STRING,
313
+ 'Lang' => 'ruby',
314
+ 'Stats' => stats_buckets,
315
+ 'Hostname' => hostname
316
+ }
317
+
318
+ # Clear consumer stats even if sending fails to prevent unbounded memory growth
319
+ # Must be done inside mutex before we release it
320
+ @consumer_stats.clear
321
+ end
322
+
323
+ # Send to agent outside mutex to avoid blocking customer code if agent is slow/hung
324
+ send_stats_to_agent(payload) if payload
325
+ rescue => e
326
+ @logger.debug("Failed to flush DSM stats to agent: #{e.class}: #{e}")
327
+ end
328
+
329
+ def get_current_pathway
330
+ get_current_context
331
+ end
332
+
333
+ def get_current_context
334
+ @pathway_context ||= begin
335
+ now = Core::Utils::Time.now
336
+ PathwayContext.new(
337
+ hash_value: 0,
338
+ pathway_start: now,
339
+ current_edge_start: now
340
+ )
341
+ end
342
+ end
343
+
344
+ def set_pathway_context(ctx)
345
+ if ctx
346
+ @pathway_context = ctx
347
+ @pathway_context.previous_direction = nil
348
+ @pathway_context.closest_opposite_direction_hash = 0
349
+ @pathway_context.closest_opposite_direction_edge_start = @pathway_context.current_edge_start
350
+ end
351
+ end
352
+
353
+ def decode_and_set_pathway_context(headers)
354
+ return unless headers && headers['dd-pathway-ctx-base64']
355
+
356
+ pathway_ctx = decode_pathway_context(headers['dd-pathway-ctx-base64'])
357
+ set_pathway_context(pathway_ctx) if pathway_ctx
358
+ end
359
+
360
+ # Compute new pathway hash using FNV-1a algorithm.
361
+ # Combines service, env, tags, and parent hash to create unique pathway identifier.
362
+ def compute_pathway_hash(current_hash, tags)
363
+ service = @settings.service || 'ruby-service'
364
+ env = @settings.env || 'none'
365
+
366
+ bytes = service.bytes + env.bytes
367
+ tags.each { |tag| bytes += tag.bytes }
368
+ byte_string = bytes.pack('C*')
369
+
370
+ node_hash = fnv1_64(byte_string)
371
+ combined_bytes = [node_hash, current_hash].pack('QQ')
372
+ fnv1_64(combined_bytes)
373
+ end
374
+
375
+ # FNV-1a 64-bit hash function.
376
+ def fnv1_64(data)
377
+ fnv_offset_basis = 14695981039346656037
378
+ fnv_prime = 1099511628211
379
+
380
+ hash_value = fnv_offset_basis
381
+ data.each_byte do |byte|
382
+ hash_value ^= byte
383
+ hash_value = (hash_value * fnv_prime) & 0xFFFFFFFFFFFFFFFF
384
+ end
385
+ hash_value
386
+ end
387
+
388
+ def record_checkpoint_stats(
389
+ hash:, parent_hash:, edge_latency_sec:, full_pathway_latency_sec:, payload_size:, tags:,
390
+ timestamp_sec:
391
+ )
392
+ @event_buffer.push(
393
+ {
394
+ type: :checkpoint,
395
+ hash: hash,
396
+ parent_hash: parent_hash,
397
+ edge_latency_sec: edge_latency_sec,
398
+ full_pathway_latency_sec: full_pathway_latency_sec,
399
+ payload_size: payload_size,
400
+ tags: tags,
401
+ timestamp_sec: timestamp_sec
402
+ }
403
+ )
404
+ true
405
+ end
406
+
407
+ def record_consumer_stats(topic:, partition:, offset:, timestamp:)
408
+ # Already handled by track_kafka_consume pushing to buffer
409
+ # This method kept for API compatibility but does nothing
410
+ end
411
+
412
+ def send_stats_to_agent(payload)
413
+ response = transport.send_stats(payload)
414
+ @logger.debug("DSM stats sent to agent: ok=#{response.ok?}")
415
+ end
416
+
417
+ def transport
418
+ @transport ||= Transport::HTTP.default(
419
+ agent_settings: @agent_settings,
420
+ logger: @logger
421
+ )
422
+ end
423
+
424
+ def serialize_buckets
425
+ serialized_buckets = []
426
+ bucket_keys_to_clear = []
427
+
428
+ @buckets.each do |bucket_time_ns, bucket|
429
+ bucket_keys_to_clear << bucket_time_ns
430
+
431
+ bucket_stats = []
432
+ bucket[:pathway_stats].each do |aggr_key, stats|
433
+ edge_tags_str, hash_value, parent_hash = aggr_key
434
+ edge_tags_array = edge_tags_str.split(',')
435
+
436
+ bucket_stats << {
437
+ 'EdgeTags' => edge_tags_array,
438
+ 'Hash' => hash_value,
439
+ 'ParentHash' => parent_hash,
440
+ 'PathwayLatency' => stats[:full_pathway_latency].encode,
441
+ 'EdgeLatency' => stats[:edge_latency].encode,
442
+ }
443
+ end
444
+
445
+ backlogs = []
446
+ bucket[:latest_produce_offsets].each do |key, offset|
447
+ topic, partition = key.split(':', 2)
448
+ backlogs << {
449
+ 'Tags' => ['type:kafka_produce', "topic:#{topic}", "partition:#{partition}"],
450
+ 'Value' => offset
451
+ }
452
+ end
453
+ bucket[:latest_commit_offsets].each do |key, offset|
454
+ group, topic, partition = key.split(':', 3)
455
+ backlogs << {
456
+ 'Tags' => ['type:kafka_commit', "consumer_group:#{group}", "topic:#{topic}", "partition:#{partition}"],
457
+ 'Value' => offset
458
+ }
459
+ end
460
+
461
+ serialized_buckets << {
462
+ 'Start' => bucket_time_ns,
463
+ 'Duration' => @bucket_size_ns,
464
+ 'Stats' => bucket_stats,
465
+ 'Backlogs' => backlogs + serialize_consumer_backlogs
466
+ }
467
+ end
468
+
469
+ bucket_keys_to_clear.each { |key| @buckets.delete(key) }
470
+
471
+ serialized_buckets
472
+ end
473
+
474
+ def serialize_consumer_backlogs
475
+ @consumer_stats.map do |stat|
476
+ {
477
+ 'Tags' => [
478
+ 'type:kafka_commit',
479
+ "topic:#{stat[:topic]}",
480
+ "partition:#{stat[:partition]}"
481
+ ],
482
+ 'Value' => stat[:offset]
483
+ }
484
+ end
485
+ end
486
+
487
+ def hostname
488
+ Core::Environment::Socket.hostname
489
+ end
490
+
491
+ def create_bucket
492
+ {
493
+ pathway_stats: {},
494
+ latest_produce_offsets: {},
495
+ latest_commit_offsets: {}
496
+ }
497
+ end
498
+
499
+ def create_pathway_stats
500
+ {
501
+ edge_latency: Datadog::Core::DDSketch.new,
502
+ full_pathway_latency: Datadog::Core::DDSketch.new,
503
+ payload_size_sum: 0,
504
+ payload_size_count: 0
505
+ }
506
+ end
507
+ end
508
+ end
509
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../stats'
4
+ require_relative '../../../core/transport/http/api/endpoint'
5
+ require_relative '../../../core/transport/http/response'
6
+
7
+ module Datadog
8
+ module DataStreams
9
+ module Transport
10
+ module HTTP
11
+ # HTTP transport behavior for Data Streams stats
12
+ module Stats
13
+ # Response from HTTP transport for DSM stats
14
+ class Response
15
+ include Datadog::Core::Transport::HTTP::Response
16
+
17
+ def initialize(http_response)
18
+ super
19
+ end
20
+ end
21
+
22
+ module API
23
+ # Endpoint for submitting DSM stats data
24
+ class Endpoint < Core::Transport::HTTP::API::Endpoint
25
+ def initialize(path)
26
+ super(:post, path)
27
+ end
28
+
29
+ def call(env, &block)
30
+ # Build request
31
+ env.verb = verb
32
+ env.path = path
33
+ env.body = env.request.parcel.data
34
+
35
+ # Send request
36
+ http_response = yield(env)
37
+
38
+ # Build response
39
+ Response.new(http_response)
40
+ end
41
+
42
+ def encoder
43
+ # DSM handles encoding in the transport layer
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/transport/http'
4
+ require_relative 'http/stats'
5
+ require_relative 'stats'
6
+
7
+ module Datadog
8
+ module DataStreams
9
+ module Transport
10
+ # HTTP transport for Data Streams Monitoring
11
+ module HTTP
12
+ V01 = Stats::API::Endpoint.new(
13
+ '/v0.1/pipeline_stats'
14
+ )
15
+
16
+ module_function
17
+
18
+ # Builds a new Transport::HTTP::Client with default settings
19
+ def default(
20
+ agent_settings:,
21
+ logger:
22
+ )
23
+ Core::Transport::HTTP.build(
24
+ agent_settings: agent_settings,
25
+ logger: logger,
26
+ headers: {
27
+ 'Content-Type' => 'application/msgpack',
28
+ 'Content-Encoding' => 'gzip'
29
+ }
30
+ ) do |transport|
31
+ transport.api 'v0.1', V01, default: true
32
+
33
+ # Call block to apply any customization, if provided
34
+ yield(transport) if block_given?
35
+ end.to_transport(Transport::Stats::Transport)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'msgpack'
4
+ require 'zlib'
5
+ require_relative '../../core/transport/parcel'
6
+ require_relative '../../core/transport/request'
7
+ require_relative '../../core/transport/transport'
8
+
9
+ module Datadog
10
+ module DataStreams
11
+ module Transport
12
+ module Stats
13
+ # Parcel for encoded DSM stats payload
14
+ class EncodedParcel
15
+ include Datadog::Core::Transport::Parcel
16
+
17
+ def initialize(data)
18
+ @data = data
19
+ end
20
+
21
+ attr_reader :data
22
+ end
23
+
24
+ # Request for DSM stats
25
+ class Request < Datadog::Core::Transport::Request
26
+ end
27
+
28
+ # Transport for Data Streams Monitoring stats
29
+ class Transport < Core::Transport::Transport
30
+ def send_stats(payload)
31
+ # MessagePack encode and gzip compress the payload
32
+ msgpack_data = MessagePack.pack(payload)
33
+ compressed_data = Zlib.gzip(msgpack_data)
34
+
35
+ # Create parcel and request
36
+ parcel = EncodedParcel.new(compressed_data)
37
+ request = Request.new(parcel)
38
+
39
+ # Send to agent
40
+ client.send_request(:stats, request)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end