datadog 2.38.0 → 2.47.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 (922) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +279 -35
  3. data/README.md +3 -0
  4. data/bin/ddprofrb +4 -4
  5. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
  6. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +256 -117
  7. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -5
  8. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +0 -2
  9. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +19 -20
  10. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +5 -1
  11. data/ext/datadog_profiling_native_extension/collectors_stack.c +267 -171
  12. data/ext/datadog_profiling_native_extension/collectors_stack.h +17 -3
  13. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +307 -217
  14. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +10 -5
  15. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +5 -4
  16. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +19 -11
  17. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +7 -10
  18. data/ext/datadog_profiling_native_extension/extconf.rb +68 -100
  19. data/ext/datadog_profiling_native_extension/heap_recorder.c +353 -257
  20. data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
  21. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -27
  22. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +853 -496
  23. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +42 -15
  24. data/ext/datadog_profiling_native_extension/profiling.c +30 -23
  25. data/ext/datadog_profiling_native_extension/ruby_helpers.c +4 -80
  26. data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -7
  27. data/ext/datadog_profiling_native_extension/stack_recorder.c +116 -89
  28. data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -5
  29. data/ext/datadog_profiling_native_extension/time_helpers.c +2 -4
  30. data/ext/datadog_profiling_native_extension/time_helpers.h +15 -15
  31. data/ext/libdatadog_api/datadog_ruby_common.c +19 -11
  32. data/ext/libdatadog_api/datadog_ruby_common.h +7 -10
  33. data/ext/libdatadog_api/di.c +43 -3
  34. data/ext/libdatadog_api/extconf.rb +29 -24
  35. data/ext/libdatadog_api/feature_flags.c +34 -0
  36. data/ext/libdatadog_api/init.c +9 -1
  37. data/ext/libdatadog_api/otel_thread_context.c +274 -0
  38. data/ext/libdatadog_api/otel_thread_context.h +5 -0
  39. data/ext/libdatadog_api/process_discovery.c +15 -1
  40. data/ext/libdatadog_api/trace_exporter.c +2078 -0
  41. data/ext/libdatadog_api/trace_exporter.h +5 -0
  42. data/ext/libdatadog_extconf_helpers.rb +22 -17
  43. data/lib/datadog/ai_guard/component.rb +24 -15
  44. data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
  45. data/lib/datadog/ai_guard/configuration.rb +13 -0
  46. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
  47. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
  48. data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
  49. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
  50. data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
  51. data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
  52. data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
  53. data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
  54. data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
  55. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
  56. data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
  57. data/lib/datadog/ai_guard/evaluation/request.rb +8 -64
  58. data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
  59. data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
  60. data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
  61. data/lib/datadog/ai_guard/evaluation.rb +36 -14
  62. data/lib/datadog/ai_guard/ext.rb +4 -1
  63. data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +9 -7
  64. data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
  65. data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
  66. data/lib/datadog/ai_guard/redaction/result.rb +29 -0
  67. data/lib/datadog/ai_guard/redaction.rb +80 -0
  68. data/lib/datadog/ai_guard.rb +58 -57
  69. data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +22 -43
  70. data/lib/datadog/appsec/actions_handler.rb +6 -6
  71. data/lib/datadog/appsec/anonymizer.rb +1 -1
  72. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +2 -2
  73. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +6 -6
  74. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +1 -1
  75. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +2 -2
  76. data/lib/datadog/appsec/api_security/route_extractor.rb +11 -11
  77. data/lib/datadog/appsec/api_security/sampler.rb +5 -5
  78. data/lib/datadog/appsec/api_security.rb +1 -1
  79. data/lib/datadog/appsec/assets/blocked.html +1 -108
  80. data/lib/datadog/appsec/assets.rb +7 -7
  81. data/lib/datadog/appsec/autoload.rb +3 -3
  82. data/lib/datadog/appsec/component.rb +15 -15
  83. data/lib/datadog/appsec/compressed_json.rb +6 -6
  84. data/lib/datadog/appsec/configuration.rb +70 -61
  85. data/lib/datadog/appsec/context.rb +7 -7
  86. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +6 -6
  87. data/lib/datadog/appsec/contrib/active_record/integration.rb +4 -4
  88. data/lib/datadog/appsec/contrib/active_record/patcher.rb +4 -4
  89. data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +7 -7
  90. data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +2 -2
  91. data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +2 -2
  92. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +36 -34
  93. data/lib/datadog/appsec/contrib/devise/configuration.rb +1 -1
  94. data/lib/datadog/appsec/contrib/devise/data_extractor.rb +3 -3
  95. data/lib/datadog/appsec/contrib/devise/ext.rb +20 -20
  96. data/lib/datadog/appsec/contrib/devise/integration.rb +4 -4
  97. data/lib/datadog/appsec/contrib/devise/patcher.rb +8 -8
  98. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +12 -12
  99. data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +8 -8
  100. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +24 -9
  101. data/lib/datadog/appsec/contrib/excon/integration.rb +4 -4
  102. data/lib/datadog/appsec/contrib/excon/patcher.rb +1 -1
  103. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +26 -26
  104. data/lib/datadog/appsec/contrib/faraday/integration.rb +4 -4
  105. data/lib/datadog/appsec/contrib/faraday/patcher.rb +4 -4
  106. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +22 -22
  107. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +4 -4
  108. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +2 -2
  109. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +7 -7
  110. data/lib/datadog/appsec/contrib/graphql/integration.rb +9 -9
  111. data/lib/datadog/appsec/contrib/graphql/patcher.rb +3 -3
  112. data/lib/datadog/appsec/contrib/rack/buffered_input.rb +1 -1
  113. data/lib/datadog/appsec/contrib/rack/ext.rb +29 -29
  114. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +14 -18
  115. data/lib/datadog/appsec/contrib/rack/gateway/response.rb +1 -1
  116. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +24 -24
  117. data/lib/datadog/appsec/contrib/rack/input_peeker.rb +6 -6
  118. data/lib/datadog/appsec/contrib/rack/integration.rb +6 -6
  119. data/lib/datadog/appsec/contrib/rack/patcher.rb +3 -3
  120. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +11 -8
  121. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +26 -26
  122. data/lib/datadog/appsec/contrib/rack/response_body.rb +1 -1
  123. data/lib/datadog/appsec/contrib/rails/gateway/request.rb +10 -10
  124. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +38 -11
  125. data/lib/datadog/appsec/contrib/rails/integration.rb +5 -5
  126. data/lib/datadog/appsec/contrib/rails/patcher.rb +17 -17
  127. data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +2 -2
  128. data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +10 -9
  129. data/lib/datadog/appsec/contrib/rails/request.rb +3 -3
  130. data/lib/datadog/appsec/contrib/rails/request_middleware.rb +2 -1
  131. data/lib/datadog/appsec/contrib/rest_client/integration.rb +5 -5
  132. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +1 -1
  133. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +23 -23
  134. data/lib/datadog/appsec/contrib/sinatra/gateway/request.rb +1 -1
  135. data/lib/datadog/appsec/contrib/sinatra/gateway/route_params.rb +1 -1
  136. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +28 -16
  137. data/lib/datadog/appsec/contrib/sinatra/integration.rb +5 -5
  138. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +11 -11
  139. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +10 -9
  140. data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
  141. data/lib/datadog/appsec/counter_sampler.rb +1 -1
  142. data/lib/datadog/appsec/event.rb +14 -14
  143. data/lib/datadog/appsec/ext.rb +11 -11
  144. data/lib/datadog/appsec/extensions.rb +2 -2
  145. data/lib/datadog/appsec/instrumentation/gateway.rb +1 -1
  146. data/lib/datadog/appsec/metrics/exporter.rb +8 -8
  147. data/lib/datadog/appsec/metrics/telemetry.rb +7 -7
  148. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +4 -4
  149. data/lib/datadog/appsec/metrics.rb +4 -4
  150. data/lib/datadog/appsec/monitor/gateway/watcher.rb +16 -12
  151. data/lib/datadog/appsec/monitor.rb +1 -1
  152. data/lib/datadog/appsec/processor/rule_loader.rb +32 -32
  153. data/lib/datadog/appsec/rate_limiter.rb +1 -1
  154. data/lib/datadog/appsec/remote.rb +6 -6
  155. data/lib/datadog/appsec/response.rb +22 -22
  156. data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +11 -11
  157. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +18 -18
  158. data/lib/datadog/appsec/route_normalizer/route_text.rb +3 -3
  159. data/lib/datadog/appsec/route_normalizer.rb +15 -15
  160. data/lib/datadog/appsec/security_engine/engine.rb +41 -40
  161. data/lib/datadog/appsec/security_engine/runner.rb +2 -2
  162. data/lib/datadog/appsec/security_event.rb +2 -2
  163. data/lib/datadog/appsec/utils/http/body.rb +11 -8
  164. data/lib/datadog/appsec/utils/http/body_reader.rb +1 -1
  165. data/lib/datadog/appsec/utils/http/media_range.rb +15 -15
  166. data/lib/datadog/appsec/utils/http/media_type.rb +4 -4
  167. data/lib/datadog/appsec/utils/http/url_encoded.rb +80 -25
  168. data/lib/datadog/appsec.rb +16 -16
  169. data/lib/datadog/auto_instrument.rb +2 -2
  170. data/lib/datadog/core/buffer/cruby.rb +1 -1
  171. data/lib/datadog/core/buffer/thread_safe.rb +1 -1
  172. data/lib/datadog/core/configuration/agent_settings.rb +1 -1
  173. data/lib/datadog/core/configuration/agent_settings_resolver.rb +15 -15
  174. data/lib/datadog/core/configuration/base.rb +2 -2
  175. data/lib/datadog/core/configuration/components.rb +82 -47
  176. data/lib/datadog/core/configuration/components_state.rb +8 -1
  177. data/lib/datadog/core/configuration/config_helper.rb +3 -3
  178. data/lib/datadog/core/configuration/deprecations.rb +6 -6
  179. data/lib/datadog/core/configuration/ext.rb +11 -11
  180. data/lib/datadog/core/configuration/option.rb +16 -16
  181. data/lib/datadog/core/configuration/option_definition.rb +5 -5
  182. data/lib/datadog/core/configuration/options.rb +2 -2
  183. data/lib/datadog/core/configuration/settings.rb +95 -100
  184. data/lib/datadog/core/configuration/stable_config.rb +1 -1
  185. data/lib/datadog/core/configuration/supported_configurations.rb +20 -3
  186. data/lib/datadog/core/configuration.rb +8 -8
  187. data/lib/datadog/core/contrib/rails/railtie.rb +3 -3
  188. data/lib/datadog/core/crashtracking/component.rb +14 -10
  189. data/lib/datadog/core/crashtracking/tag_builder.rb +5 -5
  190. data/lib/datadog/core/deprecations.rb +1 -1
  191. data/lib/datadog/core/diagnostics/environment_logger.rb +42 -7
  192. data/lib/datadog/core/diagnostics/health.rb +2 -2
  193. data/lib/datadog/core/encoding.rb +4 -4
  194. data/lib/datadog/core/environment/agent_info.rb +2 -2
  195. data/lib/datadog/core/environment/cgroup.rb +4 -4
  196. data/lib/datadog/core/environment/container.rb +9 -9
  197. data/lib/datadog/core/environment/execution.rb +3 -3
  198. data/lib/datadog/core/environment/ext.rb +18 -18
  199. data/lib/datadog/core/environment/gc.rb +1 -1
  200. data/lib/datadog/core/environment/git.rb +2 -2
  201. data/lib/datadog/core/environment/identity.rb +10 -10
  202. data/lib/datadog/core/environment/platform.rb +8 -8
  203. data/lib/datadog/core/environment/process.rb +3 -3
  204. data/lib/datadog/core/environment/socket.rb +2 -2
  205. data/lib/datadog/core/environment/variable_helpers.rb +1 -1
  206. data/lib/datadog/core/environment/vm_cache.rb +1 -1
  207. data/lib/datadog/core/environment/yjit.rb +2 -2
  208. data/lib/datadog/core/error.rb +5 -5
  209. data/lib/datadog/core/evp.rb +2 -2
  210. data/lib/datadog/core/extensions.rb +1 -1
  211. data/lib/datadog/core/feature_flags.rb +2 -2
  212. data/lib/datadog/core/git/ext.rb +4 -4
  213. data/lib/datadog/core/logger.rb +3 -3
  214. data/lib/datadog/core/logging/ext.rb +1 -1
  215. data/lib/datadog/core/metrics/client.rb +20 -20
  216. data/lib/datadog/core/metrics/ext.rb +5 -5
  217. data/lib/datadog/core/metrics/helpers.rb +1 -1
  218. data/lib/datadog/core/metrics/logging.rb +2 -2
  219. data/lib/datadog/core/metrics/options.rb +5 -5
  220. data/lib/datadog/core/pin.rb +1 -1
  221. data/lib/datadog/core/process_discovery.rb +13 -9
  222. data/lib/datadog/core/rate_limiter.rb +1 -1
  223. data/lib/datadog/core/remote/client/capabilities.rb +60 -33
  224. data/lib/datadog/core/remote/client.rb +61 -20
  225. data/lib/datadog/core/remote/component.rb +48 -17
  226. data/lib/datadog/core/remote/configuration/content.rb +3 -3
  227. data/lib/datadog/core/remote/configuration/digest.rb +1 -1
  228. data/lib/datadog/core/remote/configuration/path.rb +9 -9
  229. data/lib/datadog/core/remote/configuration/repository.rb +4 -4
  230. data/lib/datadog/core/remote/configuration/target.rb +9 -9
  231. data/lib/datadog/core/remote/configuration.rb +4 -4
  232. data/lib/datadog/core/remote/dispatcher.rb +47 -6
  233. data/lib/datadog/core/remote/ext.rb +3 -3
  234. data/lib/datadog/core/remote/negotiation.rb +1 -1
  235. data/lib/datadog/core/remote/tie/tracing.rb +7 -7
  236. data/lib/datadog/core/remote/transport/config.rb +4 -4
  237. data/lib/datadog/core/remote/transport/http/config.rb +7 -7
  238. data/lib/datadog/core/remote/transport/http/negotiation.rb +4 -4
  239. data/lib/datadog/core/remote/transport/http.rb +10 -10
  240. data/lib/datadog/core/remote/transport/negotiation.rb +3 -3
  241. data/lib/datadog/core/remote/worker.rb +13 -5
  242. data/lib/datadog/core/runtime/ext.rb +22 -22
  243. data/lib/datadog/core/runtime/metrics.rb +9 -9
  244. data/lib/datadog/core/tag_builder.rb +18 -18
  245. data/lib/datadog/core/tag_normalizer.rb +7 -7
  246. data/lib/datadog/core/telemetry/component.rb +13 -13
  247. data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
  248. data/lib/datadog/core/telemetry/emitter.rb +4 -4
  249. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +8 -6
  250. data/lib/datadog/core/telemetry/event/app_closing.rb +2 -2
  251. data/lib/datadog/core/telemetry/event/app_dependencies_loaded.rb +2 -2
  252. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +3 -3
  253. data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +2 -2
  254. data/lib/datadog/core/telemetry/event/app_heartbeat.rb +2 -2
  255. data/lib/datadog/core/telemetry/event/app_integrations_change.rb +2 -2
  256. data/lib/datadog/core/telemetry/event/app_started.rb +36 -55
  257. data/lib/datadog/core/telemetry/event/base.rb +1 -1
  258. data/lib/datadog/core/telemetry/event/distributions.rb +2 -2
  259. data/lib/datadog/core/telemetry/event/generate_metrics.rb +3 -3
  260. data/lib/datadog/core/telemetry/event/log.rb +6 -6
  261. data/lib/datadog/core/telemetry/event/message_batch.rb +1 -1
  262. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +3 -3
  263. data/lib/datadog/core/telemetry/event.rb +16 -16
  264. data/lib/datadog/core/telemetry/ext.rb +11 -11
  265. data/lib/datadog/core/telemetry/logger.rb +1 -1
  266. data/lib/datadog/core/telemetry/logging.rb +5 -5
  267. data/lib/datadog/core/telemetry/metric.rb +9 -9
  268. data/lib/datadog/core/telemetry/metrics_collection.rb +2 -2
  269. data/lib/datadog/core/telemetry/metrics_manager.rb +1 -1
  270. data/lib/datadog/core/telemetry/request.rb +6 -6
  271. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +14 -14
  272. data/lib/datadog/core/telemetry/transport/http.rb +7 -7
  273. data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -3
  274. data/lib/datadog/core/telemetry/worker.rb +6 -6
  275. data/lib/datadog/core/transport/ext.rb +14 -14
  276. data/lib/datadog/core/transport/http/adapters/net.rb +4 -4
  277. data/lib/datadog/core/transport/http/adapters/test.rb +1 -1
  278. data/lib/datadog/core/transport/http/adapters/unix_socket.rb +4 -4
  279. data/lib/datadog/core/transport/http/api/endpoint.rb +1 -1
  280. data/lib/datadog/core/transport/http/api/map.rb +1 -1
  281. data/lib/datadog/core/transport/http/builder.rb +5 -5
  282. data/lib/datadog/core/transport/http/client.rb +2 -2
  283. data/lib/datadog/core/transport/http/response.rb +1 -1
  284. data/lib/datadog/core/transport/http.rb +9 -9
  285. data/lib/datadog/core/transport/transport.rb +2 -2
  286. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +172 -20
  287. data/lib/datadog/core/utils/base64_codec.rb +3 -3
  288. data/lib/datadog/core/utils/duration.rb +1 -1
  289. data/lib/datadog/core/utils/forking.rb +2 -2
  290. data/lib/datadog/core/utils/lru_cache.rb +3 -3
  291. data/lib/datadog/core/utils/network.rb +11 -11
  292. data/lib/datadog/core/utils/only_once_successful.rb +1 -1
  293. data/lib/datadog/core/utils/safe_dup.rb +2 -1
  294. data/lib/datadog/core/utils/sequence.rb +3 -6
  295. data/lib/datadog/core/utils/spawn_monkey_patch.rb +2 -2
  296. data/lib/datadog/core/utils/time.rb +16 -54
  297. data/lib/datadog/core/utils/url.rb +1 -1
  298. data/lib/datadog/core/utils.rb +4 -4
  299. data/lib/datadog/core/workers/async.rb +1 -1
  300. data/lib/datadog/core/workers/polling.rb +2 -2
  301. data/lib/datadog/core/workers/runtime_metrics.rb +5 -5
  302. data/lib/datadog/core.rb +4 -4
  303. data/lib/datadog/data_streams/configuration.rb +3 -3
  304. data/lib/datadog/data_streams/ext.rb +1 -1
  305. data/lib/datadog/data_streams/extensions.rb +2 -2
  306. data/lib/datadog/data_streams/pathway_context.rb +25 -27
  307. data/lib/datadog/data_streams/processor.rb +92 -61
  308. data/lib/datadog/data_streams/transport/http/stats.rb +5 -5
  309. data/lib/datadog/data_streams/transport/http.rb +5 -5
  310. data/lib/datadog/data_streams/transport/stats.rb +7 -7
  311. data/lib/datadog/data_streams.rb +5 -5
  312. data/lib/datadog/di/base.rb +4 -4
  313. data/lib/datadog/di/boot.rb +26 -25
  314. data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
  315. data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
  316. data/lib/datadog/di/code_tracker.rb +6 -6
  317. data/lib/datadog/di/component.rb +119 -6
  318. data/lib/datadog/di/configuration.rb +54 -3
  319. data/lib/datadog/di/context.rb +7 -2
  320. data/lib/datadog/di/contrib/active_record.rb +8 -2
  321. data/lib/datadog/di/contrib/railtie.rb +1 -1
  322. data/lib/datadog/di/contrib.rb +5 -5
  323. data/lib/datadog/di/el/compiler.rb +113 -37
  324. data/lib/datadog/di/el/evaluator.rb +82 -2
  325. data/lib/datadog/di/el/expression.rb +13 -2
  326. data/lib/datadog/di/el.rb +3 -3
  327. data/lib/datadog/di/evaluation_failure.rb +28 -0
  328. data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
  329. data/lib/datadog/di/guardrails_telemetry.rb +203 -0
  330. data/lib/datadog/di/instrumenter.rb +124 -23
  331. data/lib/datadog/di/logger.rb +32 -10
  332. data/lib/datadog/di/preload.rb +1 -1
  333. data/lib/datadog/di/probe.rb +9 -1
  334. data/lib/datadog/di/probe_builder.rb +25 -24
  335. data/lib/datadog/di/probe_file_loader/railtie.rb +1 -1
  336. data/lib/datadog/di/probe_file_loader.rb +5 -5
  337. data/lib/datadog/di/probe_manager.rb +57 -9
  338. data/lib/datadog/di/probe_notification_builder.rb +46 -43
  339. data/lib/datadog/di/probe_notifier_worker.rb +62 -12
  340. data/lib/datadog/di/probe_repository.rb +1 -1
  341. data/lib/datadog/di/proc_responder.rb +1 -1
  342. data/lib/datadog/di/remote.rb +52 -8
  343. data/lib/datadog/di/serializer.rb +185 -68
  344. data/lib/datadog/di/snapshot_event.rb +38 -0
  345. data/lib/datadog/di/transport/diagnostics.rb +5 -5
  346. data/lib/datadog/di/transport/http/diagnostics.rb +3 -3
  347. data/lib/datadog/di/transport/http/input.rb +2 -2
  348. data/lib/datadog/di/transport/http.rb +23 -13
  349. data/lib/datadog/di/transport/input.rb +41 -16
  350. data/lib/datadog/di/utils.rb +4 -4
  351. data/lib/datadog/di.rb +5 -5
  352. data/lib/datadog/error_tracking/collector.rb +2 -2
  353. data/lib/datadog/error_tracking/component.rb +12 -12
  354. data/lib/datadog/error_tracking/configuration.rb +2 -2
  355. data/lib/datadog/error_tracking/ext.rb +7 -7
  356. data/lib/datadog/error_tracking/extensions.rb +2 -2
  357. data/lib/datadog/error_tracking/filters.rb +4 -4
  358. data/lib/datadog/error_tracking.rb +6 -6
  359. data/lib/datadog/kit/appsec/events/v2.rb +40 -40
  360. data/lib/datadog/kit/appsec/events.rb +25 -25
  361. data/lib/datadog/kit/enable_core_dumps.rb +2 -2
  362. data/lib/datadog/kit/identity.rb +18 -18
  363. data/lib/datadog/kit/tracing/method_tracer.rb +7 -7
  364. data/lib/datadog/kit.rb +1 -1
  365. data/lib/datadog/open_feature/activation.rb +268 -0
  366. data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
  367. data/lib/datadog/open_feature/agentless/parser.rb +43 -0
  368. data/lib/datadog/open_feature/agentless/response.rb +19 -0
  369. data/lib/datadog/open_feature/agentless/transport.rb +103 -0
  370. data/lib/datadog/open_feature/component.rb +112 -31
  371. data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
  372. data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
  373. data/lib/datadog/open_feature/configuration/source.rb +43 -0
  374. data/lib/datadog/open_feature/configuration.rb +139 -2
  375. data/lib/datadog/open_feature/evaluation_engine.rb +36 -10
  376. data/lib/datadog/open_feature/exposures/batch_builder.rb +1 -1
  377. data/lib/datadog/open_feature/exposures/buffer.rb +1 -1
  378. data/lib/datadog/open_feature/exposures/deduplicator.rb +1 -1
  379. data/lib/datadog/open_feature/exposures/event.rb +17 -10
  380. data/lib/datadog/open_feature/exposures/reporter.rb +3 -3
  381. data/lib/datadog/open_feature/exposures/worker.rb +6 -6
  382. data/lib/datadog/open_feature/ext.rb +32 -8
  383. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +242 -86
  384. data/lib/datadog/open_feature/flag_evaluation/writer.rb +211 -100
  385. data/lib/datadog/open_feature/hooks/evaluation_telemetry_opt_out_hook.rb +25 -0
  386. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +28 -24
  387. data/lib/datadog/open_feature/hooks/flag_eval_metrics_hook.rb +3 -1
  388. data/lib/datadog/open_feature/hooks/span_enrichment_hook/codec.rb +58 -0
  389. data/lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state.rb +96 -0
  390. data/lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state_store.rb +49 -0
  391. data/lib/datadog/open_feature/hooks/span_enrichment_hook.rb +184 -0
  392. data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +24 -24
  393. data/lib/datadog/open_feature/native_evaluator.rb +24 -10
  394. data/lib/datadog/open_feature/noop_evaluator.rb +8 -3
  395. data/lib/datadog/open_feature/provider.rb +357 -17
  396. data/lib/datadog/open_feature/remote.rb +7 -7
  397. data/lib/datadog/open_feature/resolution_details.rb +4 -2
  398. data/lib/datadog/open_feature/transport.rb +19 -14
  399. data/lib/datadog/open_feature.rb +33 -3
  400. data/lib/datadog/opentelemetry/api/baggage.rb +3 -3
  401. data/lib/datadog/opentelemetry/api/context.rb +4 -4
  402. data/lib/datadog/opentelemetry/configuration/settings.rb +41 -41
  403. data/lib/datadog/opentelemetry/ext.rb +1 -1
  404. data/lib/datadog/opentelemetry/logs.rb +5 -5
  405. data/lib/datadog/opentelemetry/metrics.rb +7 -7
  406. data/lib/datadog/opentelemetry/sdk/configurator.rb +8 -8
  407. data/lib/datadog/opentelemetry/sdk/id_generator.rb +2 -2
  408. data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +6 -6
  409. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +5 -5
  410. data/lib/datadog/opentelemetry/sdk/propagator.rb +8 -8
  411. data/lib/datadog/opentelemetry/sdk/span_processor.rb +15 -15
  412. data/lib/datadog/opentelemetry/sdk/trace/span.rb +32 -32
  413. data/lib/datadog/opentelemetry/sdk.rb +2 -2
  414. data/lib/datadog/opentelemetry/signal_configuration.rb +20 -16
  415. data/lib/datadog/opentelemetry/trace.rb +4 -1
  416. data/lib/datadog/opentelemetry.rb +8 -8
  417. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +10 -2
  418. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
  419. data/lib/datadog/profiling/collectors/info.rb +40 -13
  420. data/lib/datadog/profiling/collectors/thread_context.rb +6 -17
  421. data/lib/datadog/profiling/component.rb +33 -39
  422. data/lib/datadog/profiling/exporter.rb +11 -11
  423. data/lib/datadog/profiling/http_transport.rb +1 -1
  424. data/lib/datadog/profiling/sequence_tracker.rb +1 -1
  425. data/lib/datadog/profiling/tag_builder.rb +19 -19
  426. data/lib/datadog/profiling/tasks/exec.rb +1 -1
  427. data/lib/datadog/profiling.rb +31 -27
  428. data/lib/datadog/single_step_instrument.rb +1 -1
  429. data/lib/datadog/symbol_database/component.rb +15 -15
  430. data/lib/datadog/symbol_database/configuration.rb +3 -3
  431. data/lib/datadog/symbol_database/extensions.rb +2 -2
  432. data/lib/datadog/symbol_database/extractor.rb +141 -159
  433. data/lib/datadog/symbol_database/file_hash.rb +3 -3
  434. data/lib/datadog/symbol_database/logger.rb +1 -1
  435. data/lib/datadog/symbol_database/remote.rb +21 -6
  436. data/lib/datadog/symbol_database/scope.rb +2 -2
  437. data/lib/datadog/symbol_database/scope_batcher.rb +2 -2
  438. data/lib/datadog/symbol_database/service_version.rb +4 -4
  439. data/lib/datadog/symbol_database/symbol.rb +1 -1
  440. data/lib/datadog/symbol_database/transport/http/endpoint.rb +1 -1
  441. data/lib/datadog/symbol_database/transport/http.rb +6 -6
  442. data/lib/datadog/symbol_database/transport.rb +4 -4
  443. data/lib/datadog/symbol_database/uploader.rb +22 -22
  444. data/lib/datadog/symbol_database.rb +2 -2
  445. data/lib/datadog/tracing/analytics.rb +1 -1
  446. data/lib/datadog/tracing/buffer.rb +5 -5
  447. data/lib/datadog/tracing/client_ip.rb +4 -4
  448. data/lib/datadog/tracing/component.rb +51 -9
  449. data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
  450. data/lib/datadog/tracing/configuration/dynamic.rb +13 -11
  451. data/lib/datadog/tracing/configuration/ext.rb +46 -42
  452. data/lib/datadog/tracing/configuration/http.rb +2 -2
  453. data/lib/datadog/tracing/configuration/settings.rb +52 -16
  454. data/lib/datadog/tracing/context.rb +25 -3
  455. data/lib/datadog/tracing/context_provider.rb +19 -7
  456. data/lib/datadog/tracing/contrib/action_cable/configuration/settings.rb +2 -2
  457. data/lib/datadog/tracing/contrib/action_cable/event.rb +4 -4
  458. data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +5 -5
  459. data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +4 -4
  460. data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +5 -5
  461. data/lib/datadog/tracing/contrib/action_cable/events.rb +5 -5
  462. data/lib/datadog/tracing/contrib/action_cable/ext.rb +18 -18
  463. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +4 -4
  464. data/lib/datadog/tracing/contrib/action_cable/integration.rb +7 -7
  465. data/lib/datadog/tracing/contrib/action_cable/patcher.rb +4 -4
  466. data/lib/datadog/tracing/contrib/action_mailer/configuration/settings.rb +2 -2
  467. data/lib/datadog/tracing/contrib/action_mailer/event.rb +3 -3
  468. data/lib/datadog/tracing/contrib/action_mailer/events/deliver.rb +8 -8
  469. data/lib/datadog/tracing/contrib/action_mailer/events/process.rb +4 -4
  470. data/lib/datadog/tracing/contrib/action_mailer/events.rb +3 -3
  471. data/lib/datadog/tracing/contrib/action_mailer/ext.rb +18 -18
  472. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +7 -7
  473. data/lib/datadog/tracing/contrib/action_mailer/patcher.rb +3 -3
  474. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +9 -9
  475. data/lib/datadog/tracing/contrib/action_pack/action_controller/patcher.rb +2 -2
  476. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +4 -4
  477. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +4 -4
  478. data/lib/datadog/tracing/contrib/action_pack/configuration/settings.rb +3 -3
  479. data/lib/datadog/tracing/contrib/action_pack/ext.rb +10 -10
  480. data/lib/datadog/tracing/contrib/action_pack/integration.rb +7 -7
  481. data/lib/datadog/tracing/contrib/action_pack/patcher.rb +3 -3
  482. data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -1
  483. data/lib/datadog/tracing/contrib/action_view/configuration/settings.rb +3 -3
  484. data/lib/datadog/tracing/contrib/action_view/event.rb +1 -1
  485. data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +6 -6
  486. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +6 -6
  487. data/lib/datadog/tracing/contrib/action_view/events.rb +3 -3
  488. data/lib/datadog/tracing/contrib/action_view/ext.rb +10 -10
  489. data/lib/datadog/tracing/contrib/action_view/integration.rb +11 -11
  490. data/lib/datadog/tracing/contrib/action_view/patcher.rb +5 -5
  491. data/lib/datadog/tracing/contrib/action_view/utils.rb +2 -2
  492. data/lib/datadog/tracing/contrib/active_job/configuration/settings.rb +2 -2
  493. data/lib/datadog/tracing/contrib/active_job/data_streams.rb +2 -2
  494. data/lib/datadog/tracing/contrib/active_job/event.rb +3 -3
  495. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +5 -5
  496. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +5 -5
  497. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +5 -5
  498. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +5 -5
  499. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +5 -5
  500. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +5 -5
  501. data/lib/datadog/tracing/contrib/active_job/events.rb +6 -6
  502. data/lib/datadog/tracing/contrib/active_job/ext.rb +23 -23
  503. data/lib/datadog/tracing/contrib/active_job/integration.rb +7 -7
  504. data/lib/datadog/tracing/contrib/active_job/patcher.rb +7 -7
  505. data/lib/datadog/tracing/contrib/active_model_serializers/configuration/settings.rb +2 -2
  506. data/lib/datadog/tracing/contrib/active_model_serializers/event.rb +4 -4
  507. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +6 -6
  508. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +6 -6
  509. data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +3 -3
  510. data/lib/datadog/tracing/contrib/active_model_serializers/ext.rb +10 -10
  511. data/lib/datadog/tracing/contrib/active_model_serializers/integration.rb +5 -5
  512. data/lib/datadog/tracing/contrib/active_model_serializers/patcher.rb +3 -3
  513. data/lib/datadog/tracing/contrib/active_record/configuration/makara_resolver.rb +1 -1
  514. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +7 -7
  515. data/lib/datadog/tracing/contrib/active_record/configuration/settings.rb +4 -4
  516. data/lib/datadog/tracing/contrib/active_record/event.rb +1 -1
  517. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +8 -8
  518. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +10 -9
  519. data/lib/datadog/tracing/contrib/active_record/events.rb +3 -3
  520. data/lib/datadog/tracing/contrib/active_record/ext.rb +15 -15
  521. data/lib/datadog/tracing/contrib/active_record/integration.rb +11 -11
  522. data/lib/datadog/tracing/contrib/active_record/patcher.rb +2 -2
  523. data/lib/datadog/tracing/contrib/active_record/utils.rb +3 -3
  524. data/lib/datadog/tracing/contrib/active_support/cache/event.rb +1 -1
  525. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +12 -12
  526. data/lib/datadog/tracing/contrib/active_support/cache/events.rb +1 -1
  527. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +4 -4
  528. data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +5 -5
  529. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +4 -4
  530. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +3 -3
  531. data/lib/datadog/tracing/contrib/active_support/ext.rb +16 -16
  532. data/lib/datadog/tracing/contrib/active_support/integration.rb +8 -8
  533. data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +1 -1
  534. data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +2 -2
  535. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +1 -1
  536. data/lib/datadog/tracing/contrib/active_support/patcher.rb +2 -2
  537. data/lib/datadog/tracing/contrib/analytics.rb +1 -1
  538. data/lib/datadog/tracing/contrib/auto_instrument.rb +4 -4
  539. data/lib/datadog/tracing/contrib/aws/configuration/settings.rb +3 -3
  540. data/lib/datadog/tracing/contrib/aws/ext.rb +26 -26
  541. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -5
  542. data/lib/datadog/tracing/contrib/aws/integration.rb +9 -9
  543. data/lib/datadog/tracing/contrib/aws/parsed_context.rb +1 -1
  544. data/lib/datadog/tracing/contrib/aws/patcher.rb +5 -5
  545. data/lib/datadog/tracing/contrib/aws/service/dynamodb.rb +2 -2
  546. data/lib/datadog/tracing/contrib/aws/service/eventbridge.rb +2 -2
  547. data/lib/datadog/tracing/contrib/aws/service/kinesis.rb +4 -4
  548. data/lib/datadog/tracing/contrib/aws/service/s3.rb +2 -2
  549. data/lib/datadog/tracing/contrib/aws/service/sns.rb +3 -3
  550. data/lib/datadog/tracing/contrib/aws/service/sqs.rb +3 -3
  551. data/lib/datadog/tracing/contrib/aws/service/states.rb +5 -5
  552. data/lib/datadog/tracing/contrib/aws/services.rb +14 -14
  553. data/lib/datadog/tracing/contrib/concurrent_ruby/async_patch.rb +1 -1
  554. data/lib/datadog/tracing/contrib/concurrent_ruby/configuration/settings.rb +2 -2
  555. data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +1 -1
  556. data/lib/datadog/tracing/contrib/concurrent_ruby/ext.rb +2 -2
  557. data/lib/datadog/tracing/contrib/concurrent_ruby/future_patch.rb +1 -1
  558. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +6 -6
  559. data/lib/datadog/tracing/contrib/concurrent_ruby/patcher.rb +4 -4
  560. data/lib/datadog/tracing/contrib/concurrent_ruby/promises_future_patch.rb +1 -1
  561. data/lib/datadog/tracing/contrib/configurable.rb +2 -2
  562. data/lib/datadog/tracing/contrib/configuration/resolvers/pattern_resolver.rb +1 -1
  563. data/lib/datadog/tracing/contrib/configuration/settings.rb +2 -2
  564. data/lib/datadog/tracing/contrib/dalli/configuration/settings.rb +2 -2
  565. data/lib/datadog/tracing/contrib/dalli/ext.rb +13 -13
  566. data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +5 -5
  567. data/lib/datadog/tracing/contrib/dalli/integration.rb +7 -7
  568. data/lib/datadog/tracing/contrib/dalli/patcher.rb +3 -3
  569. data/lib/datadog/tracing/contrib/dalli/quantize.rb +2 -2
  570. data/lib/datadog/tracing/contrib/delayed_job/configuration/settings.rb +2 -2
  571. data/lib/datadog/tracing/contrib/delayed_job/ext.rb +14 -14
  572. data/lib/datadog/tracing/contrib/delayed_job/integration.rb +5 -5
  573. data/lib/datadog/tracing/contrib/delayed_job/patcher.rb +3 -3
  574. data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +5 -5
  575. data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +2 -2
  576. data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +15 -15
  577. data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +6 -6
  578. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +10 -10
  579. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +3 -3
  580. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +2 -2
  581. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +18 -13
  582. data/lib/datadog/tracing/contrib/ethon/ext.rb +13 -13
  583. data/lib/datadog/tracing/contrib/ethon/integration.rb +6 -6
  584. data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +3 -3
  585. data/lib/datadog/tracing/contrib/ethon/patcher.rb +3 -3
  586. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +5 -5
  587. data/lib/datadog/tracing/contrib/excon/ext.rb +11 -11
  588. data/lib/datadog/tracing/contrib/excon/integration.rb +6 -6
  589. data/lib/datadog/tracing/contrib/excon/middleware.rb +17 -10
  590. data/lib/datadog/tracing/contrib/excon/patcher.rb +2 -2
  591. data/lib/datadog/tracing/contrib/ext.rb +18 -18
  592. data/lib/datadog/tracing/contrib/extensions.rb +2 -2
  593. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +4 -4
  594. data/lib/datadog/tracing/contrib/faraday/ext.rb +11 -11
  595. data/lib/datadog/tracing/contrib/faraday/integration.rb +6 -6
  596. data/lib/datadog/tracing/contrib/faraday/middleware.rb +17 -11
  597. data/lib/datadog/tracing/contrib/faraday/patcher.rb +6 -6
  598. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +4 -4
  599. data/lib/datadog/tracing/contrib/grape/endpoint.rb +51 -24
  600. data/lib/datadog/tracing/contrib/grape/ext.rb +15 -15
  601. data/lib/datadog/tracing/contrib/grape/instrumentation.rb +3 -3
  602. data/lib/datadog/tracing/contrib/grape/integration.rb +5 -5
  603. data/lib/datadog/tracing/contrib/grape/patcher.rb +5 -5
  604. data/lib/datadog/tracing/contrib/graphql/configuration/error_extension_env_parser.rb +1 -1
  605. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +3 -3
  606. data/lib/datadog/tracing/contrib/graphql/ext.rb +9 -9
  607. data/lib/datadog/tracing/contrib/graphql/integration.rb +9 -9
  608. data/lib/datadog/tracing/contrib/graphql/patcher.rb +7 -7
  609. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +37 -37
  610. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +2 -2
  611. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +2 -2
  612. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +8 -8
  613. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +8 -8
  614. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor.rb +6 -6
  615. data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +1 -1
  616. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +9 -9
  617. data/lib/datadog/tracing/contrib/grpc/ext.rb +14 -14
  618. data/lib/datadog/tracing/contrib/grpc/formatting.rb +7 -7
  619. data/lib/datadog/tracing/contrib/grpc/integration.rb +5 -5
  620. data/lib/datadog/tracing/contrib/grpc/intercept_with_datadog.rb +1 -1
  621. data/lib/datadog/tracing/contrib/grpc/patcher.rb +3 -3
  622. data/lib/datadog/tracing/contrib/grpc.rb +6 -6
  623. data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +3 -3
  624. data/lib/datadog/tracing/contrib/hanami/configuration/settings.rb +2 -2
  625. data/lib/datadog/tracing/contrib/hanami/ext.rb +8 -8
  626. data/lib/datadog/tracing/contrib/hanami/integration.rb +6 -6
  627. data/lib/datadog/tracing/contrib/hanami/patcher.rb +5 -5
  628. data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +3 -3
  629. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +5 -4
  630. data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +1 -1
  631. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +4 -4
  632. data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +3 -3
  633. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +8 -8
  634. data/lib/datadog/tracing/contrib/http/ext.rb +11 -11
  635. data/lib/datadog/tracing/contrib/http/instrumentation.rb +22 -7
  636. data/lib/datadog/tracing/contrib/http/integration.rb +7 -7
  637. data/lib/datadog/tracing/contrib/http/patcher.rb +3 -3
  638. data/lib/datadog/tracing/contrib/http.rb +6 -6
  639. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +4 -4
  640. data/lib/datadog/tracing/contrib/httpclient/ext.rb +11 -11
  641. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +13 -7
  642. data/lib/datadog/tracing/contrib/httpclient/integration.rb +6 -6
  643. data/lib/datadog/tracing/contrib/httpclient/patcher.rb +3 -3
  644. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +4 -4
  645. data/lib/datadog/tracing/contrib/httprb/ext.rb +11 -11
  646. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +16 -10
  647. data/lib/datadog/tracing/contrib/httprb/integration.rb +7 -7
  648. data/lib/datadog/tracing/contrib/httprb/patcher.rb +3 -3
  649. data/lib/datadog/tracing/contrib/integration.rb +3 -3
  650. data/lib/datadog/tracing/contrib/kafka/configuration/settings.rb +2 -2
  651. data/lib/datadog/tracing/contrib/kafka/event.rb +3 -3
  652. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +7 -3
  653. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +4 -4
  654. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +4 -4
  655. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +5 -5
  656. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/join_group.rb +5 -5
  657. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/leave_group.rb +5 -5
  658. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/sync_group.rb +5 -5
  659. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +10 -5
  660. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +10 -5
  661. data/lib/datadog/tracing/contrib/kafka/events.rb +10 -10
  662. data/lib/datadog/tracing/contrib/kafka/ext.rb +40 -40
  663. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +2 -2
  664. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +4 -4
  665. data/lib/datadog/tracing/contrib/kafka/integration.rb +6 -6
  666. data/lib/datadog/tracing/contrib/kafka/patcher.rb +5 -5
  667. data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +2 -2
  668. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +9 -9
  669. data/lib/datadog/tracing/contrib/karafka/ext.rb +13 -13
  670. data/lib/datadog/tracing/contrib/karafka/integration.rb +5 -5
  671. data/lib/datadog/tracing/contrib/karafka/monitor.rb +14 -14
  672. data/lib/datadog/tracing/contrib/karafka/patcher.rb +8 -8
  673. data/lib/datadog/tracing/contrib/karafka.rb +6 -6
  674. data/lib/datadog/tracing/contrib/lograge/configuration/settings.rb +2 -2
  675. data/lib/datadog/tracing/contrib/lograge/ext.rb +1 -1
  676. data/lib/datadog/tracing/contrib/lograge/integration.rb +5 -5
  677. data/lib/datadog/tracing/contrib/lograge/patcher.rb +9 -9
  678. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +2 -2
  679. data/lib/datadog/tracing/contrib/mongodb/ext.rb +18 -18
  680. data/lib/datadog/tracing/contrib/mongodb/instrumentation.rb +3 -3
  681. data/lib/datadog/tracing/contrib/mongodb/integration.rb +7 -7
  682. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +5 -5
  683. data/lib/datadog/tracing/contrib/mongodb/patcher.rb +3 -3
  684. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +13 -13
  685. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +3 -3
  686. data/lib/datadog/tracing/contrib/mysql2/ext.rb +11 -11
  687. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +6 -6
  688. data/lib/datadog/tracing/contrib/mysql2/integration.rb +5 -5
  689. data/lib/datadog/tracing/contrib/mysql2/patcher.rb +2 -2
  690. data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +2 -2
  691. data/lib/datadog/tracing/contrib/opensearch/ext.rb +23 -23
  692. data/lib/datadog/tracing/contrib/opensearch/integration.rb +6 -6
  693. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -13
  694. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +4 -4
  695. data/lib/datadog/tracing/contrib/patchable.rb +1 -1
  696. data/lib/datadog/tracing/contrib/patcher.rb +3 -3
  697. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +3 -3
  698. data/lib/datadog/tracing/contrib/pg/ext.rb +19 -19
  699. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +6 -6
  700. data/lib/datadog/tracing/contrib/pg/integration.rb +5 -5
  701. data/lib/datadog/tracing/contrib/pg/patcher.rb +2 -2
  702. data/lib/datadog/tracing/contrib/presto/configuration/settings.rb +2 -2
  703. data/lib/datadog/tracing/contrib/presto/ext.rb +21 -21
  704. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +2 -2
  705. data/lib/datadog/tracing/contrib/presto/integration.rb +6 -6
  706. data/lib/datadog/tracing/contrib/presto/patcher.rb +4 -4
  707. data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +2 -2
  708. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +17 -17
  709. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +1 -1
  710. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +5 -5
  711. data/lib/datadog/tracing/contrib/que/configuration/settings.rb +2 -2
  712. data/lib/datadog/tracing/contrib/que/ext.rb +19 -19
  713. data/lib/datadog/tracing/contrib/que/integration.rb +6 -6
  714. data/lib/datadog/tracing/contrib/que/patcher.rb +1 -1
  715. data/lib/datadog/tracing/contrib/que/tracer.rb +2 -2
  716. data/lib/datadog/tracing/contrib/racecar/configuration/settings.rb +3 -3
  717. data/lib/datadog/tracing/contrib/racecar/event.rb +4 -4
  718. data/lib/datadog/tracing/contrib/racecar/events/batch.rb +3 -3
  719. data/lib/datadog/tracing/contrib/racecar/events/consume.rb +3 -3
  720. data/lib/datadog/tracing/contrib/racecar/events/message.rb +3 -3
  721. data/lib/datadog/tracing/contrib/racecar/events.rb +4 -4
  722. data/lib/datadog/tracing/contrib/racecar/ext.rb +18 -18
  723. data/lib/datadog/tracing/contrib/racecar/integration.rb +5 -5
  724. data/lib/datadog/tracing/contrib/racecar/patcher.rb +3 -3
  725. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +3 -3
  726. data/lib/datadog/tracing/contrib/rack/ext.rb +33 -33
  727. data/lib/datadog/tracing/contrib/rack/header_collection.rb +3 -3
  728. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +4 -4
  729. data/lib/datadog/tracing/contrib/rack/integration.rb +6 -6
  730. data/lib/datadog/tracing/contrib/rack/middlewares.rb +24 -24
  731. data/lib/datadog/tracing/contrib/rack/patcher.rb +3 -3
  732. data/lib/datadog/tracing/contrib/rack/request_queue.rb +3 -3
  733. data/lib/datadog/tracing/contrib/rack/route_inference.rb +11 -11
  734. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +16 -16
  735. data/lib/datadog/tracing/contrib/rails/auto_instrument_railtie.rb +1 -1
  736. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +3 -3
  737. data/lib/datadog/tracing/contrib/rails/ext.rb +15 -15
  738. data/lib/datadog/tracing/contrib/rails/framework.rb +11 -11
  739. data/lib/datadog/tracing/contrib/rails/integration.rb +5 -5
  740. data/lib/datadog/tracing/contrib/rails/middlewares.rb +1 -1
  741. data/lib/datadog/tracing/contrib/rails/patcher.rb +8 -8
  742. data/lib/datadog/tracing/contrib/rails/railtie.rb +4 -4
  743. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  744. data/lib/datadog/tracing/contrib/rake/configuration/settings.rb +3 -3
  745. data/lib/datadog/tracing/contrib/rake/ext.rb +12 -12
  746. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +4 -4
  747. data/lib/datadog/tracing/contrib/rake/integration.rb +5 -5
  748. data/lib/datadog/tracing/contrib/rake/patcher.rb +3 -3
  749. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +3 -3
  750. data/lib/datadog/tracing/contrib/redis/configuration/settings.rb +2 -2
  751. data/lib/datadog/tracing/contrib/redis/ext.rb +16 -16
  752. data/lib/datadog/tracing/contrib/redis/instrumentation.rb +6 -6
  753. data/lib/datadog/tracing/contrib/redis/integration.rb +7 -7
  754. data/lib/datadog/tracing/contrib/redis/patcher.rb +10 -10
  755. data/lib/datadog/tracing/contrib/redis/quantize.rb +39 -5
  756. data/lib/datadog/tracing/contrib/redis/tags.rb +5 -5
  757. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +14 -8
  758. data/lib/datadog/tracing/contrib/resque/configuration/settings.rb +2 -2
  759. data/lib/datadog/tracing/contrib/resque/ext.rb +7 -7
  760. data/lib/datadog/tracing/contrib/resque/integration.rb +5 -5
  761. data/lib/datadog/tracing/contrib/resque/patcher.rb +3 -3
  762. data/lib/datadog/tracing/contrib/resque/resque_job.rb +5 -5
  763. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +2 -2
  764. data/lib/datadog/tracing/contrib/rest_client/ext.rb +10 -10
  765. data/lib/datadog/tracing/contrib/rest_client/integration.rb +6 -6
  766. data/lib/datadog/tracing/contrib/rest_client/patcher.rb +2 -2
  767. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +15 -8
  768. data/lib/datadog/tracing/contrib/roda/configuration/settings.rb +2 -2
  769. data/lib/datadog/tracing/contrib/roda/ext.rb +6 -6
  770. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +4 -4
  771. data/lib/datadog/tracing/contrib/roda/integration.rb +6 -6
  772. data/lib/datadog/tracing/contrib/roda/patcher.rb +3 -3
  773. data/lib/datadog/tracing/contrib/semantic_logger/configuration/settings.rb +2 -2
  774. data/lib/datadog/tracing/contrib/semantic_logger/ext.rb +1 -1
  775. data/lib/datadog/tracing/contrib/semantic_logger/integration.rb +5 -5
  776. data/lib/datadog/tracing/contrib/semantic_logger/patcher.rb +2 -2
  777. data/lib/datadog/tracing/contrib/sequel/configuration/settings.rb +2 -2
  778. data/lib/datadog/tracing/contrib/sequel/database.rb +6 -10
  779. data/lib/datadog/tracing/contrib/sequel/dataset.rb +5 -5
  780. data/lib/datadog/tracing/contrib/sequel/ext.rb +12 -8
  781. data/lib/datadog/tracing/contrib/sequel/integration.rb +5 -5
  782. data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
  783. data/lib/datadog/tracing/contrib/sequel/patcher.rb +3 -3
  784. data/lib/datadog/tracing/contrib/sequel/utils.rb +101 -9
  785. data/lib/datadog/tracing/contrib/shoryuken/configuration/settings.rb +2 -2
  786. data/lib/datadog/tracing/contrib/shoryuken/ext.rb +12 -12
  787. data/lib/datadog/tracing/contrib/shoryuken/integration.rb +6 -6
  788. data/lib/datadog/tracing/contrib/shoryuken/patcher.rb +1 -1
  789. data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +2 -2
  790. data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +8 -8
  791. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +2 -2
  792. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +10 -10
  793. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +30 -30
  794. data/lib/datadog/tracing/contrib/sidekiq/integration.rb +7 -7
  795. data/lib/datadog/tracing/contrib/sidekiq/patcher.rb +8 -8
  796. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +13 -13
  797. data/lib/datadog/tracing/contrib/sidekiq/utils.rb +9 -9
  798. data/lib/datadog/tracing/contrib/sidekiq.rb +6 -6
  799. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +3 -3
  800. data/lib/datadog/tracing/contrib/sinatra/env.rb +6 -6
  801. data/lib/datadog/tracing/contrib/sinatra/ext.rb +17 -17
  802. data/lib/datadog/tracing/contrib/sinatra/framework.rb +1 -1
  803. data/lib/datadog/tracing/contrib/sinatra/integration.rb +5 -5
  804. data/lib/datadog/tracing/contrib/sinatra/patcher.rb +5 -5
  805. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +10 -10
  806. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +8 -8
  807. data/lib/datadog/tracing/contrib/sneakers/configuration/settings.rb +1 -1
  808. data/lib/datadog/tracing/contrib/sneakers/ext.rb +12 -12
  809. data/lib/datadog/tracing/contrib/sneakers/integration.rb +7 -7
  810. data/lib/datadog/tracing/contrib/sneakers/patcher.rb +2 -2
  811. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +4 -4
  812. data/lib/datadog/tracing/contrib/span_attribute_schema.rb +2 -2
  813. data/lib/datadog/tracing/contrib/status_range_env_parser.rb +2 -2
  814. data/lib/datadog/tracing/contrib/status_range_matcher.rb +1 -1
  815. data/lib/datadog/tracing/contrib/stripe/configuration/settings.rb +2 -2
  816. data/lib/datadog/tracing/contrib/stripe/ext.rb +12 -12
  817. data/lib/datadog/tracing/contrib/stripe/integration.rb +5 -5
  818. data/lib/datadog/tracing/contrib/stripe/patcher.rb +2 -2
  819. data/lib/datadog/tracing/contrib/stripe/request.rb +3 -3
  820. data/lib/datadog/tracing/contrib/sucker_punch/configuration/settings.rb +2 -2
  821. data/lib/datadog/tracing/contrib/sucker_punch/exception_handler.rb +1 -1
  822. data/lib/datadog/tracing/contrib/sucker_punch/ext.rb +13 -13
  823. data/lib/datadog/tracing/contrib/sucker_punch/instrumentation.rb +4 -4
  824. data/lib/datadog/tracing/contrib/sucker_punch/integration.rb +5 -5
  825. data/lib/datadog/tracing/contrib/sucker_punch/patcher.rb +4 -4
  826. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +3 -3
  827. data/lib/datadog/tracing/contrib/trilogy/ext.rb +11 -11
  828. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +6 -6
  829. data/lib/datadog/tracing/contrib/trilogy/integration.rb +5 -5
  830. data/lib/datadog/tracing/contrib/trilogy/patcher.rb +2 -2
  831. data/lib/datadog/tracing/contrib/utils/database.rb +5 -5
  832. data/lib/datadog/tracing/contrib/utils/quantization/hash_formatter.rb +2 -2
  833. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +118 -13
  834. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +2 -2
  835. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +9 -9
  836. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +4 -4
  837. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +5 -5
  838. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +2 -2
  839. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +7 -7
  840. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +3 -3
  841. data/lib/datadog/tracing/contrib/waterdrop.rb +6 -6
  842. data/lib/datadog/tracing/contrib.rb +54 -54
  843. data/lib/datadog/tracing/correlation.rb +12 -12
  844. data/lib/datadog/tracing/diagnostics/environment_logger.rb +12 -11
  845. data/lib/datadog/tracing/diagnostics/ext.rb +19 -19
  846. data/lib/datadog/tracing/diagnostics/health.rb +1 -1
  847. data/lib/datadog/tracing/distributed/b3_multi.rb +8 -8
  848. data/lib/datadog/tracing/distributed/b3_single.rb +4 -4
  849. data/lib/datadog/tracing/distributed/baggage.rb +27 -29
  850. data/lib/datadog/tracing/distributed/datadog.rb +23 -23
  851. data/lib/datadog/tracing/distributed/fetcher.rb +1 -1
  852. data/lib/datadog/tracing/distributed/helpers.rb +3 -3
  853. data/lib/datadog/tracing/distributed/propagation.rb +15 -12
  854. data/lib/datadog/tracing/distributed/propagation_policy.rb +5 -0
  855. data/lib/datadog/tracing/distributed/trace_context.rb +21 -278
  856. data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
  857. data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
  858. data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
  859. data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
  860. data/lib/datadog/tracing/event.rb +1 -1
  861. data/lib/datadog/tracing/ext.rb +1 -1
  862. data/lib/datadog/tracing/metadata/analytics.rb +2 -2
  863. data/lib/datadog/tracing/metadata/errors.rb +4 -4
  864. data/lib/datadog/tracing/metadata/ext.rb +72 -69
  865. data/lib/datadog/tracing/metadata/metastruct.rb +1 -1
  866. data/lib/datadog/tracing/metadata/metastruct_tagging.rb +1 -1
  867. data/lib/datadog/tracing/metadata/tagging.rb +3 -3
  868. data/lib/datadog/tracing/metadata.rb +4 -4
  869. data/lib/datadog/tracing/otel_thread_context.rb +97 -0
  870. data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
  871. data/lib/datadog/tracing/pipeline.rb +2 -2
  872. data/lib/datadog/tracing/remote.rb +250 -44
  873. data/lib/datadog/tracing/sampling/all_sampler.rb +1 -1
  874. data/lib/datadog/tracing/sampling/ext.rb +8 -8
  875. data/lib/datadog/tracing/sampling/matcher.rb +4 -4
  876. data/lib/datadog/tracing/sampling/priority_sampler.rb +4 -4
  877. data/lib/datadog/tracing/sampling/rate_by_key_sampler.rb +3 -3
  878. data/lib/datadog/tracing/sampling/rate_by_service_sampler.rb +2 -2
  879. data/lib/datadog/tracing/sampling/rate_sampler.rb +2 -2
  880. data/lib/datadog/tracing/sampling/rule.rb +4 -4
  881. data/lib/datadog/tracing/sampling/rule_sampler.rb +14 -12
  882. data/lib/datadog/tracing/sampling/sampler.rb +2 -2
  883. data/lib/datadog/tracing/sampling/span/ext.rb +3 -3
  884. data/lib/datadog/tracing/sampling/span/matcher.rb +2 -2
  885. data/lib/datadog/tracing/sampling/span/rule.rb +1 -1
  886. data/lib/datadog/tracing/sampling/span/rule_parser.rb +8 -8
  887. data/lib/datadog/tracing/span.rb +9 -9
  888. data/lib/datadog/tracing/span_event.rb +5 -5
  889. data/lib/datadog/tracing/span_operation.rb +22 -22
  890. data/lib/datadog/tracing/sync_writer.rb +12 -7
  891. data/lib/datadog/tracing/trace_digest.rb +22 -4
  892. data/lib/datadog/tracing/trace_operation.rb +33 -21
  893. data/lib/datadog/tracing/trace_segment.rb +10 -10
  894. data/lib/datadog/tracing/tracer.rb +43 -22
  895. data/lib/datadog/tracing/transport/http/client.rb +4 -4
  896. data/lib/datadog/tracing/transport/http/statistics.rb +2 -2
  897. data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
  898. data/lib/datadog/tracing/transport/http/traces.rb +10 -8
  899. data/lib/datadog/tracing/transport/http.rb +10 -10
  900. data/lib/datadog/tracing/transport/io/client.rb +2 -2
  901. data/lib/datadog/tracing/transport/io/response.rb +1 -1
  902. data/lib/datadog/tracing/transport/io/traces.rb +5 -5
  903. data/lib/datadog/tracing/transport/io.rb +3 -3
  904. data/lib/datadog/tracing/transport/native/response.rb +71 -0
  905. data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
  906. data/lib/datadog/tracing/transport/native.rb +395 -0
  907. data/lib/datadog/tracing/transport/serializable_trace.rb +19 -19
  908. data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
  909. data/lib/datadog/tracing/transport/statistics.rb +2 -2
  910. data/lib/datadog/tracing/transport/telemetry.rb +62 -0
  911. data/lib/datadog/tracing/transport/trace_formatter.rb +23 -13
  912. data/lib/datadog/tracing/transport/traces.rb +93 -46
  913. data/lib/datadog/tracing/utils.rb +2 -2
  914. data/lib/datadog/tracing/workers.rb +2 -2
  915. data/lib/datadog/tracing/writer.rb +22 -9
  916. data/lib/datadog/tracing.rb +3 -3
  917. data/lib/datadog/version.rb +4 -4
  918. data/lib/datadog.rb +13 -13
  919. metadata +55 -13
  920. data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
  921. data/lib/datadog/appsec/utils/hash_coercion.rb +0 -23
  922. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
@@ -42,6 +42,7 @@
42
42
  //
43
43
  // When `thread_context_collector_on_gc_start` gets called, the current cpu and wall-time get recorded to the thread
44
44
  // context: `cpu_time_at_gc_start_ns` and `wall_time_at_gc_start_ns`.
45
+ // (Ruby won't switch threads during GC, so `gc_start` and `gc_finish` will happen on the same thread)
45
46
  //
46
47
  // While `cpu_time_at_gc_start_ns` is set, we don't expect the thread to be sampled: the VM is doing GC
47
48
  // on the thread holding the GVL so no other samples can/will be triggered until GC finishes.
@@ -74,6 +75,11 @@
74
75
  #define THREAD_ID_LIMIT_CHARS 44 // Why 44? "#{2**64} (#{2**64})".size + 1 for \0
75
76
  #define THREAD_INVOKE_LOCATION_LIMIT_CHARS 512
76
77
  #define MISSING_TRACER_CONTEXT_KEY 0
78
+
79
+ // Used as markers in the code
80
+ #define OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED INT2FIX(1)
81
+ #define OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED INT2FIX(2)
82
+
77
83
  #define TIME_BETWEEN_GC_EVENTS_NS MILLIS_AS_NS(10)
78
84
  #define GVL_SUSPENDED ((uint64_t)1)
79
85
  #define GVL_RUNNING ((uint64_t)0)
@@ -106,7 +112,7 @@ static ID otel_fiber_context_storage_id; // id of :@opentelemetry_context in Rub
106
112
 
107
113
  // This is mutable and gets set last-writer-wins style by
108
114
  // `thread_context_collector_reset_all_per_thread_contexts`, which is called whenever
109
- // profiling is starting or restating.
115
+ // profiling is starting or restarting.
110
116
  //
111
117
  // Note: We must be careful to not change this value while the profiler is still running,
112
118
  // otherwise new threads can get the new value and cause profiling to stop with an
@@ -148,17 +154,18 @@ typedef struct {
148
154
  // Used to identify the main thread, to give it a fallback name
149
155
  VALUE main_thread;
150
156
  // Used when extracting trace identifiers from otel spans. Lazily initialized.
151
- // Qtrue serves as a marker we've not yet extracted it; when we try to extract it, we set it to an object if
152
- // successful and Qnil if not.
157
+ // * `OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED`: we've not needed it yet
158
+ // * `OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED`: a sample needed it, and it's waiting to be extracted
159
+ // * anything else: the extracted value, or Qnil if we tried to extract it and failed
153
160
  VALUE otel_current_span_key;
154
161
  // Used to enable native filenames in stack traces
155
162
  bool native_filenames_enabled;
163
+ // Used to include class/module names in method names (e.g. "Foo#bar" instead of "bar")
164
+ bool show_classes;
156
165
  // Used to cache native filename lookup results (Map[void *function_pointer, char *filename])
157
166
  st_table *native_filenames_cache;
158
167
  // Used to attribute overhead during sampling to this component
159
168
  VALUE overhead_filename;
160
- // Minimum duration of a "Waiting for GVL" period to trigger a sample
161
- uint32_t waiting_for_gvl_threshold_ns;
162
169
 
163
170
  struct stats {
164
171
  // Track how many regular samples we've taken. Does not include garbage collection samples.
@@ -167,6 +174,8 @@ typedef struct {
167
174
  unsigned int gc_samples;
168
175
  // See thread_context_collector_on_gc_start for details
169
176
  unsigned int gc_samples_missed_due_to_missing_context;
177
+ // See thread_context_collector_sample_after_gc for details
178
+ unsigned int gc_samples_skipped_nothing_to_flush;
170
179
  // How many per-thread samples were skipped because the thread has been continuously suspended
171
180
  // (no GVL) since its previous sample, so its Ruby stack cannot have changed.
172
181
  unsigned int inactive_thread_samples_skipped;
@@ -260,7 +269,7 @@ struct per_thread_context {
260
269
  bool was_skipped_at_last_sample;
261
270
  // Set as true for CpuAndWallTimeWorker and IdleSamplingHelper threads.
262
271
  // When true, per-tick samples are skipped entirely; the thread is sampled only once per
263
- // reporting period during the on_serialize flush.
272
+ // reporting period during prepare_serialize.
264
273
  //
265
274
  // These threads are always in native code so their stacks aren't interesting;
266
275
  // the Profiling::Scheduler thread on the other hand does a lot of different
@@ -298,12 +307,11 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
298
307
  static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE allow_exception);
299
308
  static VALUE _native_on_gc_start(VALUE self, VALUE collector_instance);
300
309
  static VALUE _native_on_gc_finish(VALUE self, VALUE collector_instance);
301
- static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE allow_exception);
310
+ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
302
311
  static void update_metrics_and_sample(
303
312
  thread_context_collector_state *state,
304
313
  VALUE thread_being_sampled,
305
314
  per_thread_context *thread_context,
306
- long current_cpu_time_ns,
307
315
  long current_monotonic_wall_time_ns,
308
316
  bool force_sample
309
317
  );
@@ -315,13 +323,12 @@ static void trigger_sample_for_thread(
315
323
  long current_monotonic_wall_time_ns,
316
324
  ddog_CharSlice *ruby_vm_type,
317
325
  ddog_CharSlice *class_name,
318
- bool is_gvl_waiting_state,
319
- bool is_safe_to_allocate_objects
326
+ bool is_gvl_waiting_state
320
327
  );
321
328
  static VALUE _native_thread_list(VALUE self);
322
329
  static void check_frozen_thread(VALUE thread);
323
- static per_thread_context *get_or_create_context_for(VALUE thread);
324
- static void initialize_context(VALUE thread, per_thread_context *thread_context);
330
+ static per_thread_context *get_or_create_context_for(VALUE thread, long current_monotonic_wall_time_ns);
331
+ static void initialize_context(VALUE thread, per_thread_context *thread_context, long current_monotonic_wall_time_ns);
325
332
  static VALUE _native_inspect(VALUE self, VALUE collector_instance);
326
333
  static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context);
327
334
  static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash);
@@ -336,8 +343,7 @@ static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
336
343
  static void trace_identifiers_for(
337
344
  thread_context_collector_state *state,
338
345
  VALUE thread,
339
- trace_identifiers *trace_identifiers_result,
340
- bool is_safe_to_allocate_objects
346
+ trace_identifiers *trace_identifiers_result
341
347
  );
342
348
  static bool should_collect_resource(VALUE root_span);
343
349
  static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
@@ -351,8 +357,7 @@ static void ddtrace_otel_trace_identifiers_for(
351
357
  VALUE *root_span,
352
358
  VALUE *numeric_span_id,
353
359
  VALUE active_span,
354
- VALUE otel_values,
355
- bool is_safe_to_allocate_objects
360
+ VALUE otel_values
356
361
  );
357
362
  static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples);
358
363
  static bool handle_gvl_waiting(
@@ -365,22 +370,24 @@ static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread);
365
370
  static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread);
366
371
  #ifndef NO_GVL_INSTRUMENTATION
367
372
  static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread);
368
- static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
369
- static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE allow_exception);
373
+ static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE waiting_for_gvl_threshold_ns);
374
+ static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
370
375
  #endif
371
- static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns);
376
+ static VALUE _native_apply_delta_to_time_at_previous_sample_ns(int argc, VALUE *argv, DDTRACE_UNUSED VALUE self);
377
+ static VALUE _native_apply_delta_to_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns);
372
378
  static void otel_without_ddtrace_trace_identifiers_for(
373
379
  thread_context_collector_state *state,
374
380
  VALUE thread,
375
- trace_identifiers *trace_identifiers_result,
376
- bool is_safe_to_allocate_objects
381
+ trace_identifiers *trace_identifiers_result
377
382
  );
378
383
  static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
384
+ static bool is_otel_current_span_key_needed(thread_context_collector_state *state);
379
385
  static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
380
386
  static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
381
387
  static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
382
388
  static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self);
383
389
  static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread);
390
+ static VALUE _native_prepare_serialize(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
384
391
  static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
385
392
  static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
386
393
  static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample);
@@ -410,7 +417,7 @@ void collectors_thread_context_init(VALUE profiling_module) {
410
417
  rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
411
418
  rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
412
419
  rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
413
- rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 2);
420
+ rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 1);
414
421
  rb_define_singleton_method(testing_module, "_native_thread_list", _native_thread_list, 0);
415
422
  rb_define_singleton_method(testing_module, "_native_per_thread_context", _native_per_thread_context, 1);
416
423
  rb_define_singleton_method(testing_module, "_native_stats", _native_stats, 1);
@@ -422,14 +429,16 @@ void collectors_thread_context_init(VALUE profiling_module) {
422
429
  rb_define_singleton_method(testing_module, "_native_remove_per_thread_context_for", _native_remove_per_thread_context_for, 1);
423
430
  rb_define_singleton_method(testing_module, "_native_global_reset_per_thread_context", _native_global_reset_per_thread_context, 1);
424
431
  rb_define_singleton_method(testing_module, "_native_mark_thread_as_profiler_internal", _native_mark_thread_as_profiler_internal, 1);
432
+ rb_define_singleton_method(testing_module, "_native_prepare_serialize", _native_prepare_serialize, 1);
425
433
  rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
426
434
  rb_define_singleton_method(testing_module, "_native_on_gvl_released", _native_on_gvl_released, 1);
427
435
  #ifndef NO_GVL_INSTRUMENTATION
428
436
  rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
429
437
  rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 2);
430
- rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 3);
438
+ rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 2);
431
439
  #endif
432
- rb_define_singleton_method(testing_module, "_native_apply_delta_to_cpu_time_at_previous_sample_ns", _native_apply_delta_to_cpu_time_at_previous_sample_ns, 2);
440
+ rb_define_singleton_method(testing_module, "_native_apply_delta_to_time_at_previous_sample_ns", _native_apply_delta_to_time_at_previous_sample_ns, -1);
441
+ rb_define_singleton_method(testing_module, "_native_apply_delta_to_gvl_waiting_at_for", _native_apply_delta_to_gvl_waiting_at_for, 2);
433
442
 
434
443
  at_active_span_id = rb_intern_const("@active_span");
435
444
  at_active_trace_id = rb_intern_const("@active_trace");
@@ -493,7 +502,7 @@ static void thread_context_collector_typed_data_free(void *state_ptr) {
493
502
 
494
503
  // Important: Remember that we're only guaranteed to see here what's been set in _native_new, aka
495
504
  // pointers that have been set NULL there may still be NULL here.
496
- if (state->locations.ptr != NULL) ruby_xfree(state->locations.ptr);
505
+ sample_locations_free(&state->locations);
497
506
 
498
507
  st_free_table(state->native_filenames_cache);
499
508
 
@@ -550,21 +559,20 @@ static VALUE _native_new(VALUE klass) {
550
559
  // being leaked.
551
560
 
552
561
  // Update this when modifying state struct
553
- state->locations.ptr = NULL;
554
- state->locations.len = 0;
555
562
  state->recorder_instance = Qnil;
556
563
  state->tracer_context_key = MISSING_TRACER_CONTEXT_KEY;
557
564
  VALUE thread_list_buffer = rb_ary_new();
558
565
  state->thread_list_buffer = thread_list_buffer;
559
566
  state->endpoint_collection_enabled = true;
560
567
  state->native_filenames_enabled = false;
568
+ state->show_classes = false;
561
569
  state->native_filenames_cache = st_init_numtable();
562
570
  state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
563
571
  state->otel_context_source = OTEL_CONTEXT_SOURCE_UNKNOWN;
564
572
  state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
565
573
  VALUE main_thread = rb_thread_main();
566
574
  state->main_thread = main_thread;
567
- state->otel_current_span_key = Qtrue;
575
+ state->otel_current_span_key = OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED;
568
576
  state->gc_tracking.wall_time_at_previous_gc_ns = INVALID_TIME;
569
577
  state->gc_tracking.wall_time_at_last_flushed_gc_event_ns = 0;
570
578
 
@@ -590,15 +598,15 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
590
598
  VALUE max_frames = rb_hash_fetch(options, ID2SYM(rb_intern("max_frames")));
591
599
  VALUE tracer_context_key = rb_hash_fetch(options, ID2SYM(rb_intern("tracer_context_key")));
592
600
  VALUE endpoint_collection_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("endpoint_collection_enabled")));
593
- VALUE waiting_for_gvl_threshold_ns = rb_hash_fetch(options, ID2SYM(rb_intern("waiting_for_gvl_threshold_ns")));
594
601
  VALUE otel_context_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("otel_context_enabled")));
595
602
  VALUE native_filenames_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("native_filenames_enabled")));
603
+ VALUE show_classes = rb_hash_fetch(options, ID2SYM(rb_intern("show_classes")));
596
604
  VALUE overhead_filename = rb_hash_fetch(options, ID2SYM(rb_intern("overhead_filename")));
597
605
 
598
606
  ENFORCE_TYPE(max_frames, T_FIXNUM);
599
607
  ENFORCE_BOOLEAN(endpoint_collection_enabled);
600
- ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
601
608
  ENFORCE_BOOLEAN(native_filenames_enabled);
609
+ ENFORCE_BOOLEAN(show_classes);
602
610
  ENFORCE_TYPE(overhead_filename, T_STRING);
603
611
 
604
612
  uint16_t max_frame_int = sampling_buffer_check_max_frames(NUM2INT(max_frames));
@@ -607,12 +615,11 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
607
615
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
608
616
 
609
617
  // Update this when modifying state struct
610
- state->locations.len = max_frame_int;
611
- state->locations.ptr = ruby_xcalloc(max_frame_int, sizeof(ddog_prof_Location));
618
+ sample_locations_initialize(&state->locations, max_frame_int, show_classes == Qtrue);
612
619
  state->recorder_instance = enforce_recorder_instance(recorder_instance);
613
- recorder_install_on_serialize(recorder_instance, self_instance);
614
620
  state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
615
621
  state->native_filenames_enabled = (native_filenames_enabled == Qtrue);
622
+ state->show_classes = (show_classes == Qtrue);
616
623
  state->overhead_filename = overhead_filename;
617
624
  if (otel_context_enabled == Qfalse || otel_context_enabled == Qnil) {
618
625
  state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
@@ -624,8 +631,6 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
624
631
  raise_error(rb_eArgError, "Unexpected value for otel_context_enabled: %+" PRIsVALUE, otel_context_enabled);
625
632
  }
626
633
 
627
- state->waiting_for_gvl_threshold_ns = NUM2UINT(waiting_for_gvl_threshold_ns);
628
-
629
634
  if (RTEST(tracer_context_key)) {
630
635
  ENFORCE_TYPE(tracer_context_key, T_SYMBOL);
631
636
  // Note about rb_to_id and dynamic symbols: calling `rb_to_id` prevents symbols from ever being garbage collected.
@@ -649,10 +654,14 @@ static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance
649
654
 
650
655
  if (allow_exception == Qfalse) debug_enter_unsafe_context();
651
656
 
652
- thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE));
657
+ bool needs_otel_span_key =
658
+ thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns());
653
659
 
654
660
  if (allow_exception == Qfalse) debug_leave_unsafe_context();
655
661
 
662
+ // Same as the CpuAndWallTimeWorker does: this can't happen during a sample (and thus not inside the unsafe context)
663
+ if (needs_otel_span_key) thread_context_collector_resolve_otel_span_key_may_lose_gvl(collector_instance);
664
+
656
665
  return Qtrue;
657
666
  }
658
667
 
@@ -680,21 +689,19 @@ static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_ins
680
689
  return Qtrue;
681
690
  }
682
691
 
683
- static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE allow_exception) {
684
- ENFORCE_BOOLEAN(allow_exception);
685
-
686
- if (allow_exception == Qfalse) debug_enter_unsafe_context();
692
+ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
693
+ debug_enter_unsafe_context();
687
694
 
688
695
  thread_context_collector_sample_after_gc(collector_instance);
689
696
 
690
- if (allow_exception == Qfalse) debug_leave_unsafe_context();
697
+ debug_leave_unsafe_context();
691
698
 
692
699
  return Qtrue;
693
700
  }
694
701
 
695
702
  // Record profiler sampling overhead as a placeholder stack
696
703
  static void record_sampling_overhead(thread_context_collector_state *state, per_thread_context *current_thread_context) {
697
- long wall_time_after_sampling = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
704
+ long wall_time_after_sampling = monotonic_wall_time_now_ns();
698
705
  long cpu_time_after_sampling = cpu_time_now_ns(current_thread_context);
699
706
 
700
707
  long overhead_cpu_time_ns = update_cpu_time_since_previous_sample(current_thread_context, cpu_time_after_sampling);
@@ -737,32 +744,38 @@ static void record_sampling_overhead(thread_context_collector_state *state, per_
737
744
  // Assumption 3: This function IS NOT called from a signal handler. This function is not async-signal-safe.
738
745
  // Assumption 4: This function IS NOT called in a reentrant way.
739
746
  // Assumption 5: This function is called from the main Ractor (if Ruby has support for Ractors).
747
+ // Assumption 6: When called while the profiler is active, `during_sample` MUST be set.
740
748
  //
741
- void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns) {
749
+ bool thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns) {
742
750
  thread_context_collector_state *state;
743
751
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
744
752
 
745
753
  VALUE current_thread = rb_thread_current();
746
- per_thread_context *current_thread_context = get_or_create_context_for(current_thread);
747
- long cpu_time_at_sample_start_for_current_thread = cpu_time_now_ns(current_thread_context);
754
+ per_thread_context *current_thread_context = get_or_create_context_for(current_thread, current_monotonic_wall_time_ns);
755
+
756
+ // Sample the current thread (which will use the current CPU-time) first.
757
+ // This ensures any CPU-time the profiler spends sampling (e.g. the rest of the function) is not wrongly blamed on this thread,
758
+ // and instead is separately accounted by `record_sampling_overhead()` below.
759
+ update_metrics_and_sample(
760
+ state,
761
+ current_thread,
762
+ current_thread_context,
763
+ current_monotonic_wall_time_ns,
764
+ false);
748
765
 
749
766
  VALUE threads = thread_list(state);
750
767
 
751
768
  const long thread_count = RARRAY_LEN(threads);
752
769
  for (long i = 0; i < thread_count; i++) {
753
- VALUE thread = RARRAY_AREF(threads, i);
754
- per_thread_context *thread_context = get_or_create_context_for(thread);
770
+ VALUE thread = rb_ary_entry(threads, i);
771
+ ENFORCE_THREAD(thread);
755
772
 
756
- // We account for cpu-time for the current thread in a different way: we use the cpu-time at sampling start,
757
- // to avoid blaming the time the profiler took on whatever is currently running on the thread,
758
- // and instead we report that time the profiler took as sampling overhead below.
759
- long current_cpu_time_ns = (thread == current_thread) ? cpu_time_at_sample_start_for_current_thread : cpu_time_now_ns(thread_context);
773
+ if (thread == current_thread) continue; // Already sampled above
760
774
 
761
775
  update_metrics_and_sample(
762
776
  state,
763
777
  thread,
764
- thread_context,
765
- current_cpu_time_ns,
778
+ get_or_create_context_for(thread, current_monotonic_wall_time_ns),
766
779
  current_monotonic_wall_time_ns,
767
780
  false);
768
781
  }
@@ -778,41 +791,44 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
778
791
  if (!current_thread_context->is_profiler_internal_thread) {
779
792
  record_sampling_overhead(state, current_thread_context);
780
793
  }
794
+
795
+ return is_otel_current_span_key_needed(state);
781
796
  }
782
797
 
783
798
  static void update_metrics_and_sample(
784
799
  thread_context_collector_state *state,
785
800
  VALUE thread_being_sampled,
786
801
  per_thread_context *thread_context,
787
- long current_cpu_time_ns,
788
802
  long current_monotonic_wall_time_ns,
789
803
  bool force_sample
790
804
  ) {
805
+ long current_cpu_time_ns = cpu_time_now_ns(thread_context);
791
806
  bool is_gvl_waiting_state =
792
807
  handle_gvl_waiting(state, thread_being_sampled, thread_context, current_cpu_time_ns);
793
808
 
794
809
  if (skip_sample(state, thread_context, is_gvl_waiting_state, force_sample)) return;
795
810
 
796
- // Don't assign/update cpu during "Waiting for GVL"
797
- long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
798
-
799
- long wall_time_elapsed_ns = update_wall_time_since_previous_sample(thread_context, current_monotonic_wall_time_ns);
800
-
801
811
  // A thread enters "Waiting for GVL", well, as the name implies, without the GVL.
802
812
  //
803
- // As a consequence, it's possible that a thread enters "Waiting for GVL" in parallel with the current thread working
813
+ // As a consequence, it's possible for another thread to enter "Waiting for GVL" in parallel with the current thread working
804
814
  // on sampling, and thus for the `current_monotonic_wall_time_ns` (which is recorded at the start of sampling)
805
- // to be < the time at which we started Waiting for GVL.
815
+ // to be <= the time we observe for a "Waiting for GVL".
806
816
  //
807
- // All together, this means that when `handle_gvl_waiting` creates an extra sample (see comments on that function for
808
- // what the extra sample is), it's possible that there's no more wall-time to be assigned.
809
- // Thus, in this case, we don't want to produce a sample representing Waiting for GVL with a wall-time of 0, and
817
+ // When that happens, `handle_gvl_waiting` has already accounted for all wall-time up to the (later) start of the
818
+ // wait via the extra sample it pushes (see comments on that function), so there may be no remaining time
819
+ // to account for here.
820
+ // Thus, in this case, we don't want to produce a sample representing "Waiting for GVL" with a wall-time of 0 (nor negative!), and
810
821
  // thus we skip creating such a sample.
811
- if (is_gvl_waiting_state && wall_time_elapsed_ns == 0) return;
822
+ if (is_gvl_waiting_state && current_monotonic_wall_time_ns <= thread_context->wall_time_at_previous_sample_ns) return;
812
823
  // ...you may also wonder: is there any other situation where it makes sense to produce a sample with
813
824
  // wall_time_elapsed_ns == 0? I believe that yes, because the sample still includes a timestamp and a stack, but we
814
825
  // may revisit/change our minds on this in the future.
815
826
 
827
+ // Don't assign/update cpu during "Waiting for GVL"
828
+ long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
829
+
830
+ long wall_time_elapsed_ns = update_wall_time_since_previous_sample(thread_context, current_monotonic_wall_time_ns);
831
+
816
832
  trigger_sample_for_thread(
817
833
  state,
818
834
  thread_being_sampled,
@@ -821,8 +837,7 @@ static void update_metrics_and_sample(
821
837
  current_monotonic_wall_time_ns,
822
838
  NULL,
823
839
  NULL,
824
- is_gvl_waiting_state,
825
- /* is_safe_to_allocate_objects: */ true // We called from a context that's safe to run any regular code, including allocations
840
+ is_gvl_waiting_state
826
841
  );
827
842
  }
828
843
 
@@ -838,7 +853,7 @@ static bool skip_sample(thread_context_collector_state *state, per_thread_contex
838
853
  // Skip this per-tick sample entirely when the thread does not have the GVL and did not acquire
839
854
  // it since the previous sample: its Ruby-level stack has not changed. The skipped wall-time will
840
855
  // be picked up by either by an extra sample when the thread acquires the GVL, or by
841
- // the on-serialize flush in the stack recorder (using was_skipped_at_last_sample).
856
+ // prepare_serialize (using was_skipped_at_last_sample).
842
857
  // The check is gated by `!is_gvl_waiting_state` so the existing "Waiting for GVL" machinery
843
858
  // in handle_gvl_waiting (situation 1 extra sample, situation 2 regular sample) keeps running.
844
859
  // TODO: we could probably also skip while "Waiting for GVL"
@@ -883,11 +898,14 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
883
898
  }
884
899
 
885
900
  // Here we record the wall-time first and in on_gc_finish we record it second to try to avoid having wall-time be slightly < cpu-time
886
- thread_context->gc_tracking.wall_time_at_start_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
901
+ thread_context->gc_tracking.wall_time_at_start_ns = monotonic_wall_time_now_ns();
887
902
  thread_context->gc_tracking.cpu_time_at_start_ns = cpu_time_now_ns(thread_context);
888
903
  }
889
904
 
890
905
  // This function gets called when Ruby has finished running the Garbage Collector on the current thread.
906
+ // (Note: The `GC_EXIT` event gets emitted by the VM while `during_gc` is still set to true -- the flag
907
+ // is only cleared after the event gets emitted -- see top of the file for more details)
908
+ //
891
909
  // It records the cpu/wall-time observed during GC, which will be used to later
892
910
  // create an event including the cpu/wall time spent from the start of garbage collector work until now.
893
911
  //
@@ -912,7 +930,8 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
912
930
  long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
913
931
  long wall_time_at_start_ns = thread_context->gc_tracking.wall_time_at_start_ns;
914
932
 
915
- if (cpu_time_at_start_ns == INVALID_TIME && wall_time_at_start_ns == INVALID_TIME) {
933
+ // Both fields are always set and cleared together, so checking one is enough
934
+ if (cpu_time_at_start_ns == INVALID_TIME) {
916
935
  // If this happened, it means that on_gc_start was either never called for the thread OR it was called but no thread
917
936
  // context existed at the time. The former can be the result of a bug, but since we can't distinguish them, we just
918
937
  // do nothing.
@@ -925,16 +944,13 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
925
944
 
926
945
  // Here we record the wall-time second and in on_gc_start we record it first to try to avoid having wall-time be slightly < cpu-time
927
946
  long cpu_time_at_finish_ns = cpu_time_now_ns(thread_context);
928
- long wall_time_at_finish_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
929
-
930
- // If our end timestamp is not OK, we bail out
931
- if (wall_time_at_finish_ns == 0) return false;
947
+ long wall_time_at_finish_ns = monotonic_wall_time_now_ns();
932
948
 
933
949
  long gc_cpu_time_elapsed_ns = cpu_time_at_finish_ns - cpu_time_at_start_ns;
934
950
  long gc_wall_time_elapsed_ns = wall_time_at_finish_ns - wall_time_at_start_ns;
935
951
 
936
- // Wall-time can go backwards if the system clock gets changed (and we observed spurious jumps back on macOS as well)
937
- // so let's ensure we don't get negative values for time deltas.
952
+ // @ivoanjo: We don't expect these clocks to ever go backwards (and in other code paths we enforce that with an exception)
953
+ // but during GC it's really really awkward to signal back failures so for now I left the clamping.
938
954
  gc_cpu_time_elapsed_ns = long_max_of(gc_cpu_time_elapsed_ns, 0);
939
955
  gc_wall_time_elapsed_ns = long_max_of(gc_wall_time_elapsed_ns, 0);
940
956
 
@@ -949,7 +965,7 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
949
965
 
950
966
  // Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample.
951
967
  // We don't do the same for wall-time, because GC is just like any other reason a thread didn't make
952
- // progress -- time always goes forward regardless of the thread making progress on what it wanted.
968
+ // progress -- time always goes forward regardless of the thread making progress on what it wanted.
953
969
  if (thread_context->cpu_time_at_previous_sample_ns != INVALID_TIME) {
954
970
  thread_context->cpu_time_at_previous_sample_ns += gc_cpu_time_elapsed_ns;
955
971
  }
@@ -957,10 +973,10 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
957
973
  // Let the caller know if it should schedule a flush or not. Returning true every time would cause a lot of overhead
958
974
  // on the application (see GC tracking introduction at the top of the file), so instead we try to accumulate a few
959
975
  // samples first.
960
- bool over_flush_time_treshold =
976
+ bool over_flush_time_threshold =
961
977
  (wall_time_at_finish_ns - state->gc_tracking.wall_time_at_last_flushed_gc_event_ns) >= TIME_BETWEEN_GC_EVENTS_NS;
962
978
 
963
- if (over_flush_time_treshold) {
979
+ if (over_flush_time_threshold) {
964
980
  return true;
965
981
  } else {
966
982
  return gc_profiling_has_major_gc_finished();
@@ -975,12 +991,20 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
975
991
  // Assumption 2: This function is allowed to raise exceptions. Caller is responsible for handling them, if needed.
976
992
  // Assumption 3: Unlike `on_gc_start` and `on_gc_finish`, this method is allowed to allocate memory as needed.
977
993
  // Assumption 4: This function is called from the main Ractor (if Ruby has support for Ractors).
994
+ // Assumption 5: When called while the profiler is active, `during_sample` MUST be set.
978
995
  VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
979
996
  thread_context_collector_state *state;
980
997
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
981
998
 
982
999
  if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
983
- raise_error(rb_eRuntimeError, "BUG: Unexpected call to sample_after_gc without valid GC information available");
1000
+ // Rarely, we might be called with nothing to do, as an earlier call already flushed the needed info.
1001
+ // This is because Ruby clears the "pending" flag for an entire batch of postponed jobs BEFORE running any of them
1002
+ // (see `rb_postponed_job_flush`). Thus, if another GC happens while an earlier job in the postponed batch is
1003
+ // running (and other parts of the profiler such as the heap profiler can trigger this)
1004
+ // then `on_gc_finish` records that data and asks for a second flush, even though Ruby has not yet run the
1005
+ // first one. Because of this, Ruby can call us once more with nothing left to do.
1006
+ state->stats.gc_samples_skipped_nothing_to_flush++;
1007
+ return Qnil;
984
1008
  }
985
1009
 
986
1010
  int max_labels_needed_for_gc = 7; // Magic number gets validated inside gc_profiling_set_metadata
@@ -1014,13 +1038,23 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
1014
1038
 
1015
1039
  state->stats.gc_samples++;
1016
1040
 
1017
- // Let recorder do any cleanup/updates it requires after a GC step.
1018
- recorder_after_gc_step(state->recorder_instance);
1019
-
1020
1041
  // Return a VALUE to make it easier to call this function from Ruby APIs that expect a return value (such as rb_rescue2)
1021
1042
  return Qnil;
1022
1043
  }
1023
1044
 
1045
+ // Let the recorder do any cleanup/updates it requires after a GC step.
1046
+ //
1047
+ // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
1048
+ // Assumption 2: This function is allowed to raise exceptions. Caller is responsible for handling them, if needed.
1049
+ VALUE thread_context_collector_heap_update_may_lose_gvl(VALUE self_instance) {
1050
+ thread_context_collector_state *state;
1051
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1052
+
1053
+ recorder_heap_update_may_lose_gvl(state->recorder_instance);
1054
+
1055
+ return Qnil; // for rb_rescue2
1056
+ }
1057
+
1024
1058
  static void trigger_sample_for_thread(
1025
1059
  thread_context_collector_state *state,
1026
1060
  VALUE thread_being_sampled,
@@ -1030,10 +1064,7 @@ static void trigger_sample_for_thread(
1030
1064
  // These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
1031
1065
  ddog_CharSlice *ruby_vm_type,
1032
1066
  ddog_CharSlice *class_name,
1033
- bool is_gvl_waiting_state,
1034
- // If the Ruby VM is at a state that can allocate objects safely, or not. Added for allocation profiling: we're not
1035
- // allowed to allocate objects (or raise exceptions) when inside the NEWOBJ tracepoint.
1036
- bool is_safe_to_allocate_objects
1067
+ bool is_gvl_waiting_state
1037
1068
  ) {
1038
1069
  int max_label_count =
1039
1070
  1 + // thread id
@@ -1071,11 +1102,11 @@ static void trigger_sample_for_thread(
1071
1102
  }
1072
1103
 
1073
1104
  trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
1074
- trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result, is_safe_to_allocate_objects);
1105
+ trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result);
1075
1106
 
1076
1107
  if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
1077
1108
  // If we couldn't get something with ddtrace, let's see if we can get some trace identifiers from opentelemetry directly
1078
- otel_without_ddtrace_trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result, is_safe_to_allocate_objects);
1109
+ otel_without_ddtrace_trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result);
1079
1110
  }
1080
1111
 
1081
1112
  if (trace_identifiers_result.valid) {
@@ -1157,7 +1188,8 @@ static void trigger_sample_for_thread(
1157
1188
  .is_gvl_waiting_state = is_gvl_waiting_state,
1158
1189
  },
1159
1190
  state->native_filenames_enabled,
1160
- state->native_filenames_cache
1191
+ state->native_filenames_cache,
1192
+ state->show_classes
1161
1193
  );
1162
1194
  }
1163
1195
 
@@ -1183,14 +1215,14 @@ static void check_frozen_thread(VALUE thread) {
1183
1215
 
1184
1216
  // See the docs on struct per_thread_context.
1185
1217
  // This allocates a Ruby object and therefore needs the GVL and is not safe to call from RUBY_INTERNAL_EVENT_* hooks.
1186
- static per_thread_context *get_or_create_context_for(VALUE thread) {
1218
+ static per_thread_context *get_or_create_context_for(VALUE thread, long current_monotonic_wall_time_ns) {
1187
1219
  per_thread_context *thread_context = get_per_thread_context(thread);
1188
1220
  if (thread_context != NULL) return thread_context;
1189
1221
 
1190
1222
  check_frozen_thread(thread);
1191
1223
 
1192
1224
  thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
1193
- initialize_context(thread, thread_context);
1225
+ initialize_context(thread, thread_context, current_monotonic_wall_time_ns);
1194
1226
 
1195
1227
  VALUE wrapper = TypedData_Wrap_Struct(rb_cObject, &per_thread_context_typed_data, thread_context);
1196
1228
  rb_ivar_set(thread, dd_per_thread_context_id, wrapper);
@@ -1204,7 +1236,8 @@ static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *un
1204
1236
 
1205
1237
  VALUE thread = rb_tracearg_self(rb_tracearg_from_tracepoint(tracepoint_data));
1206
1238
  ENFORCE_THREAD(thread);
1207
- get_or_create_context_for(thread);
1239
+
1240
+ get_or_create_context_for(thread, monotonic_wall_time_now_ns());
1208
1241
  }
1209
1242
 
1210
1243
  #define LOGGING_GEM_PATH "/lib/logging/diagnostic_context.rb"
@@ -1228,7 +1261,7 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
1228
1261
  return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
1229
1262
  }
1230
1263
 
1231
- static void initialize_context(VALUE thread, per_thread_context *thread_context) {
1264
+ static void initialize_context(VALUE thread, per_thread_context *thread_context, long current_monotonic_wall_time_ns) {
1232
1265
  // We always create per_thread_context's with latest_max_frames; that value is kept in sync with the
1233
1266
  // active profiler's max_frames by the global reset that runs when profiling starts
1234
1267
  // so we expect to always see here the latest correct value to be used.
@@ -1265,7 +1298,7 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context)
1265
1298
 
1266
1299
  thread_context->thread_cpu_time_id = thread_cpu_time_id_for(thread);
1267
1300
 
1268
- thread_context->wall_time_at_previous_sample_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
1301
+ thread_context->wall_time_at_previous_sample_ns = current_monotonic_wall_time_ns;
1269
1302
  thread_context->cpu_time_at_previous_sample_ns = cpu_time_now_ns(thread_context);
1270
1303
 
1271
1304
  // These will only be used during a GC operation
@@ -1295,22 +1328,26 @@ void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance)
1295
1328
 
1296
1329
  latest_max_frames = state->locations.len;
1297
1330
 
1331
+ long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns();
1332
+
1298
1333
  VALUE threads = thread_list(state);
1299
1334
  const long thread_count = RARRAY_LEN(threads);
1300
1335
  for (long i = 0; i < thread_count; i++) {
1301
1336
  VALUE thread = rb_ary_entry(threads, i);
1337
+ ENFORCE_THREAD(thread);
1338
+
1302
1339
  per_thread_context *thread_context = get_per_thread_context(thread);
1303
1340
  if (thread_context != NULL) {
1304
1341
  bool is_profiler_internal_thread = thread_context->is_profiler_internal_thread;
1305
1342
 
1306
1343
  sampling_buffer_free(&thread_context->sampling_buffer);
1307
1344
  memset(thread_context, 0, sizeof(per_thread_context));
1308
- initialize_context(thread, thread_context);
1345
+ initialize_context(thread, thread_context, current_monotonic_wall_time_ns);
1309
1346
 
1310
1347
  thread_context->is_profiler_internal_thread = is_profiler_internal_thread;
1311
1348
  } else {
1312
1349
  // If thread didn't have a context, let's trigger its creation
1313
- get_or_create_context_for(thread);
1350
+ get_or_create_context_for(thread, current_monotonic_wall_time_ns);
1314
1351
  }
1315
1352
  }
1316
1353
  }
@@ -1329,6 +1366,7 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
1329
1366
  rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_to_ruby_hash(state, rb_hash_new())));
1330
1367
  rb_str_concat(result, rb_sprintf(" endpoint_collection_enabled=%"PRIsVALUE, state->endpoint_collection_enabled ? Qtrue : Qfalse));
1331
1368
  rb_str_concat(result, rb_sprintf(" native_filenames_enabled=%"PRIsVALUE, state->native_filenames_enabled ? Qtrue : Qfalse));
1369
+ rb_str_concat(result, rb_sprintf(" show_classes=%"PRIsVALUE, state->show_classes ? Qtrue : Qfalse));
1332
1370
  // Note: `st_table_size()` is available from Ruby 3.2+ but not before
1333
1371
  rb_str_concat(result, rb_sprintf(" native_filenames_cache_size=%zu", state->native_filenames_cache->num_entries));
1334
1372
  rb_str_concat(result, rb_sprintf(" otel_context_enabled=%d", state->otel_context_enabled));
@@ -1340,7 +1378,6 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
1340
1378
  rb_str_concat(result, rb_sprintf(" main_thread=%"PRIsVALUE, state->main_thread));
1341
1379
  rb_str_concat(result, rb_sprintf(" gc_tracking=%"PRIsVALUE, gc_tracking_as_ruby_hash(state)));
1342
1380
  rb_str_concat(result, rb_sprintf(" otel_current_span_key=%"PRIsVALUE, state->otel_current_span_key));
1343
- rb_str_concat(result, rb_sprintf(" waiting_for_gvl_threshold_ns=%u", state->waiting_for_gvl_threshold_ns));
1344
1381
 
1345
1382
  return result;
1346
1383
  }
@@ -1380,6 +1417,7 @@ static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE has
1380
1417
  ID2SYM(rb_intern("sample_count")), /* => */ UINT2NUM(state->stats.sample_count),
1381
1418
  ID2SYM(rb_intern("gc_samples")), /* => */ UINT2NUM(state->stats.gc_samples),
1382
1419
  ID2SYM(rb_intern("gc_samples_missed_due_to_missing_context")), /* => */ UINT2NUM(state->stats.gc_samples_missed_due_to_missing_context),
1420
+ ID2SYM(rb_intern("gc_samples_skipped_nothing_to_flush")), /* => */ UINT2NUM(state->stats.gc_samples_skipped_nothing_to_flush),
1383
1421
  ID2SYM(rb_intern("inactive_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.inactive_thread_samples_skipped),
1384
1422
  ID2SYM(rb_intern("profiler_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.profiler_thread_samples_skipped),
1385
1423
  };
@@ -1412,7 +1450,9 @@ static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collec
1412
1450
  VALUE threads = thread_list(state);
1413
1451
  const long thread_count = RARRAY_LEN(threads);
1414
1452
  for (long i = 0; i < thread_count; i++) {
1415
- VALUE thread = RARRAY_AREF(threads, i);
1453
+ VALUE thread = rb_ary_entry(threads, i);
1454
+ ENFORCE_THREAD(thread);
1455
+
1416
1456
  per_thread_context *thread_context = get_per_thread_context(thread);
1417
1457
  if (thread_context != NULL) {
1418
1458
  rb_hash_aset(result, thread, per_thread_context_to_ruby_hash(thread_context));
@@ -1422,9 +1462,6 @@ static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collec
1422
1462
  }
1423
1463
 
1424
1464
  static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, per_thread_context *thread_context) {
1425
- // If we didn't have a time for the previous sample, we use the current one
1426
- if (*time_at_previous_sample_ns == INVALID_TIME) *time_at_previous_sample_ns = current_time_ns;
1427
-
1428
1465
  // We don't expect to be sampling a thread (and thus updating these counters) while Ruby is doing GC (between
1429
1466
  // `thread_context_collector_on_gc_start` and `thread_context_collector_on_gc_finish`)
1430
1467
  if (thread_context->gc_tracking.cpu_time_at_start_ns != INVALID_TIME) {
@@ -1435,7 +1472,7 @@ static long update_time_since_previous_sample(long *time_at_previous_sample_ns,
1435
1472
  thread_context->thread_id,
1436
1473
  thread_context->gc_tracking.cpu_time_at_start_ns,
1437
1474
  thread_context->gc_tracking.wall_time_at_start_ns,
1438
- monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
1475
+ monotonic_wall_time_now_ns()
1439
1476
  );
1440
1477
  }
1441
1478
 
@@ -1446,16 +1483,20 @@ static long update_time_since_previous_sample(long *time_at_previous_sample_ns,
1446
1483
  }
1447
1484
 
1448
1485
  static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns) {
1486
+ // A previous `cpu_time_now_ns` may have failed to read the clock and thus invalidated our baseline; in that case we
1487
+ // use the current time, so this sample gets no cpu-time and the next one gets an accurate delta.
1488
+ // Note wall-time never needs this: it is always seeded with a real value in `initialize_context`.
1489
+ if (thread_context->cpu_time_at_previous_sample_ns == INVALID_TIME) {
1490
+ thread_context->cpu_time_at_previous_sample_ns = current_cpu_time_ns;
1491
+ }
1492
+
1449
1493
  long elapsed_time_ns = update_time_since_previous_sample(
1450
1494
  &thread_context->cpu_time_at_previous_sample_ns,
1451
1495
  current_cpu_time_ns,
1452
1496
  thread_context
1453
1497
  );
1454
1498
 
1455
- // We don't expect cpu-time to go backwards, so let's flag this as a bug
1456
- if (elapsed_time_ns < 0) {
1457
- raise_error(rb_eRuntimeError, "BUG: Unexpected CPU time going backwards between samples");
1458
- }
1499
+ if (elapsed_time_ns < 0) raise_error(rb_eRuntimeError, "BUG: Unexpected CPU time going backwards between samples");
1459
1500
 
1460
1501
  return elapsed_time_ns;
1461
1502
  }
@@ -1467,11 +1508,9 @@ static long update_wall_time_since_previous_sample(per_thread_context *thread_co
1467
1508
  thread_context
1468
1509
  );
1469
1510
 
1470
- // Wall-time can actually go backwards (e.g. when the system clock gets set) so we can't assume time going backwards
1471
- // was a bug.
1472
- // @ivoanjo: I've also observed time going backwards spuriously on macOS, see discussion on
1473
- // https://github.com/DataDog/dd-trace-rb/pull/2336.
1474
- return long_max_of(elapsed_time_ns, 0);
1511
+ if (elapsed_time_ns < 0) raise_error(rb_eRuntimeError, "BUG: Unexpected wall time going backwards between samples");
1512
+
1513
+ return elapsed_time_ns;
1475
1514
  }
1476
1515
 
1477
1516
  // Safety: This function is assumed never to raise exceptions by callers
@@ -1510,14 +1549,14 @@ VALUE enforce_thread_context_collector_instance(VALUE object) {
1510
1549
  return object;
1511
1550
  }
1512
1551
 
1513
- // Finalize any pending heap allocation recordings.
1514
- // On Ruby 4+, heap allocations are recorded in two phases: during on_newobj_event we capture
1515
- // the object reference, then later we safely call rb_obj_id() to get the object ID.
1516
- void thread_context_collector_after_allocation(VALUE self_instance) {
1552
+ // Commit any pending heap allocation recordings.
1553
+ // Recording an allocation only puts it on a pending list, because taking the weak reference the heap profiler needs
1554
+ // can't be done from inside the NEWOBJ tracepoint -- see `heap_recorder_commit_recordings_may_lose_gvl`.
1555
+ void thread_context_collector_commit_heap_recordings_may_lose_gvl(VALUE self_instance) {
1517
1556
  thread_context_collector_state *state;
1518
1557
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1519
1558
 
1520
- recorder_after_sample(state->recorder_instance);
1559
+ recorder_commit_heap_recordings_may_lose_gvl(state->recorder_instance);
1521
1560
  }
1522
1561
 
1523
1562
  // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
@@ -1542,8 +1581,7 @@ static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_ins
1542
1581
  static void trace_identifiers_for(
1543
1582
  thread_context_collector_state *state,
1544
1583
  VALUE thread,
1545
- trace_identifiers *trace_identifiers_result,
1546
- bool is_safe_to_allocate_objects
1584
+ trace_identifiers *trace_identifiers_result
1547
1585
  ) {
1548
1586
  if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
1549
1587
  if (state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY) return;
@@ -1564,7 +1602,7 @@ static void trace_identifiers_for(
1564
1602
  VALUE numeric_span_id = Qnil;
1565
1603
 
1566
1604
  if (otel_values != Qnil) {
1567
- ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values, is_safe_to_allocate_objects);
1605
+ ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values);
1568
1606
  }
1569
1607
 
1570
1608
  if (root_span == Qnil || (active_span == Qnil && numeric_span_id == Qnil)) return;
@@ -1645,14 +1683,7 @@ static VALUE thread_list(thread_context_collector_state *state) {
1645
1683
  return result;
1646
1684
  }
1647
1685
 
1648
- // Inside a signal handler, we don't want to do the whole work of recording a sample, but we only record the stack of
1649
- // the current thread.
1650
- //
1651
- // Assumptions for this function are same as for `thread_context_collector_sample` except that this function is
1652
- // expected to be called from a signal handler and to be async-signal-safe.
1653
- //
1654
- // Also, no allocation (Ruby or malloc) can happen.
1655
- bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
1686
+ static inline bool thread_context_collector_prepare_sample_internal(void) {
1656
1687
  VALUE current_thread = rb_thread_current();
1657
1688
  per_thread_context *thread_context = get_per_thread_context(current_thread);
1658
1689
  if (thread_context == NULL) return false;
@@ -1660,13 +1691,26 @@ bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
1660
1691
  return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
1661
1692
  }
1662
1693
 
1663
- // This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should never allocate in the
1664
- // Ruby heap.
1694
+ // Called from a signal handler on the main Ractor with the GVL held, outside GC and with `during_sample` unset.
1695
+ // Must remain async-signal-safe: no allocation (Ruby or malloc), exceptions, or releasing the GVL.
1696
+ bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
1697
+ return thread_context_collector_prepare_sample_internal();
1698
+ }
1699
+
1700
+ // Called at RUBY_INTERNAL_EVENT_GC_EXIT on the main Ractor with the GVL held; `during_sample` MUST be set to prevent reentrancy.
1701
+ // Ruby is still doing GC: no allocation (Ruby or malloc), exceptions, or releasing the GVL.
1702
+ void thread_context_collector_prepare_sample_on_gc_finish(void) {
1703
+ thread_context_collector_prepare_sample_internal();
1704
+ }
1705
+
1706
+ // This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
1707
+ // Ruby heap nor release the GVL (https://github.com/DataDog/dd-trace-rb/pull/4240).
1665
1708
  //
1666
- // Returns true if the after_allocation needs to be called (to do work that can't be done from inside the
1667
- // tracepoint, such as allocate new objects), and false if it doesn't
1709
+ // Returns true if `thread_context_collector_commit_heap_recordings_may_lose_gvl` needs to be called (to do work
1710
+ // that can't be done from inside the tracepoint, such as allocate new objects), and false if it doesn't
1668
1711
  //
1669
- // The callers must ensure thread_context is non-NULL.
1712
+ // Assumption 1: The callers must ensure thread_context is non-NULL.
1713
+ // Assumption 2: When called while the profiler is active, `during_sample` MUST be set.
1670
1714
  bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object) {
1671
1715
  thread_context_collector_state *state;
1672
1716
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
@@ -1709,13 +1753,10 @@ bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_
1709
1753
  // Thus, we need to make sure there's actually a class before getting its name.
1710
1754
 
1711
1755
  if (klass != 0) {
1712
- const char *name = rb_class2name(klass);
1713
- size_t name_length = name != NULL ? strlen(name) : 0;
1714
-
1715
- if (name_length > 0) {
1716
- class_name = (ddog_CharSlice) {.ptr = name, .len = name_length};
1756
+ VALUE name = ddtrace_alloc_free_rb_mod_name(klass);
1757
+ if (name != Qnil) {
1758
+ class_name = char_slice_from_ruby_string(name);
1717
1759
  } else {
1718
- // @ivoanjo: I'm not sure this can ever happen, but just-in-case
1719
1760
  class_name = ruby_value_type_to_class_name(type);
1720
1761
  }
1721
1762
  } else {
@@ -1735,23 +1776,22 @@ bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_
1735
1776
  class_name = ruby_vm_type; // For other weird internal things we just use the VM type
1736
1777
  }
1737
1778
 
1738
- bool needs_after_allocation = track_object(state->recorder_instance, new_object, sample_weight, class_name);
1739
-
1740
1779
  VALUE current_thread = rb_thread_current();
1741
1780
 
1781
+ heap_sample_values heap_sample = {.new_object = new_object, .alloc_class = class_name};
1782
+
1742
1783
  trigger_sample_for_thread(
1743
1784
  state,
1744
1785
  current_thread,
1745
1786
  thread_context,
1746
- (sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
1787
+ (sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = &heap_sample},
1747
1788
  INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
1748
1789
  &ruby_vm_type,
1749
1790
  &class_name,
1750
- /* is_gvl_waiting_state: */ false,
1751
- /* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
1791
+ /* is_gvl_waiting_state: */ false
1752
1792
  );
1753
1793
 
1754
- return needs_after_allocation;
1794
+ return heap_sample.out_needs_commit;
1755
1795
  }
1756
1796
 
1757
1797
  // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
@@ -1764,13 +1804,13 @@ static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collecto
1764
1804
 
1765
1805
  debug_enter_unsafe_context();
1766
1806
 
1767
- bool needs_after_allocation = thread_context_collector_sample_allocation(collector_instance, thread_context, NUM2UINT(sample_weight), new_object);
1807
+ bool needs_commit = thread_context_collector_sample_allocation(collector_instance, thread_context, NUM2UINT(sample_weight), new_object);
1768
1808
 
1769
1809
  debug_leave_unsafe_context();
1770
1810
 
1771
1811
  // We could instead choose to automatically trigger the after allocation here; yet, it seems kinda nice to keep it manual for
1772
1812
  // the tests so we can pull on each lever separately and observe "the sausage being made" in steps
1773
- return needs_after_allocation ? Qtrue : Qfalse;
1813
+ return needs_commit ? Qtrue : Qfalse;
1774
1814
  }
1775
1815
 
1776
1816
  static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
@@ -1820,26 +1860,57 @@ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
1820
1860
  return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
1821
1861
  }
1822
1862
 
1823
- static VALUE get_otel_current_span_key(thread_context_collector_state *state, bool is_safe_to_allocate_objects) {
1824
- if (state->otel_current_span_key == Qtrue) { // Qtrue means we haven't tried to extract it yet
1825
- if (!is_safe_to_allocate_objects) {
1826
- // Calling read_otel_current_span_key_const below can trigger exceptions and arbitrary Ruby code running (e.g.
1827
- // `const_missing`, etc). Not safe to call in this situation, so we just skip otel info for this sample.
1828
- return Qnil;
1829
- }
1830
-
1831
- // If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
1832
- // gets refactored, we should not fall on our face)
1833
- VALUE span_key = rb_protect(read_otel_current_span_key_const, Qnil, NULL);
1834
- rb_set_errinfo(Qnil); // **Clear any pending exception after ignoring it**
1863
+ static VALUE otel_current_span_key_not_found(DDTRACE_UNUSED VALUE _unused, DDTRACE_UNUSED VALUE _exception) {
1864
+ return Qnil;
1865
+ }
1835
1866
 
1836
- // Note that this gets set to Qnil if we failed to extract the correct value, and thus we won't try to extract it again
1837
- state->otel_current_span_key = span_key;
1867
+ static VALUE get_otel_current_span_key(thread_context_collector_state *state) {
1868
+ if (state->otel_current_span_key == OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED) {
1869
+ // Extracting this needs calling into Ruby code, which we don't want to do in the middle of a sample.
1870
+ // So instead we signal that we want this, and let it be resolved separately.
1871
+ state->otel_current_span_key = OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED;
1872
+ return Qnil;
1838
1873
  }
1839
1874
 
1875
+ // We asked for it, but it hasn't been extracted yet
1876
+ if (is_otel_current_span_key_needed(state)) return Qnil;
1877
+
1840
1878
  return state->otel_current_span_key;
1841
1879
  }
1842
1880
 
1881
+ static bool is_otel_current_span_key_needed(thread_context_collector_state *state) {
1882
+ return state->otel_current_span_key == OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED;
1883
+ }
1884
+
1885
+ // Extracts `otel_current_span_key`, if a sample asked for it (see `get_otel_current_span_key`).
1886
+ // This is deliberately not done during sampling because we can lose the GVL.
1887
+ //
1888
+ // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
1889
+ void thread_context_collector_resolve_otel_span_key_may_lose_gvl(VALUE self_instance) {
1890
+ thread_context_collector_state *state;
1891
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1892
+
1893
+ if (!is_otel_current_span_key_needed(state)) return;
1894
+
1895
+ // If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
1896
+ // gets refactored, we should not fall on our face).
1897
+ //
1898
+ // Note that we use `rb_rescue2` and not `rb_protect` so that we only swallow exceptions: anything else that can
1899
+ // unwind through here (e.g. a `Thread#kill`) is not ours to swallow. `rb_rescue2` also takes care of restoring
1900
+ // any previously-pending exception for us.
1901
+ VALUE span_key = rb_rescue2(
1902
+ read_otel_current_span_key_const,
1903
+ Qnil,
1904
+ otel_current_span_key_not_found,
1905
+ Qnil,
1906
+ rb_eException, // rb_eException is the base class of all Ruby exceptions
1907
+ 0 // Required by API to be the last argument
1908
+ );
1909
+
1910
+ // Note that this gets set to Qnil if we failed to extract the correct value, and thus we won't try to extract it again
1911
+ state->otel_current_span_key = span_key;
1912
+ }
1913
+
1843
1914
  // This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
1844
1915
  // differently, and this codepath handles it.
1845
1916
  static void ddtrace_otel_trace_identifiers_for(
@@ -1848,8 +1919,7 @@ static void ddtrace_otel_trace_identifiers_for(
1848
1919
  VALUE *root_span,
1849
1920
  VALUE *numeric_span_id,
1850
1921
  VALUE active_span,
1851
- VALUE otel_values,
1852
- bool is_safe_to_allocate_objects
1922
+ VALUE otel_values
1853
1923
  ) {
1854
1924
  VALUE resolved_numeric_span_id =
1855
1925
  active_span == Qnil ?
@@ -1860,7 +1930,7 @@ static void ddtrace_otel_trace_identifiers_for(
1860
1930
 
1861
1931
  if (resolved_numeric_span_id == Qnil) return;
1862
1932
 
1863
- VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
1933
+ VALUE otel_current_span_key = get_otel_current_span_key(state);
1864
1934
  if (otel_current_span_key == Qnil) return;
1865
1935
  VALUE current_trace = *active_trace;
1866
1936
 
@@ -1886,6 +1956,7 @@ static void ddtrace_otel_trace_identifiers_for(
1886
1956
  *numeric_span_id = resolved_numeric_span_id;
1887
1957
  }
1888
1958
 
1959
+ // Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
1889
1960
  void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples) {
1890
1961
  thread_context_collector_state *state;
1891
1962
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
@@ -1980,15 +2051,14 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
1980
2051
  static void otel_without_ddtrace_trace_identifiers_for(
1981
2052
  thread_context_collector_state *state,
1982
2053
  VALUE thread,
1983
- trace_identifiers *trace_identifiers_result,
1984
- bool is_safe_to_allocate_objects
2054
+ trace_identifiers *trace_identifiers_result
1985
2055
  ) {
1986
2056
  VALUE context_storage = otel_context_storage_for(state, thread);
1987
2057
 
1988
2058
  // If it exists, context_storage is expected to be an Array[OpenTelemetry::Context]
1989
2059
  if (context_storage == Qnil || !RB_TYPE_P(context_storage, T_ARRAY)) return;
1990
2060
 
1991
- VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
2061
+ VALUE otel_current_span_key = get_otel_current_span_key(state);
1992
2062
  if (otel_current_span_key == Qnil) return;
1993
2063
 
1994
2064
  long active_context_index = RARRAY_LEN(context_storage) - 1;
@@ -2086,70 +2156,79 @@ static void mark_thread_as_profiler_internal(per_thread_context *ctx) {
2086
2156
  }
2087
2157
 
2088
2158
  void thread_context_collector_profiler_internal_thread_started(void) {
2089
- per_thread_context *ctx = get_or_create_context_for(rb_thread_current());
2159
+ per_thread_context *ctx = get_or_create_context_for(rb_thread_current(), monotonic_wall_time_now_ns());
2090
2160
  mark_thread_as_profiler_internal(ctx);
2091
2161
  }
2092
2162
 
2093
2163
  static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread) {
2094
- per_thread_context *ctx = get_or_create_context_for(thread);
2164
+ per_thread_context *ctx = get_or_create_context_for(thread, monotonic_wall_time_now_ns());
2095
2165
  mark_thread_as_profiler_internal(ctx);
2096
2166
  return Qnil;
2097
2167
  }
2098
2168
 
2099
2169
  // Called via rb_ensure when a profiler-internal thread (worker or idle helper) is about to exit.
2100
- // Records a final sample so the thread's accumulated cpu/wall time since the last on_serialize
2101
- // flush is not lost. on_serialize (below) also flushes profiler-internal threads during periodic
2170
+ // Records a final sample so the thread's accumulated cpu/wall time since the last prepare_serialize
2171
+ // is not lost. prepare_serialize (below) also flushes profiler-internal threads during periodic
2102
2172
  // serialization, but it can't help at shutdown: by the time the final serialize runs, these
2103
2173
  // threads are already dead and absent from thread_list.
2104
- void thread_context_collector_profiler_internal_thread_done(VALUE self_instance) {
2174
+ //
2175
+ // Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
2176
+ VALUE thread_context_collector_profiler_internal_thread_done(VALUE self_instance) {
2105
2177
  thread_context_collector_state *state;
2106
2178
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
2107
2179
 
2108
2180
  VALUE current_thread = rb_thread_current();
2109
- per_thread_context *thread_context = get_or_create_context_for(current_thread);
2181
+ long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns();
2182
+ per_thread_context *thread_context = get_or_create_context_for(current_thread, current_monotonic_wall_time_ns);
2110
2183
  if (!thread_context->is_profiler_internal_thread) {
2111
2184
  rb_raise(rb_eRuntimeError, "current thread %"PRIsVALUE" is not profiler-internal thread", current_thread);
2112
2185
  }
2113
2186
 
2114
- long current_cpu_time_ns = cpu_time_now_ns(thread_context);
2115
- long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
2116
-
2117
2187
  update_metrics_and_sample(
2118
2188
  state,
2119
2189
  current_thread,
2120
2190
  thread_context,
2121
- current_cpu_time_ns,
2122
2191
  current_monotonic_wall_time_ns,
2123
2192
  true);
2193
+
2194
+ return Qnil;
2124
2195
  }
2125
2196
 
2126
2197
  // Flushes threads whose last per-tick sample was skipped (either by the SUSPENDED-skip
2127
2198
  // optimization, or by is_profiler_internal_thread) so their accumulated time is recorded.
2128
- // Called by the stack recorder at the start of _native_serialize (regular periodic flush).
2129
- void thread_context_collector_on_serialize(VALUE self_instance) {
2199
+ // Called before serialization so that threads suspended across the whole profile period still get sampled.
2200
+ //
2201
+ // Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
2202
+ VALUE thread_context_prepare_serialize(VALUE self_instance) {
2130
2203
  thread_context_collector_state *state;
2131
2204
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
2132
2205
 
2133
- long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
2206
+ long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns();
2134
2207
  VALUE threads = thread_list(state);
2135
2208
  const long thread_count = RARRAY_LEN(threads);
2136
2209
 
2137
2210
  for (long i = 0; i < thread_count; i++) {
2138
- VALUE thread = RARRAY_AREF(threads, i);
2211
+ VALUE thread = rb_ary_entry(threads, i);
2212
+ ENFORCE_THREAD(thread);
2213
+
2139
2214
  per_thread_context *thread_context = get_per_thread_context(thread);
2140
2215
 
2141
2216
  if (thread_context != NULL && (thread_context->was_skipped_at_last_sample || thread_context->is_profiler_internal_thread)) {
2142
- long current_cpu_time_ns = cpu_time_now_ns(thread_context);
2143
- // We need to force_sample=true otherwise this sample would be skipped too
2144
2217
  update_metrics_and_sample(
2145
2218
  state,
2146
2219
  thread,
2147
2220
  thread_context,
2148
- current_cpu_time_ns,
2149
2221
  current_monotonic_wall_time_ns,
2150
- true);
2222
+ true // We need to force_sample=true otherwise this sample would be skipped too
2223
+ );
2151
2224
  }
2152
2225
  }
2226
+
2227
+ return state->recorder_instance;
2228
+ }
2229
+
2230
+ static VALUE _native_prepare_serialize(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
2231
+ return thread_context_prepare_serialize(collector_instance);
2153
2232
  }
2154
2233
 
2155
2234
  void thread_context_collector_on_gvl_released(per_thread_context *thread_context) {
@@ -2157,10 +2236,7 @@ void thread_context_collector_on_gvl_released(per_thread_context *thread_context
2157
2236
  }
2158
2237
 
2159
2238
  void thread_context_collector_on_gvl_waiting(per_thread_context *thread_context) {
2160
- long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
2161
- if (current_monotonic_wall_time_ns <= 0) return;
2162
-
2163
- thread_context->gvl_waiting_at = current_monotonic_wall_time_ns;
2239
+ thread_context->gvl_waiting_at = monotonic_wall_time_now_ns();
2164
2240
  }
2165
2241
 
2166
2242
  static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
@@ -2190,12 +2266,11 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2190
2266
  }
2191
2267
 
2192
2268
  #ifndef NO_GVL_INSTRUMENTATION
2193
- // This function runs on the passed thread and has the GVL because it gets called just after the Ruby thread acquired the GVL
2269
+ // We must only use async-signal-safe functions here, see notes in the caller
2270
+ //
2271
+ // Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
2194
2272
  __attribute__((warn_unused_result))
2195
- on_gvl_running_result thread_context_collector_on_gvl_running(VALUE self_instance, VALUE thread, per_thread_context *thread_context) {
2196
- thread_context_collector_state *state;
2197
- TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
2198
-
2273
+ on_gvl_running_result thread_context_collector_on_gvl_running(VALUE thread, per_thread_context *thread_context, uint32_t waiting_for_gvl_threshold_ns) {
2199
2274
  // Bump the event counter and clears the state bit to "running"
2200
2275
  uint64_t counter_portion = thread_context->gvl_state_change_count >> 1;
2201
2276
  thread_context->gvl_state_change_count = ((counter_portion + 1) << 1) | GVL_RUNNING;
@@ -2210,9 +2285,9 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2210
2285
  return (on_gvl_running_result) {.action = ON_GVL_RUNNING_SAMPLE, .waiting_for_gvl_duration_ns = 0};
2211
2286
  }
2212
2287
 
2213
- long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - gvl_waiting_at;
2288
+ long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns() - gvl_waiting_at;
2214
2289
 
2215
- bool should_sample = waiting_for_gvl_duration_ns >= state->waiting_for_gvl_threshold_ns;
2290
+ bool should_sample = waiting_for_gvl_duration_ns >= waiting_for_gvl_threshold_ns;
2216
2291
 
2217
2292
  if (should_sample) {
2218
2293
  // We flip the gvl_waiting_at to negative to mark that the thread is now running and no longer waiting
@@ -2270,7 +2345,8 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2270
2345
  //
2271
2346
  // ---
2272
2347
  //
2273
- // Always called with the GVL, either from a postponed_job or from tests.
2348
+ // Assumption 1: Always called with the GVL, either from a postponed_job or from tests.
2349
+ // Assumption 2: When called while the profiler is active, `during_sample` MUST be set.
2274
2350
  //
2275
2351
  // NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
2276
2352
  // exists to enable testing.
@@ -2278,7 +2354,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2278
2354
  thread_context_collector_state *state;
2279
2355
  TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
2280
2356
 
2281
- per_thread_context *thread_context = get_or_create_context_for(current_thread);
2357
+ per_thread_context *thread_context = get_or_create_context_for(current_thread, current_monotonic_wall_time_ns);
2282
2358
 
2283
2359
  long gvl_waiting_at = thread_context->gvl_waiting_at;
2284
2360
 
@@ -2290,18 +2366,12 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2290
2366
  return Qfalse;
2291
2367
  }
2292
2368
 
2293
- // We don't actually account for cpu-time during Waiting for GVL. BUT, we may choose to push an
2294
- // extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
2295
- // cpu-time of the thread and let `update_metrics_and_sample` decide what to do with it.
2296
- long cpu_time_for_thread = cpu_time_now_ns(thread_context);
2297
-
2298
2369
  // TODO: Should we update the dynamic sampling rate overhead tracking with this sample as well?
2299
2370
 
2300
2371
  update_metrics_and_sample(
2301
2372
  state,
2302
2373
  current_thread,
2303
2374
  thread_context,
2304
- cpu_time_for_thread,
2305
2375
  current_monotonic_wall_time_ns,
2306
2376
  false);
2307
2377
 
@@ -2387,8 +2457,7 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2387
2457
  gvl_waiting_started_wall_time_ns,
2388
2458
  NULL,
2389
2459
  NULL,
2390
- /* is_gvl_waiting_state: */ false, // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
2391
- /* is_safe_to_allocate_objects: */ true // This is similar to a regular cpu/wall sample, so it's also safe
2460
+ /* is_gvl_waiting_state: */ false // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
2392
2461
  );
2393
2462
  }
2394
2463
 
@@ -2408,15 +2477,18 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2408
2477
  return result;
2409
2478
  }
2410
2479
 
2411
- static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
2480
+ static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE waiting_for_gvl_threshold_ns) {
2412
2481
  ENFORCE_THREAD(thread);
2482
+ ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
2413
2483
 
2414
2484
  debug_enter_unsafe_context();
2415
2485
 
2416
2486
  per_thread_context *thread_context = get_per_thread_context(thread);
2417
2487
  VALUE result;
2418
2488
  if (thread_context) {
2419
- result = thread_context_collector_on_gvl_running(collector_instance, thread, thread_context).action == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
2489
+ on_gvl_running_result gvl_running_result =
2490
+ thread_context_collector_on_gvl_running(thread, thread_context, NUM2UINT(waiting_for_gvl_threshold_ns));
2491
+ result = gvl_running_result.action == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
2420
2492
  } else {
2421
2493
  result = Qfalse;
2422
2494
  }
@@ -2426,19 +2498,18 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2426
2498
  return result;
2427
2499
  }
2428
2500
 
2429
- static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE allow_exception) {
2501
+ static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
2430
2502
  ENFORCE_THREAD(thread);
2431
- ENFORCE_BOOLEAN(allow_exception);
2432
2503
 
2433
- if (allow_exception == Qfalse) debug_enter_unsafe_context();
2504
+ debug_enter_unsafe_context();
2434
2505
 
2435
2506
  VALUE result = thread_context_collector_sample_after_gvl_running(
2436
2507
  collector_instance,
2437
2508
  thread,
2438
- monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
2509
+ monotonic_wall_time_now_ns()
2439
2510
  );
2440
2511
 
2441
- if (allow_exception == Qfalse) debug_leave_unsafe_context();
2512
+ debug_leave_unsafe_context();
2442
2513
 
2443
2514
  return result;
2444
2515
  }
@@ -2453,13 +2524,33 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
2453
2524
 
2454
2525
  #endif // NO_GVL_INSTRUMENTATION
2455
2526
 
2456
- static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns) {
2527
+ static VALUE _native_apply_delta_to_time_at_previous_sample_ns(int argc, VALUE *argv, DDTRACE_UNUSED VALUE self) {
2528
+ VALUE thread;
2529
+ VALUE options;
2530
+ rb_scan_args(argc, argv, "1:", &thread, &options);
2531
+ if (options == Qnil) options = rb_hash_new();
2532
+
2533
+ VALUE cpu_time_delta_ns = rb_hash_lookup2(options, ID2SYM(rb_intern("cpu_time")), INT2FIX(0));
2534
+ VALUE wall_time_delta_ns = rb_hash_lookup2(options, ID2SYM(rb_intern("wall_time")), INT2FIX(0));
2535
+
2536
+ ENFORCE_THREAD(thread);
2537
+
2538
+ per_thread_context *thread_context = get_per_thread_context(thread);
2539
+ if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
2540
+
2541
+ thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(cpu_time_delta_ns);
2542
+ thread_context->wall_time_at_previous_sample_ns += NUM2LONG(wall_time_delta_ns);
2543
+
2544
+ return Qtrue;
2545
+ }
2546
+
2547
+ static VALUE _native_apply_delta_to_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns) {
2457
2548
  ENFORCE_THREAD(thread);
2458
2549
 
2459
2550
  per_thread_context *thread_context = get_per_thread_context(thread);
2460
2551
  if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
2461
2552
 
2462
- thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
2553
+ thread_context->gvl_waiting_at += NUM2LONG(delta_ns);
2463
2554
 
2464
2555
  return Qtrue;
2465
2556
  }
@@ -2502,8 +2593,7 @@ static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE c
2502
2593
  thread_context_collector_state *state;
2503
2594
  TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
2504
2595
 
2505
- long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
2506
- long system_epoch_time_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, current_monotonic_wall_time_ns);
2596
+ long system_epoch_time_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, monotonic_wall_time_now_ns());
2507
2597
 
2508
2598
  return LONG2NUM(system_epoch_time_ns);
2509
2599
  }