datadog 2.4.0 → 2.44.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 (1070) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1261 -1
  3. data/README.md +3 -1
  4. data/bin/ddprofrb +4 -4
  5. data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
  6. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
  7. data/ext/datadog_profiling_native_extension/clock_id.h +11 -3
  8. data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +66 -0
  9. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +3 -2
  10. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +653 -212
  11. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +5 -5
  12. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
  13. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
  14. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +33 -20
  15. data/ext/datadog_profiling_native_extension/collectors_stack.c +409 -113
  16. data/ext/datadog_profiling_native_extension/collectors_stack.h +41 -5
  17. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +1208 -623
  18. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +33 -8
  19. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +240 -0
  20. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +85 -16
  21. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +47 -1
  22. data/ext/datadog_profiling_native_extension/encoded_profile.c +80 -0
  23. data/ext/datadog_profiling_native_extension/encoded_profile.h +8 -0
  24. data/ext/datadog_profiling_native_extension/extconf.rb +99 -120
  25. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +6 -43
  26. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -55
  27. data/ext/datadog_profiling_native_extension/heap_recorder.c +762 -608
  28. data/ext/datadog_profiling_native_extension/heap_recorder.h +67 -28
  29. data/ext/datadog_profiling_native_extension/http_transport.c +108 -144
  30. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +18 -15
  31. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -10
  32. data/ext/datadog_profiling_native_extension/macos_sampler_thread.h +55 -0
  33. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -40
  34. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +796 -417
  35. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +48 -12
  36. data/ext/datadog_profiling_native_extension/profiling.c +93 -27
  37. data/ext/datadog_profiling_native_extension/ruby_helpers.c +77 -137
  38. data/ext/datadog_profiling_native_extension/ruby_helpers.h +21 -20
  39. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +34 -10
  40. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +2 -1
  41. data/ext/datadog_profiling_native_extension/stack_recorder.c +458 -302
  42. data/ext/datadog_profiling_native_extension/stack_recorder.h +14 -3
  43. data/ext/datadog_profiling_native_extension/time_helpers.h +18 -11
  44. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +48 -0
  45. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +36 -0
  46. data/ext/libdatadog_api/crashtracker.c +33 -30
  47. data/ext/libdatadog_api/crashtracker.h +5 -0
  48. data/ext/libdatadog_api/crashtracker_report_exception.c +126 -0
  49. data/ext/libdatadog_api/datadog_ruby_common.c +85 -16
  50. data/ext/libdatadog_api/datadog_ruby_common.h +47 -1
  51. data/ext/libdatadog_api/ddsketch.c +102 -0
  52. data/ext/libdatadog_api/di.c +218 -0
  53. data/ext/libdatadog_api/extconf.rb +44 -45
  54. data/ext/libdatadog_api/feature_flags.c +588 -0
  55. data/ext/libdatadog_api/feature_flags.h +5 -0
  56. data/ext/libdatadog_api/helpers.h +27 -0
  57. data/ext/libdatadog_api/init.c +35 -0
  58. data/ext/libdatadog_api/library_config.c +173 -0
  59. data/ext/libdatadog_api/library_config.h +25 -0
  60. data/ext/libdatadog_api/otel_thread_context.c +268 -0
  61. data/ext/libdatadog_api/otel_thread_context.h +5 -0
  62. data/ext/libdatadog_api/process_discovery.c +118 -0
  63. data/ext/libdatadog_api/process_discovery.h +5 -0
  64. data/ext/libdatadog_api/trace_exporter.c +1932 -0
  65. data/ext/libdatadog_api/trace_exporter.h +5 -0
  66. data/ext/libdatadog_extconf_helpers.rb +116 -18
  67. data/lib/datadog/ai_guard/autoload.rb +10 -0
  68. data/lib/datadog/ai_guard/component.rb +53 -0
  69. data/lib/datadog/ai_guard/configuration/ext.rb +19 -0
  70. data/lib/datadog/ai_guard/configuration.rb +128 -0
  71. data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -0
  72. data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
  73. data/lib/datadog/ai_guard/contrib/rack/integration.rb +42 -0
  74. data/lib/datadog/ai_guard/contrib/rack/patcher.rb +26 -0
  75. data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +97 -0
  76. data/lib/datadog/ai_guard/contrib/rails/integration.rb +41 -0
  77. data/lib/datadog/ai_guard/contrib/rails/patcher.rb +97 -0
  78. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +73 -0
  79. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
  80. data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
  81. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +31 -0
  82. data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
  83. data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
  84. data/lib/datadog/ai_guard/evaluation/content_part.rb +48 -0
  85. data/lib/datadog/ai_guard/evaluation/message.rb +43 -0
  86. data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
  87. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +36 -0
  88. data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
  89. data/lib/datadog/ai_guard/evaluation/request.rb +30 -0
  90. data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
  91. data/lib/datadog/ai_guard/evaluation/result.rb +38 -0
  92. data/lib/datadog/ai_guard/evaluation/tool_call.rb +31 -0
  93. data/lib/datadog/ai_guard/evaluation.rb +130 -0
  94. data/lib/datadog/ai_guard/ext.rb +32 -0
  95. data/lib/datadog/ai_guard/http_client.rb +86 -0
  96. data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
  97. data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
  98. data/lib/datadog/ai_guard/redaction/result.rb +29 -0
  99. data/lib/datadog/ai_guard/redaction.rb +80 -0
  100. data/lib/datadog/ai_guard.rb +182 -0
  101. data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +68 -0
  102. data/lib/datadog/appsec/actions_handler.rb +49 -0
  103. data/lib/datadog/appsec/anonymizer.rb +16 -0
  104. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
  105. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +66 -0
  106. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +36 -0
  107. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
  108. data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
  109. data/lib/datadog/appsec/api_security/route_extractor.rb +104 -0
  110. data/lib/datadog/appsec/api_security/sampler.rb +62 -0
  111. data/lib/datadog/appsec/api_security.rb +23 -0
  112. data/lib/datadog/appsec/assets/blocked.html +1 -99
  113. data/lib/datadog/appsec/assets/blocked.json +1 -1
  114. data/lib/datadog/appsec/assets/blocked.text +3 -1
  115. data/lib/datadog/appsec/assets/waf_rules/README.md +44 -5
  116. data/lib/datadog/appsec/assets/waf_rules/recommended.json +954 -229
  117. data/lib/datadog/appsec/assets/waf_rules/strict.json +109 -106
  118. data/lib/datadog/appsec/assets.rb +8 -8
  119. data/lib/datadog/appsec/autoload.rb +5 -5
  120. data/lib/datadog/appsec/component.rb +56 -75
  121. data/lib/datadog/appsec/compressed_json.rb +40 -0
  122. data/lib/datadog/appsec/configuration.rb +436 -1
  123. data/lib/datadog/appsec/context.rb +135 -0
  124. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +94 -0
  125. data/lib/datadog/appsec/contrib/active_record/integration.rb +41 -0
  126. data/lib/datadog/appsec/contrib/active_record/patcher.rb +104 -0
  127. data/lib/datadog/appsec/contrib/auto_instrument.rb +1 -1
  128. data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +75 -0
  129. data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +39 -0
  130. data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +30 -0
  131. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +146 -0
  132. data/lib/datadog/appsec/contrib/devise/configuration.rb +52 -0
  133. data/lib/datadog/appsec/contrib/devise/data_extractor.rb +78 -0
  134. data/lib/datadog/appsec/contrib/devise/ext.rb +22 -0
  135. data/lib/datadog/appsec/contrib/devise/integration.rb +5 -6
  136. data/lib/datadog/appsec/contrib/devise/patcher.rb +34 -26
  137. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +104 -0
  138. data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +70 -0
  139. data/lib/datadog/appsec/contrib/devise/{patcher/rememberable_patch.rb → patches/skip_signin_tracking_patch.rb} +3 -3
  140. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +121 -0
  141. data/lib/datadog/appsec/contrib/excon/integration.rb +41 -0
  142. data/lib/datadog/appsec/contrib/excon/patcher.rb +30 -0
  143. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +181 -0
  144. data/lib/datadog/appsec/contrib/faraday/connection_patch.rb +22 -0
  145. data/lib/datadog/appsec/contrib/faraday/integration.rb +42 -0
  146. data/lib/datadog/appsec/contrib/faraday/patcher.rb +53 -0
  147. data/lib/datadog/appsec/contrib/faraday/rack_builder_patch.rb +22 -0
  148. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +166 -0
  149. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +4 -10
  150. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +76 -26
  151. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +21 -36
  152. data/lib/datadog/appsec/contrib/graphql/integration.rb +10 -9
  153. data/lib/datadog/appsec/contrib/graphql/patcher.rb +3 -6
  154. data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
  155. data/lib/datadog/appsec/contrib/rack/ext.rb +34 -0
  156. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +56 -21
  157. data/lib/datadog/appsec/contrib/rack/gateway/response.rb +4 -4
  158. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +101 -106
  159. data/lib/datadog/appsec/contrib/rack/input_peeker.rb +79 -0
  160. data/lib/datadog/appsec/contrib/rack/integration.rb +7 -7
  161. data/lib/datadog/appsec/contrib/rack/patcher.rb +3 -6
  162. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +17 -15
  163. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +118 -114
  164. data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
  165. data/lib/datadog/appsec/contrib/rails/gateway/request.rb +43 -4
  166. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +58 -35
  167. data/lib/datadog/appsec/contrib/rails/integration.rb +6 -6
  168. data/lib/datadog/appsec/contrib/rails/patcher.rb +82 -69
  169. data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +29 -0
  170. data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +33 -0
  171. data/lib/datadog/appsec/contrib/rails/request.rb +3 -3
  172. data/lib/datadog/appsec/contrib/rails/request_middleware.rb +2 -1
  173. data/lib/datadog/appsec/contrib/rest_client/integration.rb +45 -0
  174. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +30 -0
  175. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +193 -0
  176. data/lib/datadog/appsec/contrib/sinatra/gateway/request.rb +1 -1
  177. data/lib/datadog/appsec/contrib/sinatra/gateway/route_params.rb +1 -1
  178. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +83 -71
  179. data/lib/datadog/appsec/contrib/sinatra/integration.rb +6 -6
  180. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +25 -61
  181. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +31 -0
  182. data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
  183. data/lib/datadog/appsec/counter_sampler.rb +25 -0
  184. data/lib/datadog/appsec/default_header_tags.rb +52 -0
  185. data/lib/datadog/appsec/event.rb +80 -128
  186. data/lib/datadog/appsec/ext.rb +17 -1
  187. data/lib/datadog/appsec/extensions.rb +2 -2
  188. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +23 -2
  189. data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +23 -0
  190. data/lib/datadog/appsec/instrumentation/gateway.rb +6 -24
  191. data/lib/datadog/appsec/metrics/collector.rb +80 -0
  192. data/lib/datadog/appsec/metrics/exporter.rb +48 -0
  193. data/lib/datadog/appsec/metrics/telemetry.rb +40 -0
  194. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +47 -0
  195. data/lib/datadog/appsec/metrics.rb +14 -0
  196. data/lib/datadog/appsec/monitor/gateway/watcher.rb +60 -36
  197. data/lib/datadog/appsec/monitor.rb +1 -1
  198. data/lib/datadog/appsec/processor/rule_loader.rb +54 -60
  199. data/lib/datadog/appsec/rate_limiter.rb +1 -1
  200. data/lib/datadog/appsec/remote.rb +59 -70
  201. data/lib/datadog/appsec/response.rb +42 -94
  202. data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
  203. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
  204. data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
  205. data/lib/datadog/appsec/route_normalizer.rb +80 -0
  206. data/lib/datadog/appsec/security_engine/engine.rb +197 -0
  207. data/lib/datadog/appsec/security_engine/result.rb +103 -0
  208. data/lib/datadog/appsec/security_engine/runner.rb +111 -0
  209. data/lib/datadog/appsec/security_engine.rb +9 -0
  210. data/lib/datadog/appsec/security_event.rb +37 -0
  211. data/lib/datadog/appsec/thread_safe_ref.rb +61 -0
  212. data/lib/datadog/appsec/trace_keeper.rb +36 -0
  213. data/lib/datadog/appsec/utils/hash_coercion.rb +23 -0
  214. data/lib/datadog/appsec/utils/http/body.rb +41 -0
  215. data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
  216. data/lib/datadog/appsec/utils/http/media_range.rb +17 -16
  217. data/lib/datadog/appsec/utils/http/media_type.rb +33 -27
  218. data/lib/datadog/appsec/utils/http/url_encoded.rb +107 -0
  219. data/lib/datadog/appsec.rb +35 -28
  220. data/lib/datadog/auto_instrument.rb +5 -2
  221. data/lib/datadog/auto_instrument_base.rb +2 -1
  222. data/lib/datadog/core/buffer/cruby.rb +1 -1
  223. data/lib/datadog/core/buffer/random.rb +18 -2
  224. data/lib/datadog/core/buffer/thread_safe.rb +1 -1
  225. data/lib/datadog/core/configuration/agent_settings.rb +52 -0
  226. data/lib/datadog/core/configuration/agent_settings_resolver.rb +46 -59
  227. data/lib/datadog/core/configuration/agentless_settings_resolver.rb +176 -0
  228. data/lib/datadog/core/configuration/base.rb +19 -7
  229. data/lib/datadog/core/configuration/components.rb +283 -54
  230. data/lib/datadog/core/configuration/components_state.rb +28 -0
  231. data/lib/datadog/core/configuration/config_helper.rb +109 -0
  232. data/lib/datadog/core/configuration/deprecations.rb +36 -0
  233. data/lib/datadog/core/configuration/ext.rb +15 -12
  234. data/lib/datadog/core/configuration/option.rb +150 -84
  235. data/lib/datadog/core/configuration/option_definition.rb +51 -30
  236. data/lib/datadog/core/configuration/options.rb +58 -19
  237. data/lib/datadog/core/configuration/settings.rb +341 -140
  238. data/lib/datadog/core/configuration/stable_config.rb +32 -0
  239. data/lib/datadog/core/configuration/supported_configurations.rb +426 -0
  240. data/lib/datadog/core/configuration.rb +51 -21
  241. data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
  242. data/lib/datadog/core/contrib/rails/utils.rb +28 -0
  243. data/lib/datadog/core/crashtracking/component.rb +89 -39
  244. data/lib/datadog/core/crashtracking/tag_builder.rb +11 -23
  245. data/lib/datadog/core/ddsketch.rb +19 -0
  246. data/lib/datadog/core/deprecations.rb +3 -3
  247. data/lib/datadog/core/diagnostics/environment_logger.rb +46 -9
  248. data/lib/datadog/core/diagnostics/health.rb +2 -2
  249. data/lib/datadog/core/encoding.rb +21 -5
  250. data/lib/datadog/core/environment/agent_info.rb +142 -0
  251. data/lib/datadog/core/environment/cgroup.rb +53 -28
  252. data/lib/datadog/core/environment/container.rb +143 -51
  253. data/lib/datadog/core/environment/execution.rb +3 -3
  254. data/lib/datadog/core/environment/ext.rb +32 -21
  255. data/lib/datadog/core/environment/gc.rb +1 -1
  256. data/lib/datadog/core/environment/git.rb +5 -4
  257. data/lib/datadog/core/environment/identity.rb +50 -14
  258. data/lib/datadog/core/environment/platform.rb +8 -8
  259. data/lib/datadog/core/environment/process.rb +122 -0
  260. data/lib/datadog/core/environment/socket.rb +15 -2
  261. data/lib/datadog/core/environment/variable_helpers.rb +4 -4
  262. data/lib/datadog/core/environment/vm_cache.rb +1 -1
  263. data/lib/datadog/core/environment/yjit.rb +8 -2
  264. data/lib/datadog/core/error.rb +15 -13
  265. data/lib/datadog/core/evp.rb +11 -0
  266. data/lib/datadog/core/extensions.rb +1 -1
  267. data/lib/datadog/core/feature_flags.rb +61 -0
  268. data/lib/datadog/core/git/ext.rb +4 -4
  269. data/lib/datadog/core/knuth_sampler.rb +59 -0
  270. data/lib/datadog/core/logger.rb +5 -5
  271. data/lib/datadog/core/logging/ext.rb +1 -1
  272. data/lib/datadog/core/metrics/client.rb +49 -49
  273. data/lib/datadog/core/metrics/ext.rb +5 -5
  274. data/lib/datadog/core/metrics/helpers.rb +1 -1
  275. data/lib/datadog/core/metrics/logging.rb +8 -8
  276. data/lib/datadog/core/metrics/options.rb +5 -5
  277. data/lib/datadog/core/pin.rb +9 -9
  278. data/lib/datadog/core/process_discovery/tracer_memfd.rb +13 -0
  279. data/lib/datadog/core/process_discovery.rb +62 -0
  280. data/lib/datadog/core/rate_limiter.rb +16 -4
  281. data/lib/datadog/core/remote/client/capabilities.rb +82 -11
  282. data/lib/datadog/core/remote/client.rb +130 -107
  283. data/lib/datadog/core/remote/component.rb +97 -55
  284. data/lib/datadog/core/remote/configuration/content.rb +17 -4
  285. data/lib/datadog/core/remote/configuration/digest.rb +22 -15
  286. data/lib/datadog/core/remote/configuration/path.rb +9 -9
  287. data/lib/datadog/core/remote/configuration/repository.rb +19 -6
  288. data/lib/datadog/core/remote/configuration/target.rb +15 -8
  289. data/lib/datadog/core/remote/configuration.rb +4 -4
  290. data/lib/datadog/core/remote/ext.rb +3 -3
  291. data/lib/datadog/core/remote/negotiation.rb +10 -10
  292. data/lib/datadog/core/remote/tie/tracing.rb +7 -7
  293. data/lib/datadog/core/remote/transport/config.rb +11 -37
  294. data/lib/datadog/core/remote/transport/http/config.rb +39 -107
  295. data/lib/datadog/core/remote/transport/http/negotiation.rb +19 -92
  296. data/lib/datadog/core/remote/transport/http.rb +33 -106
  297. data/lib/datadog/core/remote/transport/negotiation.rb +9 -21
  298. data/lib/datadog/core/remote/worker.rb +40 -41
  299. data/lib/datadog/core/runtime/ext.rb +22 -22
  300. data/lib/datadog/core/runtime/metrics.rb +34 -14
  301. data/lib/datadog/core/semaphore.rb +32 -0
  302. data/lib/datadog/core/tag_builder.rb +52 -0
  303. data/lib/datadog/core/tag_normalizer.rb +84 -0
  304. data/lib/datadog/core/telemetry/component.rb +143 -60
  305. data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
  306. data/lib/datadog/core/telemetry/emitter.rb +26 -14
  307. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +67 -0
  308. data/lib/datadog/core/telemetry/event/app_closing.rb +18 -0
  309. data/lib/datadog/core/telemetry/event/app_dependencies_loaded.rb +33 -0
  310. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
  311. data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
  312. data/lib/datadog/core/telemetry/event/app_heartbeat.rb +18 -0
  313. data/lib/datadog/core/telemetry/event/app_integrations_change.rb +58 -0
  314. data/lib/datadog/core/telemetry/event/app_started.rb +274 -0
  315. data/lib/datadog/core/telemetry/event/base.rb +40 -0
  316. data/lib/datadog/core/telemetry/event/distributions.rb +18 -0
  317. data/lib/datadog/core/telemetry/event/generate_metrics.rb +43 -0
  318. data/lib/datadog/core/telemetry/event/log.rb +76 -0
  319. data/lib/datadog/core/telemetry/event/message_batch.rb +42 -0
  320. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +66 -0
  321. data/lib/datadog/core/telemetry/event.rb +21 -392
  322. data/lib/datadog/core/telemetry/ext.rb +11 -9
  323. data/lib/datadog/core/telemetry/http/adapters/net.rb +12 -97
  324. data/lib/datadog/core/telemetry/logger.rb +6 -3
  325. data/lib/datadog/core/telemetry/logging.rb +35 -17
  326. data/lib/datadog/core/telemetry/metric.rb +37 -15
  327. data/lib/datadog/core/telemetry/metrics_collection.rb +2 -2
  328. data/lib/datadog/core/telemetry/metrics_manager.rb +10 -1
  329. data/lib/datadog/core/telemetry/request.rb +23 -11
  330. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +66 -0
  331. data/lib/datadog/core/telemetry/transport/http.rb +68 -0
  332. data/lib/datadog/core/telemetry/transport/telemetry.rb +42 -0
  333. data/lib/datadog/core/telemetry/worker.rb +214 -35
  334. data/lib/datadog/core/transport/ext.rb +14 -10
  335. data/lib/datadog/core/transport/http/adapters/net.rb +19 -4
  336. data/lib/datadog/core/transport/http/adapters/test.rb +3 -2
  337. data/lib/datadog/core/transport/http/adapters/unix_socket.rb +5 -5
  338. data/lib/datadog/core/transport/http/api/endpoint.rb +10 -5
  339. data/lib/datadog/{tracing → core}/transport/http/api/instance.rb +5 -5
  340. data/lib/datadog/core/transport/http/api/map.rb +1 -1
  341. data/lib/datadog/{tracing → core}/transport/http/builder.rb +62 -36
  342. data/lib/datadog/core/transport/http/client.rb +80 -0
  343. data/lib/datadog/core/transport/http/env.rb +8 -0
  344. data/lib/datadog/core/transport/http/response.rb +5 -1
  345. data/lib/datadog/core/transport/http.rb +80 -0
  346. data/lib/datadog/core/transport/parcel.rb +61 -9
  347. data/lib/datadog/core/transport/response.rb +19 -1
  348. data/lib/datadog/core/transport/transport.rb +90 -0
  349. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +181 -29
  350. data/lib/datadog/core/utils/base64_codec.rb +23 -0
  351. data/lib/datadog/core/utils/duration.rb +33 -33
  352. data/lib/datadog/core/utils/enumerable_compat.rb +29 -0
  353. data/lib/datadog/core/utils/fnv.rb +26 -0
  354. data/lib/datadog/core/utils/forking.rb +5 -3
  355. data/lib/datadog/core/utils/hash.rb +0 -23
  356. data/lib/datadog/core/utils/lru_cache.rb +45 -0
  357. data/lib/datadog/core/utils/network.rb +34 -13
  358. data/lib/datadog/core/utils/only_once.rb +1 -1
  359. data/lib/datadog/core/utils/only_once_successful.rb +25 -8
  360. data/lib/datadog/core/utils/safe_dup.rb +4 -3
  361. data/lib/datadog/core/utils/sequence.rb +3 -4
  362. data/lib/datadog/core/utils/spawn_monkey_patch.rb +68 -0
  363. data/lib/datadog/core/utils/time.rb +19 -33
  364. data/lib/datadog/core/utils/truncation.rb +21 -0
  365. data/lib/datadog/core/utils/url.rb +1 -1
  366. data/lib/datadog/core/utils.rb +14 -5
  367. data/lib/datadog/core/vendor/multipart-post/multipart/post/composite_read_io.rb +1 -1
  368. data/lib/datadog/core/vendor/multipart-post/multipart/post/multipartable.rb +8 -8
  369. data/lib/datadog/core/vendor/multipart-post/multipart/post/parts.rb +7 -7
  370. data/lib/datadog/core/worker.rb +1 -1
  371. data/lib/datadog/core/workers/async.rb +41 -15
  372. data/lib/datadog/core/workers/interval_loop.rb +63 -4
  373. data/lib/datadog/core/workers/polling.rb +4 -2
  374. data/lib/datadog/core/workers/queue.rb +96 -1
  375. data/lib/datadog/core/workers/runtime_metrics.rb +14 -6
  376. data/lib/datadog/core.rb +20 -3
  377. data/lib/datadog/data_streams/configuration.rb +50 -0
  378. data/lib/datadog/data_streams/ext.rb +11 -0
  379. data/lib/datadog/data_streams/extensions.rb +16 -0
  380. data/lib/datadog/data_streams/pathway_context.rb +167 -0
  381. data/lib/datadog/data_streams/processor.rb +542 -0
  382. data/lib/datadog/data_streams/transport/http/stats.rb +58 -0
  383. data/lib/datadog/data_streams/transport/http.rb +36 -0
  384. data/lib/datadog/data_streams/transport/stats.rb +39 -0
  385. data/lib/datadog/data_streams.rb +100 -0
  386. data/lib/datadog/di/base.rb +123 -0
  387. data/lib/datadog/di/boot.rb +47 -0
  388. data/lib/datadog/di/capture_expression.rb +21 -0
  389. data/lib/datadog/di/capture_expression_evaluator.rb +71 -0
  390. data/lib/datadog/di/capture_limits.rb +41 -0
  391. data/lib/datadog/di/code_tracker.rb +256 -18
  392. data/lib/datadog/di/component.rb +220 -0
  393. data/lib/datadog/di/configuration.rb +241 -2
  394. data/lib/datadog/di/context.rb +82 -0
  395. data/lib/datadog/di/contrib/active_record.rb +41 -0
  396. data/lib/datadog/di/contrib/railtie.rb +15 -0
  397. data/lib/datadog/di/contrib.rb +28 -0
  398. data/lib/datadog/di/el/compiler.rb +244 -0
  399. data/lib/datadog/di/el/evaluator.rb +239 -0
  400. data/lib/datadog/di/el/expression.rb +53 -0
  401. data/lib/datadog/di/el.rb +5 -0
  402. data/lib/datadog/di/error.rb +66 -0
  403. data/lib/datadog/di/fatal_exceptions.rb +26 -0
  404. data/lib/datadog/di/instrumenter.rb +978 -0
  405. data/lib/datadog/di/logger.rb +30 -0
  406. data/lib/datadog/di/preload.rb +18 -0
  407. data/lib/datadog/di/probe.rb +188 -14
  408. data/lib/datadog/di/probe_builder.rb +132 -3
  409. data/lib/datadog/di/probe_file_loader/railtie.rb +15 -0
  410. data/lib/datadog/di/probe_file_loader.rb +87 -0
  411. data/lib/datadog/di/probe_manager.rb +352 -0
  412. data/lib/datadog/di/probe_notification_builder.rb +452 -0
  413. data/lib/datadog/di/probe_notifier_worker.rb +381 -0
  414. data/lib/datadog/di/probe_repository.rb +198 -0
  415. data/lib/datadog/di/proc_responder.rb +36 -0
  416. data/lib/datadog/di/redactor.rb +24 -3
  417. data/lib/datadog/di/remote.rb +310 -0
  418. data/lib/datadog/di/serializer.rb +487 -96
  419. data/lib/datadog/di/transport/diagnostics.rb +32 -0
  420. data/lib/datadog/di/transport/http/diagnostics.rb +35 -0
  421. data/lib/datadog/di/transport/http/input.rb +45 -0
  422. data/lib/datadog/di/transport/http.rb +81 -0
  423. data/lib/datadog/di/transport/input.rb +144 -0
  424. data/lib/datadog/di/utils.rb +170 -0
  425. data/lib/datadog/di.rb +224 -2
  426. data/lib/datadog/error_tracking/collector.rb +88 -0
  427. data/lib/datadog/error_tracking/component.rb +167 -0
  428. data/lib/datadog/error_tracking/configuration.rb +64 -0
  429. data/lib/datadog/error_tracking/ext.rb +18 -0
  430. data/lib/datadog/error_tracking/extensions.rb +16 -0
  431. data/lib/datadog/error_tracking/filters.rb +77 -0
  432. data/lib/datadog/error_tracking.rb +18 -0
  433. data/lib/datadog/kit/appsec/events/v2.rb +253 -0
  434. data/lib/datadog/kit/appsec/events.rb +39 -26
  435. data/lib/datadog/kit/enable_core_dumps.rb +3 -3
  436. data/lib/datadog/kit/identity.rb +34 -24
  437. data/lib/datadog/kit/tracing/method_tracer.rb +135 -0
  438. data/lib/datadog/kit.rb +1 -1
  439. data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
  440. data/lib/datadog/open_feature/agentless/parser.rb +43 -0
  441. data/lib/datadog/open_feature/agentless/response.rb +19 -0
  442. data/lib/datadog/open_feature/agentless/transport.rb +103 -0
  443. data/lib/datadog/open_feature/component.rb +165 -0
  444. data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
  445. data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
  446. data/lib/datadog/open_feature/configuration/source.rb +43 -0
  447. data/lib/datadog/open_feature/configuration.rb +168 -0
  448. data/lib/datadog/open_feature/evaluation_engine.rb +96 -0
  449. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  450. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  451. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  452. data/lib/datadog/open_feature/exposures/event.rb +67 -0
  453. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  454. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  455. data/lib/datadog/open_feature/ext.rb +40 -0
  456. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +442 -0
  457. data/lib/datadog/open_feature/flag_evaluation/writer.rb +544 -0
  458. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +105 -0
  459. data/lib/datadog/open_feature/hooks/flag_eval_metrics_hook.rb +51 -0
  460. data/lib/datadog/open_feature/hooks/span_enrichment_hook/codec.rb +58 -0
  461. data/lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state.rb +96 -0
  462. data/lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state_store.rb +49 -0
  463. data/lib/datadog/open_feature/hooks/span_enrichment_hook.rb +184 -0
  464. data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
  465. data/lib/datadog/open_feature/native_evaluator.rb +74 -0
  466. data/lib/datadog/open_feature/noop_evaluator.rb +31 -0
  467. data/lib/datadog/open_feature/provider.rb +235 -0
  468. data/lib/datadog/open_feature/remote.rb +67 -0
  469. data/lib/datadog/open_feature/resolution_details.rb +37 -0
  470. data/lib/datadog/open_feature/transport.rb +126 -0
  471. data/lib/datadog/open_feature.rb +19 -0
  472. data/lib/datadog/opentelemetry/api/baggage.rb +90 -0
  473. data/lib/datadog/opentelemetry/api/context.rb +25 -10
  474. data/lib/datadog/opentelemetry/configuration/settings.rb +226 -0
  475. data/lib/datadog/opentelemetry/ext.rb +9 -0
  476. data/lib/datadog/opentelemetry/logs.rb +98 -0
  477. data/lib/datadog/opentelemetry/metrics.rb +90 -0
  478. data/lib/datadog/opentelemetry/sdk/configurator.rb +69 -5
  479. data/lib/datadog/opentelemetry/sdk/id_generator.rb +16 -10
  480. data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +30 -0
  481. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +28 -0
  482. data/lib/datadog/opentelemetry/sdk/propagator.rb +21 -15
  483. data/lib/datadog/opentelemetry/sdk/span_processor.rb +24 -21
  484. data/lib/datadog/opentelemetry/sdk/trace/span.rb +43 -39
  485. data/lib/datadog/opentelemetry/sdk.rb +19 -0
  486. data/lib/datadog/opentelemetry/signal_configuration.rb +57 -0
  487. data/lib/datadog/opentelemetry/trace.rb +8 -5
  488. data/lib/datadog/opentelemetry.rb +11 -6
  489. data/lib/datadog/profiling/collectors/code_provenance.rb +84 -16
  490. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +60 -3
  491. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -4
  492. data/lib/datadog/profiling/collectors/info.rb +62 -8
  493. data/lib/datadog/profiling/collectors/thread_context.rb +25 -18
  494. data/lib/datadog/profiling/component.rb +123 -104
  495. data/lib/datadog/profiling/encoded_profile.rb +11 -0
  496. data/lib/datadog/profiling/exporter.rb +51 -17
  497. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +24 -5
  498. data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
  499. data/lib/datadog/profiling/ext.rb +2 -17
  500. data/lib/datadog/profiling/flush.rb +26 -17
  501. data/lib/datadog/profiling/http_transport.rb +30 -96
  502. data/lib/datadog/profiling/load_native_extension.rb +3 -35
  503. data/lib/datadog/profiling/profiler.rb +31 -12
  504. data/lib/datadog/profiling/scheduler.rb +19 -10
  505. data/lib/datadog/profiling/sequence_tracker.rb +44 -0
  506. data/lib/datadog/profiling/stack_recorder.rb +38 -18
  507. data/lib/datadog/profiling/tag_builder.rb +41 -41
  508. data/lib/datadog/profiling/tasks/exec.rb +11 -6
  509. data/lib/datadog/profiling/tasks/help.rb +1 -0
  510. data/lib/datadog/profiling/tasks/setup.rb +4 -2
  511. data/lib/datadog/profiling.rb +42 -37
  512. data/lib/datadog/ruby_version.rb +25 -0
  513. data/lib/datadog/single_step_instrument.rb +11 -2
  514. data/lib/datadog/symbol_database/component.rb +734 -0
  515. data/lib/datadog/symbol_database/configuration.rb +73 -0
  516. data/lib/datadog/symbol_database/extensions.rb +19 -0
  517. data/lib/datadog/symbol_database/extractor.rb +1092 -0
  518. data/lib/datadog/symbol_database/file_hash.rb +48 -0
  519. data/lib/datadog/symbol_database/logger.rb +43 -0
  520. data/lib/datadog/symbol_database/remote.rb +195 -0
  521. data/lib/datadog/symbol_database/scope.rb +102 -0
  522. data/lib/datadog/symbol_database/scope_batcher.rb +292 -0
  523. data/lib/datadog/symbol_database/service_version.rb +66 -0
  524. data/lib/datadog/symbol_database/symbol.rb +69 -0
  525. data/lib/datadog/symbol_database/transport/http/endpoint.rb +28 -0
  526. data/lib/datadog/symbol_database/transport/http.rb +45 -0
  527. data/lib/datadog/symbol_database/transport.rb +54 -0
  528. data/lib/datadog/symbol_database/uploader.rb +230 -0
  529. data/lib/datadog/symbol_database.rb +71 -0
  530. data/lib/datadog/tracing/analytics.rb +2 -2
  531. data/lib/datadog/tracing/buffer.rb +15 -15
  532. data/lib/datadog/tracing/client_ip.rb +4 -4
  533. data/lib/datadog/tracing/component.rb +63 -23
  534. data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
  535. data/lib/datadog/tracing/configuration/dynamic.rb +16 -16
  536. data/lib/datadog/tracing/configuration/ext.rb +62 -33
  537. data/lib/datadog/tracing/configuration/http.rb +2 -2
  538. data/lib/datadog/tracing/configuration/settings.rb +182 -34
  539. data/lib/datadog/tracing/context.rb +27 -5
  540. data/lib/datadog/tracing/context_provider.rb +12 -8
  541. data/lib/datadog/tracing/contrib/action_cable/configuration/settings.rb +2 -2
  542. data/lib/datadog/tracing/contrib/action_cable/event.rb +5 -5
  543. data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +9 -6
  544. data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +8 -5
  545. data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +9 -6
  546. data/lib/datadog/tracing/contrib/action_cable/events.rb +5 -5
  547. data/lib/datadog/tracing/contrib/action_cable/ext.rb +18 -18
  548. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -5
  549. data/lib/datadog/tracing/contrib/action_cable/integration.rb +10 -7
  550. data/lib/datadog/tracing/contrib/action_cable/patcher.rb +4 -4
  551. data/lib/datadog/tracing/contrib/action_mailer/configuration/settings.rb +2 -2
  552. data/lib/datadog/tracing/contrib/action_mailer/event.rb +7 -4
  553. data/lib/datadog/tracing/contrib/action_mailer/events/deliver.rb +8 -8
  554. data/lib/datadog/tracing/contrib/action_mailer/events/process.rb +4 -4
  555. data/lib/datadog/tracing/contrib/action_mailer/events.rb +3 -3
  556. data/lib/datadog/tracing/contrib/action_mailer/ext.rb +18 -18
  557. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +11 -7
  558. data/lib/datadog/tracing/contrib/action_mailer/patcher.rb +3 -3
  559. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +38 -20
  560. data/lib/datadog/tracing/contrib/action_pack/action_controller/patcher.rb +2 -2
  561. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +48 -13
  562. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +5 -3
  563. data/lib/datadog/tracing/contrib/action_pack/configuration/settings.rb +3 -3
  564. data/lib/datadog/tracing/contrib/action_pack/ext.rb +10 -8
  565. data/lib/datadog/tracing/contrib/action_pack/integration.rb +10 -7
  566. data/lib/datadog/tracing/contrib/action_pack/patcher.rb +3 -3
  567. data/lib/datadog/tracing/contrib/action_pack/utils.rb +2 -3
  568. data/lib/datadog/tracing/contrib/action_view/configuration/settings.rb +3 -3
  569. data/lib/datadog/tracing/contrib/action_view/event.rb +1 -1
  570. data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +10 -7
  571. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +12 -9
  572. data/lib/datadog/tracing/contrib/action_view/events.rb +3 -3
  573. data/lib/datadog/tracing/contrib/action_view/ext.rb +10 -10
  574. data/lib/datadog/tracing/contrib/action_view/integration.rb +14 -11
  575. data/lib/datadog/tracing/contrib/action_view/patcher.rb +5 -5
  576. data/lib/datadog/tracing/contrib/action_view/utils.rb +2 -2
  577. data/lib/datadog/tracing/contrib/active_job/configuration/settings.rb +2 -2
  578. data/lib/datadog/tracing/contrib/active_job/data_streams.rb +62 -0
  579. data/lib/datadog/tracing/contrib/active_job/event.rb +11 -11
  580. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +11 -8
  581. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +11 -8
  582. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +11 -8
  583. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +11 -8
  584. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +11 -8
  585. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +11 -8
  586. data/lib/datadog/tracing/contrib/active_job/events.rb +6 -6
  587. data/lib/datadog/tracing/contrib/active_job/ext.rb +23 -23
  588. data/lib/datadog/tracing/contrib/active_job/integration.rb +10 -7
  589. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
  590. data/lib/datadog/tracing/contrib/active_job/patcher.rb +21 -5
  591. data/lib/datadog/tracing/contrib/active_model_serializers/configuration/settings.rb +2 -2
  592. data/lib/datadog/tracing/contrib/active_model_serializers/event.rb +4 -4
  593. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +8 -8
  594. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +9 -10
  595. data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +3 -3
  596. data/lib/datadog/tracing/contrib/active_model_serializers/ext.rb +10 -10
  597. data/lib/datadog/tracing/contrib/active_model_serializers/integration.rb +5 -6
  598. data/lib/datadog/tracing/contrib/active_model_serializers/patcher.rb +3 -3
  599. data/lib/datadog/tracing/contrib/active_record/configuration/makara_resolver.rb +1 -1
  600. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +9 -9
  601. data/lib/datadog/tracing/contrib/active_record/configuration/settings.rb +4 -4
  602. data/lib/datadog/tracing/contrib/active_record/event.rb +1 -1
  603. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +10 -10
  604. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +17 -19
  605. data/lib/datadog/tracing/contrib/active_record/events.rb +3 -3
  606. data/lib/datadog/tracing/contrib/active_record/ext.rb +15 -15
  607. data/lib/datadog/tracing/contrib/active_record/integration.rb +16 -12
  608. data/lib/datadog/tracing/contrib/active_record/patcher.rb +2 -2
  609. data/lib/datadog/tracing/contrib/active_record/utils.rb +19 -19
  610. data/lib/datadog/tracing/contrib/active_support/cache/event.rb +1 -1
  611. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +29 -21
  612. data/lib/datadog/tracing/contrib/active_support/cache/events.rb +1 -1
  613. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +40 -8
  614. data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +8 -4
  615. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +16 -6
  616. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +26 -3
  617. data/lib/datadog/tracing/contrib/active_support/ext.rb +16 -16
  618. data/lib/datadog/tracing/contrib/active_support/integration.rb +11 -8
  619. data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +3 -2
  620. data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +2 -2
  621. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +9 -11
  622. data/lib/datadog/tracing/contrib/active_support/patcher.rb +2 -2
  623. data/lib/datadog/tracing/contrib/analytics.rb +1 -1
  624. data/lib/datadog/tracing/contrib/auto_instrument.rb +5 -5
  625. data/lib/datadog/tracing/contrib/aws/configuration/settings.rb +3 -3
  626. data/lib/datadog/tracing/contrib/aws/ext.rb +27 -27
  627. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +21 -13
  628. data/lib/datadog/tracing/contrib/aws/integration.rb +11 -8
  629. data/lib/datadog/tracing/contrib/aws/parsed_context.rb +9 -3
  630. data/lib/datadog/tracing/contrib/aws/patcher.rb +10 -6
  631. data/lib/datadog/tracing/contrib/aws/service/base.rb +2 -1
  632. data/lib/datadog/tracing/contrib/aws/service/dynamodb.rb +2 -2
  633. data/lib/datadog/tracing/contrib/aws/service/eventbridge.rb +2 -2
  634. data/lib/datadog/tracing/contrib/aws/service/kinesis.rb +4 -4
  635. data/lib/datadog/tracing/contrib/aws/service/s3.rb +2 -2
  636. data/lib/datadog/tracing/contrib/aws/service/sns.rb +3 -3
  637. data/lib/datadog/tracing/contrib/aws/service/sqs.rb +3 -3
  638. data/lib/datadog/tracing/contrib/aws/service/states.rb +5 -5
  639. data/lib/datadog/tracing/contrib/aws/services.rb +14 -14
  640. data/lib/datadog/tracing/contrib/component.rb +3 -3
  641. data/lib/datadog/tracing/contrib/concurrent_ruby/async_patch.rb +2 -2
  642. data/lib/datadog/tracing/contrib/concurrent_ruby/configuration/settings.rb +2 -2
  643. data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +2 -2
  644. data/lib/datadog/tracing/contrib/concurrent_ruby/ext.rb +2 -2
  645. data/lib/datadog/tracing/contrib/concurrent_ruby/future_patch.rb +2 -2
  646. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +8 -5
  647. data/lib/datadog/tracing/contrib/concurrent_ruby/patcher.rb +4 -4
  648. data/lib/datadog/tracing/contrib/concurrent_ruby/promises_future_patch.rb +2 -2
  649. data/lib/datadog/tracing/contrib/configurable.rb +24 -9
  650. data/lib/datadog/tracing/contrib/configuration/resolver.rb +14 -4
  651. data/lib/datadog/tracing/contrib/configuration/resolvers/pattern_resolver.rb +5 -5
  652. data/lib/datadog/tracing/contrib/configuration/settings.rb +3 -3
  653. data/lib/datadog/tracing/contrib/dalli/configuration/settings.rb +2 -2
  654. data/lib/datadog/tracing/contrib/dalli/ext.rb +16 -15
  655. data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +6 -10
  656. data/lib/datadog/tracing/contrib/dalli/integration.rb +10 -7
  657. data/lib/datadog/tracing/contrib/dalli/patcher.rb +3 -3
  658. data/lib/datadog/tracing/contrib/dalli/quantize.rb +3 -3
  659. data/lib/datadog/tracing/contrib/delayed_job/configuration/settings.rb +2 -2
  660. data/lib/datadog/tracing/contrib/delayed_job/ext.rb +14 -14
  661. data/lib/datadog/tracing/contrib/delayed_job/integration.rb +5 -5
  662. data/lib/datadog/tracing/contrib/delayed_job/patcher.rb +3 -3
  663. data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +7 -5
  664. data/lib/datadog/tracing/contrib/delayed_job/server_internal_tracer/worker.rb +1 -0
  665. data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +6 -2
  666. data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +18 -17
  667. data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +8 -8
  668. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +63 -64
  669. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +9 -9
  670. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +7 -3
  671. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +19 -17
  672. data/lib/datadog/tracing/contrib/ethon/ext.rb +16 -14
  673. data/lib/datadog/tracing/contrib/ethon/integration.rb +6 -6
  674. data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +4 -11
  675. data/lib/datadog/tracing/contrib/ethon/patcher.rb +3 -3
  676. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +21 -10
  677. data/lib/datadog/tracing/contrib/excon/ext.rb +14 -12
  678. data/lib/datadog/tracing/contrib/excon/integration.rb +6 -6
  679. data/lib/datadog/tracing/contrib/excon/middleware.rb +18 -17
  680. data/lib/datadog/tracing/contrib/excon/patcher.rb +2 -2
  681. data/lib/datadog/tracing/contrib/ext.rb +23 -20
  682. data/lib/datadog/tracing/contrib/extensions.rb +57 -14
  683. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +20 -13
  684. data/lib/datadog/tracing/contrib/faraday/ext.rb +14 -12
  685. data/lib/datadog/tracing/contrib/faraday/integration.rb +6 -6
  686. data/lib/datadog/tracing/contrib/faraday/middleware.rb +20 -17
  687. data/lib/datadog/tracing/contrib/faraday/patcher.rb +6 -6
  688. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +11 -7
  689. data/lib/datadog/tracing/contrib/grape/endpoint.rb +69 -36
  690. data/lib/datadog/tracing/contrib/grape/ext.rb +15 -15
  691. data/lib/datadog/tracing/contrib/grape/instrumentation.rb +15 -10
  692. data/lib/datadog/tracing/contrib/grape/integration.rb +5 -5
  693. data/lib/datadog/tracing/contrib/grape/patcher.rb +10 -5
  694. data/lib/datadog/tracing/contrib/graphql/configuration/error_extension_env_parser.rb +21 -0
  695. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +20 -2
  696. data/lib/datadog/tracing/contrib/graphql/ext.rb +11 -5
  697. data/lib/datadog/tracing/contrib/graphql/integration.rb +9 -9
  698. data/lib/datadog/tracing/contrib/graphql/patcher.rb +8 -8
  699. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +174 -41
  700. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +2 -2
  701. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +7 -4
  702. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +26 -24
  703. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +14 -18
  704. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor.rb +6 -6
  705. data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +2 -2
  706. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +13 -8
  707. data/lib/datadog/tracing/contrib/grpc/ext.rb +14 -13
  708. data/lib/datadog/tracing/contrib/grpc/formatting.rb +7 -7
  709. data/lib/datadog/tracing/contrib/grpc/integration.rb +5 -5
  710. data/lib/datadog/tracing/contrib/grpc/intercept_with_datadog.rb +1 -1
  711. data/lib/datadog/tracing/contrib/grpc/patcher.rb +3 -3
  712. data/lib/datadog/tracing/contrib/grpc.rb +7 -6
  713. data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +4 -3
  714. data/lib/datadog/tracing/contrib/hanami/configuration/settings.rb +2 -2
  715. data/lib/datadog/tracing/contrib/hanami/ext.rb +8 -8
  716. data/lib/datadog/tracing/contrib/hanami/integration.rb +6 -6
  717. data/lib/datadog/tracing/contrib/hanami/patcher.rb +5 -5
  718. data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +5 -4
  719. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +14 -14
  720. data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +1 -7
  721. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +20 -9
  722. data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +6 -6
  723. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +13 -8
  724. data/lib/datadog/tracing/contrib/http/ext.rb +14 -12
  725. data/lib/datadog/tracing/contrib/http/instrumentation.rb +20 -24
  726. data/lib/datadog/tracing/contrib/http/integration.rb +9 -8
  727. data/lib/datadog/tracing/contrib/http/patcher.rb +3 -3
  728. data/lib/datadog/tracing/contrib/http.rb +7 -6
  729. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +20 -9
  730. data/lib/datadog/tracing/contrib/httpclient/ext.rb +14 -12
  731. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +21 -27
  732. data/lib/datadog/tracing/contrib/httpclient/integration.rb +6 -6
  733. data/lib/datadog/tracing/contrib/httpclient/patcher.rb +3 -3
  734. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +20 -9
  735. data/lib/datadog/tracing/contrib/httprb/ext.rb +14 -12
  736. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +21 -29
  737. data/lib/datadog/tracing/contrib/httprb/integration.rb +9 -6
  738. data/lib/datadog/tracing/contrib/httprb/patcher.rb +3 -3
  739. data/lib/datadog/tracing/contrib/integration.rb +3 -3
  740. data/lib/datadog/tracing/contrib/kafka/configuration/settings.rb +2 -2
  741. data/lib/datadog/tracing/contrib/kafka/event.rb +5 -4
  742. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +8 -4
  743. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +5 -5
  744. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +5 -5
  745. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +6 -6
  746. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/join_group.rb +6 -6
  747. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/leave_group.rb +6 -6
  748. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/sync_group.rb +6 -6
  749. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +11 -6
  750. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +11 -6
  751. data/lib/datadog/tracing/contrib/kafka/events.rb +10 -10
  752. data/lib/datadog/tracing/contrib/kafka/ext.rb +40 -40
  753. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  754. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  755. data/lib/datadog/tracing/contrib/kafka/integration.rb +8 -5
  756. data/lib/datadog/tracing/contrib/kafka/patcher.rb +17 -3
  757. data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +31 -0
  758. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +50 -0
  759. data/lib/datadog/tracing/contrib/karafka/ext.rb +28 -0
  760. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  761. data/lib/datadog/tracing/contrib/karafka/integration.rb +45 -0
  762. data/lib/datadog/tracing/contrib/karafka/monitor.rb +77 -0
  763. data/lib/datadog/tracing/contrib/karafka/patcher.rb +102 -0
  764. data/lib/datadog/tracing/contrib/karafka.rb +38 -0
  765. data/lib/datadog/tracing/contrib/lograge/configuration/settings.rb +2 -2
  766. data/lib/datadog/tracing/contrib/lograge/ext.rb +1 -1
  767. data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +1 -1
  768. data/lib/datadog/tracing/contrib/lograge/integration.rb +5 -5
  769. data/lib/datadog/tracing/contrib/lograge/patcher.rb +13 -11
  770. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +11 -3
  771. data/lib/datadog/tracing/contrib/mongodb/ext.rb +19 -18
  772. data/lib/datadog/tracing/contrib/mongodb/instrumentation.rb +3 -3
  773. data/lib/datadog/tracing/contrib/mongodb/integration.rb +9 -6
  774. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +10 -10
  775. data/lib/datadog/tracing/contrib/mongodb/patcher.rb +3 -3
  776. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +38 -24
  777. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +9 -3
  778. data/lib/datadog/tracing/contrib/mysql2/ext.rb +12 -12
  779. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +25 -18
  780. data/lib/datadog/tracing/contrib/mysql2/integration.rb +5 -5
  781. data/lib/datadog/tracing/contrib/mysql2/patcher.rb +2 -2
  782. data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +19 -2
  783. data/lib/datadog/tracing/contrib/opensearch/ext.rb +31 -21
  784. data/lib/datadog/tracing/contrib/opensearch/integration.rb +8 -6
  785. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +75 -77
  786. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +10 -10
  787. data/lib/datadog/tracing/contrib/patchable.rb +1 -1
  788. data/lib/datadog/tracing/contrib/patcher.rb +15 -14
  789. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +9 -3
  790. data/lib/datadog/tracing/contrib/pg/ext.rb +19 -19
  791. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +21 -24
  792. data/lib/datadog/tracing/contrib/pg/integration.rb +5 -5
  793. data/lib/datadog/tracing/contrib/pg/patcher.rb +2 -2
  794. data/lib/datadog/tracing/contrib/presto/configuration/settings.rb +2 -2
  795. data/lib/datadog/tracing/contrib/presto/ext.rb +22 -22
  796. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +11 -13
  797. data/lib/datadog/tracing/contrib/presto/integration.rb +8 -5
  798. data/lib/datadog/tracing/contrib/presto/patcher.rb +4 -4
  799. data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +2 -2
  800. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +26 -13
  801. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +7 -3
  802. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +29 -5
  803. data/lib/datadog/tracing/contrib/que/configuration/settings.rb +7 -4
  804. data/lib/datadog/tracing/contrib/que/ext.rb +19 -18
  805. data/lib/datadog/tracing/contrib/que/integration.rb +6 -6
  806. data/lib/datadog/tracing/contrib/que/patcher.rb +1 -1
  807. data/lib/datadog/tracing/contrib/que/tracer.rb +3 -2
  808. data/lib/datadog/tracing/contrib/racecar/configuration/settings.rb +3 -3
  809. data/lib/datadog/tracing/contrib/racecar/event.rb +6 -10
  810. data/lib/datadog/tracing/contrib/racecar/events/batch.rb +5 -5
  811. data/lib/datadog/tracing/contrib/racecar/events/consume.rb +4 -4
  812. data/lib/datadog/tracing/contrib/racecar/events/message.rb +5 -5
  813. data/lib/datadog/tracing/contrib/racecar/events.rb +4 -4
  814. data/lib/datadog/tracing/contrib/racecar/ext.rb +18 -18
  815. data/lib/datadog/tracing/contrib/racecar/integration.rb +5 -5
  816. data/lib/datadog/tracing/contrib/racecar/patcher.rb +3 -3
  817. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +14 -4
  818. data/lib/datadog/tracing/contrib/rack/ext.rb +43 -15
  819. data/lib/datadog/tracing/contrib/rack/header_collection.rb +13 -3
  820. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +55 -32
  821. data/lib/datadog/tracing/contrib/rack/integration.rb +7 -7
  822. data/lib/datadog/tracing/contrib/rack/middlewares.rb +102 -63
  823. data/lib/datadog/tracing/contrib/rack/patcher.rb +6 -6
  824. data/lib/datadog/tracing/contrib/rack/request_queue.rb +8 -7
  825. data/lib/datadog/tracing/contrib/rack/route_inference.rb +67 -0
  826. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +131 -7
  827. data/lib/datadog/tracing/contrib/rails/auto_instrument_railtie.rb +1 -1
  828. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +9 -6
  829. data/lib/datadog/tracing/contrib/rails/ext.rb +16 -14
  830. data/lib/datadog/tracing/contrib/rails/framework.rb +12 -12
  831. data/lib/datadog/tracing/contrib/rails/integration.rb +6 -6
  832. data/lib/datadog/tracing/contrib/rails/log_injection.rb +2 -2
  833. data/lib/datadog/tracing/contrib/rails/middlewares.rb +4 -4
  834. data/lib/datadog/tracing/contrib/rails/patcher.rb +11 -9
  835. data/lib/datadog/tracing/contrib/rails/railtie.rb +4 -4
  836. data/lib/datadog/tracing/contrib/rails/runner.rb +64 -40
  837. data/lib/datadog/tracing/contrib/rake/configuration/settings.rb +3 -3
  838. data/lib/datadog/tracing/contrib/rake/ext.rb +12 -12
  839. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +13 -11
  840. data/lib/datadog/tracing/contrib/rake/integration.rb +5 -5
  841. data/lib/datadog/tracing/contrib/rake/patcher.rb +3 -3
  842. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +5 -5
  843. data/lib/datadog/tracing/contrib/redis/configuration/settings.rb +2 -2
  844. data/lib/datadog/tracing/contrib/redis/ext.rb +19 -18
  845. data/lib/datadog/tracing/contrib/redis/instrumentation.rb +6 -6
  846. data/lib/datadog/tracing/contrib/redis/integration.rb +7 -7
  847. data/lib/datadog/tracing/contrib/redis/patcher.rb +12 -12
  848. data/lib/datadog/tracing/contrib/redis/quantize.rb +41 -7
  849. data/lib/datadog/tracing/contrib/redis/tags.rb +7 -12
  850. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +20 -12
  851. data/lib/datadog/tracing/contrib/registerable.rb +11 -0
  852. data/lib/datadog/tracing/contrib/registry.rb +1 -1
  853. data/lib/datadog/tracing/contrib/resque/configuration/settings.rb +2 -2
  854. data/lib/datadog/tracing/contrib/resque/ext.rb +7 -7
  855. data/lib/datadog/tracing/contrib/resque/integration.rb +5 -5
  856. data/lib/datadog/tracing/contrib/resque/patcher.rb +3 -3
  857. data/lib/datadog/tracing/contrib/resque/resque_job.rb +7 -6
  858. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +7 -4
  859. data/lib/datadog/tracing/contrib/rest_client/ext.rb +13 -11
  860. data/lib/datadog/tracing/contrib/rest_client/integration.rb +8 -5
  861. data/lib/datadog/tracing/contrib/rest_client/patcher.rb +2 -2
  862. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +19 -15
  863. data/lib/datadog/tracing/contrib/roda/configuration/settings.rb +2 -2
  864. data/lib/datadog/tracing/contrib/roda/ext.rb +6 -5
  865. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +12 -7
  866. data/lib/datadog/tracing/contrib/roda/integration.rb +6 -6
  867. data/lib/datadog/tracing/contrib/roda/patcher.rb +3 -3
  868. data/lib/datadog/tracing/contrib/semantic_logger/configuration/settings.rb +2 -2
  869. data/lib/datadog/tracing/contrib/semantic_logger/ext.rb +1 -1
  870. data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +1 -1
  871. data/lib/datadog/tracing/contrib/semantic_logger/integration.rb +5 -5
  872. data/lib/datadog/tracing/contrib/semantic_logger/patcher.rb +2 -2
  873. data/lib/datadog/tracing/contrib/sequel/configuration/settings.rb +2 -2
  874. data/lib/datadog/tracing/contrib/sequel/database.rb +8 -11
  875. data/lib/datadog/tracing/contrib/sequel/dataset.rb +7 -6
  876. data/lib/datadog/tracing/contrib/sequel/ext.rb +12 -8
  877. data/lib/datadog/tracing/contrib/sequel/integration.rb +5 -5
  878. data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
  879. data/lib/datadog/tracing/contrib/sequel/patcher.rb +3 -3
  880. data/lib/datadog/tracing/contrib/sequel/utils.rb +103 -16
  881. data/lib/datadog/tracing/contrib/shoryuken/configuration/settings.rb +2 -2
  882. data/lib/datadog/tracing/contrib/shoryuken/ext.rb +12 -12
  883. data/lib/datadog/tracing/contrib/shoryuken/integration.rb +6 -6
  884. data/lib/datadog/tracing/contrib/shoryuken/patcher.rb +1 -1
  885. data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +3 -2
  886. data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +15 -9
  887. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +7 -3
  888. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +14 -9
  889. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +31 -29
  890. data/lib/datadog/tracing/contrib/sidekiq/integration.rb +7 -7
  891. data/lib/datadog/tracing/contrib/sidekiq/patcher.rb +8 -8
  892. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/heartbeat.rb +2 -0
  893. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/job_fetch.rb +1 -0
  894. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/redis_info.rb +1 -0
  895. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/scheduled_poller.rb +2 -0
  896. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/stop.rb +1 -0
  897. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +19 -15
  898. data/lib/datadog/tracing/contrib/sidekiq/utils.rb +10 -10
  899. data/lib/datadog/tracing/contrib/sidekiq.rb +7 -6
  900. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +8 -4
  901. data/lib/datadog/tracing/contrib/sinatra/env.rb +6 -6
  902. data/lib/datadog/tracing/contrib/sinatra/ext.rb +17 -16
  903. data/lib/datadog/tracing/contrib/sinatra/framework.rb +2 -2
  904. data/lib/datadog/tracing/contrib/sinatra/integration.rb +5 -5
  905. data/lib/datadog/tracing/contrib/sinatra/patcher.rb +5 -5
  906. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +11 -10
  907. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +46 -45
  908. data/lib/datadog/tracing/contrib/sneakers/configuration/settings.rb +1 -1
  909. data/lib/datadog/tracing/contrib/sneakers/ext.rb +12 -12
  910. data/lib/datadog/tracing/contrib/sneakers/integration.rb +19 -8
  911. data/lib/datadog/tracing/contrib/sneakers/patcher.rb +2 -2
  912. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +5 -4
  913. data/lib/datadog/tracing/contrib/span_attribute_schema.rb +9 -4
  914. data/lib/datadog/tracing/contrib/status_range_env_parser.rb +2 -2
  915. data/lib/datadog/tracing/contrib/status_range_matcher.rb +13 -1
  916. data/lib/datadog/tracing/contrib/stripe/configuration/settings.rb +2 -2
  917. data/lib/datadog/tracing/contrib/stripe/ext.rb +12 -12
  918. data/lib/datadog/tracing/contrib/stripe/integration.rb +5 -5
  919. data/lib/datadog/tracing/contrib/stripe/patcher.rb +2 -2
  920. data/lib/datadog/tracing/contrib/stripe/request.rb +5 -5
  921. data/lib/datadog/tracing/contrib/sucker_punch/configuration/settings.rb +2 -2
  922. data/lib/datadog/tracing/contrib/sucker_punch/exception_handler.rb +1 -1
  923. data/lib/datadog/tracing/contrib/sucker_punch/ext.rb +13 -13
  924. data/lib/datadog/tracing/contrib/sucker_punch/instrumentation.rb +5 -4
  925. data/lib/datadog/tracing/contrib/sucker_punch/integration.rb +5 -5
  926. data/lib/datadog/tracing/contrib/sucker_punch/patcher.rb +4 -4
  927. data/lib/datadog/tracing/contrib/support.rb +28 -0
  928. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +9 -3
  929. data/lib/datadog/tracing/contrib/trilogy/ext.rb +12 -12
  930. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +10 -12
  931. data/lib/datadog/tracing/contrib/trilogy/integration.rb +5 -5
  932. data/lib/datadog/tracing/contrib/trilogy/patcher.rb +2 -2
  933. data/lib/datadog/tracing/contrib/utils/database.rb +5 -5
  934. data/lib/datadog/tracing/contrib/utils/quantization/{hash.rb → hash_formatter.rb} +17 -15
  935. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +16 -16
  936. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +31 -0
  937. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +50 -0
  938. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +18 -0
  939. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  940. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  941. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +49 -0
  942. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  943. data/lib/datadog/tracing/contrib/waterdrop.rb +42 -0
  944. data/lib/datadog/tracing/contrib.rb +62 -52
  945. data/lib/datadog/tracing/correlation.rb +21 -14
  946. data/lib/datadog/tracing/diagnostics/environment_logger.rb +23 -15
  947. data/lib/datadog/tracing/diagnostics/ext.rb +19 -19
  948. data/lib/datadog/tracing/diagnostics/health.rb +1 -1
  949. data/lib/datadog/tracing/distributed/b3_multi.rb +9 -9
  950. data/lib/datadog/tracing/distributed/b3_single.rb +6 -6
  951. data/lib/datadog/tracing/distributed/baggage.rb +250 -0
  952. data/lib/datadog/tracing/distributed/datadog.rb +41 -38
  953. data/lib/datadog/tracing/distributed/fetcher.rb +1 -1
  954. data/lib/datadog/tracing/distributed/helpers.rb +4 -4
  955. data/lib/datadog/tracing/distributed/none.rb +4 -2
  956. data/lib/datadog/tracing/distributed/propagation.rb +73 -14
  957. data/lib/datadog/tracing/distributed/propagation_policy.rb +47 -0
  958. data/lib/datadog/tracing/distributed/trace_context.rb +31 -259
  959. data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
  960. data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
  961. data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
  962. data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
  963. data/lib/datadog/tracing/event.rb +6 -8
  964. data/lib/datadog/tracing/ext.rb +9 -0
  965. data/lib/datadog/tracing/flush.rb +1 -1
  966. data/lib/datadog/tracing/metadata/analytics.rb +3 -3
  967. data/lib/datadog/tracing/metadata/errors.rb +8 -8
  968. data/lib/datadog/tracing/metadata/ext.rb +89 -59
  969. data/lib/datadog/tracing/metadata/metastruct.rb +36 -0
  970. data/lib/datadog/tracing/metadata/metastruct_tagging.rb +42 -0
  971. data/lib/datadog/tracing/metadata/tagging.rb +9 -9
  972. data/lib/datadog/tracing/metadata.rb +5 -3
  973. data/lib/datadog/tracing/otel_thread_context.rb +99 -0
  974. data/lib/datadog/tracing/pipeline/span_filter.rb +5 -3
  975. data/lib/datadog/tracing/pipeline/span_processor.rb +3 -1
  976. data/lib/datadog/tracing/pipeline.rb +4 -4
  977. data/lib/datadog/tracing/remote.rb +228 -30
  978. data/lib/datadog/tracing/sampling/all_sampler.rb +1 -1
  979. data/lib/datadog/tracing/sampling/ext.rb +9 -9
  980. data/lib/datadog/tracing/sampling/matcher.rb +4 -4
  981. data/lib/datadog/tracing/sampling/priority_sampler.rb +20 -5
  982. data/lib/datadog/tracing/sampling/rate_by_key_sampler.rb +3 -3
  983. data/lib/datadog/tracing/sampling/rate_by_service_sampler.rb +2 -2
  984. data/lib/datadog/tracing/sampling/rate_sampler.rb +9 -19
  985. data/lib/datadog/tracing/sampling/rule.rb +5 -5
  986. data/lib/datadog/tracing/sampling/rule_sampler.rb +91 -58
  987. data/lib/datadog/tracing/sampling/sampler.rb +2 -2
  988. data/lib/datadog/tracing/sampling/span/ext.rb +3 -3
  989. data/lib/datadog/tracing/sampling/span/matcher.rb +2 -2
  990. data/lib/datadog/tracing/sampling/span/rule.rb +1 -2
  991. data/lib/datadog/tracing/sampling/span/rule_parser.rb +11 -11
  992. data/lib/datadog/tracing/sampling/span/sampler.rb +0 -7
  993. data/lib/datadog/tracing/span.rb +29 -12
  994. data/lib/datadog/tracing/span_event.rb +125 -5
  995. data/lib/datadog/tracing/span_link.rb +12 -12
  996. data/lib/datadog/tracing/span_operation.rb +120 -40
  997. data/lib/datadog/tracing/sync_writer.rb +22 -14
  998. data/lib/datadog/tracing/trace_digest.rb +58 -28
  999. data/lib/datadog/tracing/trace_operation.rb +263 -120
  1000. data/lib/datadog/tracing/trace_segment.rb +18 -16
  1001. data/lib/datadog/tracing/tracer.rb +208 -73
  1002. data/lib/datadog/tracing/transport/http/client.rb +17 -30
  1003. data/lib/datadog/tracing/transport/http/statistics.rb +2 -2
  1004. data/lib/datadog/tracing/transport/http/traces.rb +11 -90
  1005. data/lib/datadog/tracing/transport/http.rb +26 -73
  1006. data/lib/datadog/tracing/transport/io/client.rb +13 -16
  1007. data/lib/datadog/tracing/transport/io/response.rb +1 -1
  1008. data/lib/datadog/tracing/transport/io/traces.rb +33 -39
  1009. data/lib/datadog/tracing/transport/io.rb +3 -3
  1010. data/lib/datadog/tracing/transport/native/response.rb +71 -0
  1011. data/lib/datadog/tracing/transport/native.rb +379 -0
  1012. data/lib/datadog/tracing/transport/serializable_trace.rb +47 -23
  1013. data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
  1014. data/lib/datadog/tracing/transport/statistics.rb +3 -3
  1015. data/lib/datadog/tracing/transport/trace_formatter.rb +49 -10
  1016. data/lib/datadog/tracing/transport/traces.rb +45 -99
  1017. data/lib/datadog/tracing/utils.rb +3 -3
  1018. data/lib/datadog/tracing/workers.rb +12 -10
  1019. data/lib/datadog/tracing/writer.rb +54 -39
  1020. data/lib/datadog/tracing.rb +19 -6
  1021. data/lib/datadog/version.rb +4 -4
  1022. data/lib/datadog.rb +21 -5
  1023. metadata +372 -70
  1024. data/ext/datadog_profiling_loader/datadog_profiling_loader.c +0 -142
  1025. data/ext/datadog_profiling_loader/extconf.rb +0 -60
  1026. data/ext/datadog_profiling_native_extension/clock_id_noop.c +0 -21
  1027. data/ext/libdatadog_api/macos_development.md +0 -26
  1028. data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -92
  1029. data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -114
  1030. data/lib/datadog/appsec/configuration/settings.rb +0 -207
  1031. data/lib/datadog/appsec/contrib/devise/event.rb +0 -57
  1032. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +0 -77
  1033. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +0 -54
  1034. data/lib/datadog/appsec/contrib/devise/resource.rb +0 -35
  1035. data/lib/datadog/appsec/contrib/devise/tracking.rb +0 -57
  1036. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +0 -59
  1037. data/lib/datadog/appsec/contrib/patcher.rb +0 -12
  1038. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +0 -81
  1039. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +0 -60
  1040. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +0 -66
  1041. data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
  1042. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +0 -66
  1043. data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
  1044. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +0 -61
  1045. data/lib/datadog/appsec/monitor/reactive/set_user.rb +0 -58
  1046. data/lib/datadog/appsec/processor/actions.rb +0 -49
  1047. data/lib/datadog/appsec/processor/rule_merger.rb +0 -170
  1048. data/lib/datadog/appsec/processor.rb +0 -170
  1049. data/lib/datadog/appsec/reactive/address_hash.rb +0 -22
  1050. data/lib/datadog/appsec/reactive/engine.rb +0 -47
  1051. data/lib/datadog/appsec/reactive/operation.rb +0 -68
  1052. data/lib/datadog/appsec/reactive/subscriber.rb +0 -19
  1053. data/lib/datadog/appsec/scope.rb +0 -61
  1054. data/lib/datadog/core/crashtracking/agent_base_url.rb +0 -21
  1055. data/lib/datadog/core/remote/transport/http/api/instance.rb +0 -39
  1056. data/lib/datadog/core/remote/transport/http/api/spec.rb +0 -21
  1057. data/lib/datadog/core/remote/transport/http/api.rb +0 -58
  1058. data/lib/datadog/core/remote/transport/http/builder.rb +0 -219
  1059. data/lib/datadog/core/remote/transport/http/client.rb +0 -48
  1060. data/lib/datadog/core/telemetry/http/env.rb +0 -20
  1061. data/lib/datadog/core/telemetry/http/ext.rb +0 -28
  1062. data/lib/datadog/core/telemetry/http/response.rb +0 -70
  1063. data/lib/datadog/core/telemetry/http/transport.rb +0 -90
  1064. data/lib/datadog/core/utils/base64.rb +0 -22
  1065. data/lib/datadog/di/configuration/settings.rb +0 -163
  1066. data/lib/datadog/tracing/contrib/rails/utils.rb +0 -26
  1067. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -84
  1068. data/lib/datadog/tracing/transport/http/api/spec.rb +0 -19
  1069. data/lib/datadog/tracing/transport/http/api.rb +0 -43
  1070. data/lib/datadog/tracing/workers/trace_writer.rb +0 -196
@@ -17,14 +17,11 @@
17
17
  #include "setup_signal_handler.h"
18
18
  #include "time_helpers.h"
19
19
 
20
- #define ERR_CLOCK_FAIL "failed to get clock time"
21
-
22
- // Maximum allowed value for an allocation weight. Attempts to use higher values will result in clamping.
23
- // See https://docs.google.com/document/d/1lWLB714wlLBBq6T4xZyAc4a5wtWhSmr4-hgiPKeErlA/edit#heading=h.ugp0zxcj5iqh
24
- // (Datadog-only link) for research backing the choice of this value.
25
- unsigned int MAX_ALLOC_WEIGHT = 10000;
20
+ #ifdef __APPLE__
21
+ #include "macos_sampler_thread.h"
22
+ #endif
26
23
 
27
- // Used to trigger the execution of Collectors::ThreadState, which implements all of the sampling logic
24
+ // Used to trigger the execution of Collectors::ThreadContext, which implements all of the sampling logic
28
25
  // itself; this class only implements the "when to do it" part.
29
26
  //
30
27
  // This file implements the native bits of the Datadog::Profiling::Collectors::CpuAndWallTimeWorker class
@@ -40,7 +37,7 @@ unsigned int MAX_ALLOC_WEIGHT = 10000;
40
37
  // Currently, sampling Ruby threads requires calling Ruby VM APIs that are only safe to call while holding on to the
41
38
  // global VM lock (and are not async-signal safe -- cannot be called from a signal handler).
42
39
  //
43
- // @ivoanjo: As a note, I don't think we should think of this constraint as set in stone. Since can reach into the Ruby
40
+ // @ivoanjo: As a note, I don't think we should think of this constraint as set in stone. Since we can reach inside the Ruby
44
41
  // internals, we may be able to figure out a way of overcoming it. But it's definitely going to be hard so for now
45
42
  // we're considering it as a given.
46
43
  //
@@ -71,28 +68,44 @@ unsigned int MAX_ALLOC_WEIGHT = 10000;
71
68
  // 4. The Ruby VM calls our `sample_from_postponed_job` from a thread holding the global VM lock. A sample is recorded by
72
69
  // calling `thread_context_collector_sample`.
73
70
  //
74
- // ### TracePoints and Forking
71
+ //
72
+ // ### Hooks and TracePoints
73
+ //
74
+ // This class uses various hooks:
75
+ // * A RUBY_INTERNAL_EVENT_GC_ENTER & RUBY_INTERNAL_EVENT_GC_EXIT TracePoint
76
+ // * A RUBY_INTERNAL_EVENT_NEWOBJ "event hook"/internal tracepoint
77
+ // * A GVL thread event hook
78
+ //
79
+ // We refer to those collectively as "hooks".
80
+ //
81
+ // ### Hooks and Forking
75
82
  //
76
83
  // When the Ruby VM forks, the CPU/Wall-time profiling stops naturally because it's triggered by a background thread
77
84
  // that doesn't get automatically restarted by the VM on the child process. (The profiler does trigger its restart at
78
85
  // some point -- see `Profiling::Tasks::Setup` for details).
79
86
  //
80
- // But this doesn't apply to any `TracePoint`s this class may use, which will continue to be active. Thus, we need to
81
- // always remember consider this case of -- the worker thread may not be alive but the `TracePoint`s can continue to
87
+ // But this doesn't apply to any hooks this class may use, which will continue to be active. Thus, we need to
88
+ // always remember consider this case of -- the worker thread may not be alive but the hooks can continue to
82
89
  // trigger samples.
83
90
  //
84
91
  // ---
85
92
 
93
+ // Maximum allowed value for an allocation weight. Attempts to use higher values will result in clamping.
94
+ // See https://docs.google.com/document/d/1lWLB714wlLBBq6T4xZyAc4a5wtWhSmr4-hgiPKeErlA/edit#heading=h.ugp0zxcj5iqh
95
+ // (Datadog-only link) for research backing the choice of this value.
96
+ unsigned int MAX_ALLOC_WEIGHT = 10000;
97
+
86
98
  #ifndef NO_POSTPONED_TRIGGER
87
99
  // Used to call the rb_postponed_job_trigger from Ruby 3.3+. These get initialized in
88
100
  // `collectors_cpu_and_wall_time_worker_init` below and always get reused after that.
89
101
  static rb_postponed_job_handle_t sample_from_postponed_job_handle;
90
102
  static rb_postponed_job_handle_t after_gc_from_postponed_job_handle;
91
103
  static rb_postponed_job_handle_t after_gvl_running_from_postponed_job_handle;
104
+ static rb_postponed_job_handle_t commit_heap_recordings_from_postponed_job_may_lose_gvl_handle;
92
105
  #endif
93
106
 
94
107
  // Contains state for a single CpuAndWallTimeWorker instance
95
- struct cpu_and_wall_time_worker_state {
108
+ typedef struct {
96
109
  // These are immutable after initialization
97
110
 
98
111
  bool gc_profiling_enabled;
@@ -102,6 +115,10 @@ struct cpu_and_wall_time_worker_state {
102
115
  bool allocation_counting_enabled;
103
116
  bool gvl_profiling_enabled;
104
117
  bool skip_idle_samples_for_testing;
118
+ bool sighandler_sampling_enabled;
119
+ uint32_t cpu_sampling_interval_ms;
120
+ // Minimum duration of a "Waiting for GVL" period to trigger a sample
121
+ uint32_t waiting_for_gvl_threshold_ns;
105
122
  VALUE self_instance;
106
123
  VALUE thread_context_collector_instance;
107
124
  VALUE idle_sampling_helper_instance;
@@ -116,13 +133,29 @@ struct cpu_and_wall_time_worker_state {
116
133
  // When something goes wrong during sampling, we record the Ruby exception here, so that it can be "re-raised" on
117
134
  // the CpuAndWallTimeWorker thread
118
135
  VALUE failure_exception;
136
+ const char *failure_exception_during_operation;
119
137
  // Used by `_native_stop` to flag the worker thread to start (see comment on `_native_sampling_loop`)
120
138
  VALUE stop_thread;
121
139
 
122
140
  // Others
123
141
 
124
- // Used to detect/avoid nested sampling, e.g. when on_newobj_event gets triggered by a memory allocation
125
- // that happens during another sample.
142
+ // Used to detect/avoid nested sampling, and intended to behave as a lock to ensure the profiler doesn't recurse on itself,
143
+ // e.g. when on_newobj_event gets triggered by a memory allocation that happens during another sample, or when the
144
+ // signal handler gets triggered while we're already in the middle of sampling.
145
+ //
146
+ // It's not an actual lock because we rely on the GVL for correct synchronization
147
+ // (and thus this flag is only valid when we know we have the GVL).
148
+ //
149
+ // Similar to a lock, it should not be held across long-running operations,
150
+ // **in particular it MUST NEVER be held during operations where we might lose the GVL**
151
+ // because effectively that would stop the profiler from working until control
152
+ // goes back to that special thread, which on a contended Ruby app, can take hundreds of ms (or more).
153
+ //
154
+ // Because what we want is "profiler doesn't recurse on itself" we want this to behave as a non-reentrant lock
155
+ // (FIXME: We should have checks for this)
156
+ //
157
+ // @ivoanjo: Right now we always sample inside `safely_call`; if that ever changes, this flag may need to become
158
+ // volatile/atomic/have some barriers to ensure it's visible during e.g. signal handlers.
126
159
  bool during_sample;
127
160
 
128
161
  #ifndef NO_GVL_INSTRUMENTATION
@@ -134,27 +167,19 @@ struct cpu_and_wall_time_worker_state {
134
167
  // # Generic stats
135
168
  // How many times we tried to trigger a sample
136
169
  unsigned int trigger_sample_attempts;
170
+ // How many times extra sleep was triggered by dynamic sampling rate
171
+ unsigned int trigger_sample_extra_sleep;
137
172
  // How many times we tried to simulate signal delivery
138
173
  unsigned int trigger_simulated_signal_delivery_attempts;
139
174
  // How many times we actually simulated signal delivery
140
175
  unsigned int simulated_signal_delivery;
141
- // How many times we actually called rb_postponed_job_register_one from a signal handler
176
+ // How many times we actually called rb_postponed_job_register_one from the signal handler
142
177
  unsigned int signal_handler_enqueued_sample;
143
- // How many times the signal handler was called from the wrong thread
144
- unsigned int signal_handler_wrong_thread;
178
+ // How many times we prepared a sample (sampled directly) from the signal handler
179
+ unsigned int signal_handler_prepared_sample;
145
180
  // How many times we actually tried to interrupt a thread for sampling
146
181
  unsigned int interrupt_thread_attempts;
147
182
 
148
- // # Stats for the results of calling rb_postponed_job_register_one
149
- // The same function was already waiting to be executed
150
- unsigned int postponed_job_skipped_already_existed;
151
- // The function was added to the queue successfully
152
- unsigned int postponed_job_success;
153
- // The queue was full
154
- unsigned int postponed_job_full;
155
- // The function returned an unknown result code
156
- unsigned int postponed_job_unknown_result;
157
-
158
183
  // # CPU/Walltime sampling stats
159
184
  // How many times we actually CPU/wall sampled
160
185
  unsigned int cpu_sampled;
@@ -178,35 +203,58 @@ struct cpu_and_wall_time_worker_state {
178
203
  unsigned int allocations_during_sample;
179
204
 
180
205
  // # GVL profiling stats
206
+ // Note that this tracks two kind of samples from RESUMED:
207
+ // * samples when Waiting for GVL for more than the threshold
208
+ // * samples for the skip-samples-while-without-GVL optimization,
209
+ // which are needed to attribute the time without GVL to the correct Ruby stack
210
+
181
211
  // How many times we triggered the after_gvl_running sampling
182
212
  unsigned int after_gvl_running;
213
+ // How many times we skipped the after_gvl_running sampling
214
+ unsigned int gvl_dont_sample;
215
+ // Min/max/total wall-time spent on gvl sampling
216
+ uint64_t gvl_sampling_time_ns_min;
217
+ uint64_t gvl_sampling_time_ns_max;
218
+ uint64_t gvl_sampling_time_ns_total;
219
+
220
+ struct vm_metrics {
221
+ // Total time spent waiting for the GVL
222
+ uint64_t gvl_waiting_time_ns_total;
223
+ } vm_metrics;
183
224
  } stats;
184
- };
225
+ } cpu_and_wall_time_worker_state;
185
226
 
186
227
  static VALUE _native_new(VALUE klass);
187
228
  static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
188
229
  static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr);
189
230
  static VALUE _native_sampling_loop(VALUE self, VALUE instance);
231
+ static VALUE _native_profiler_internal_thread_done(VALUE self_instance);
190
232
  static VALUE _native_stop(DDTRACE_UNUSED VALUE _self, VALUE self_instance, VALUE worker_thread);
191
- static VALUE stop(VALUE self_instance, VALUE optional_exception);
192
- static void stop_state(struct cpu_and_wall_time_worker_state *state, VALUE optional_exception);
233
+ static VALUE stop(VALUE self_instance, VALUE optional_exception, const char *optional_exception_during_operation);
234
+ static void stop_state(cpu_and_wall_time_worker_state *state, VALUE optional_exception, const char *optional_operation_name);
193
235
  static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
194
236
  static void *run_sampling_trigger_loop(void *state_ptr);
195
237
  static void interrupt_sampling_trigger_loop(void *state_ptr);
196
238
  static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused);
197
239
  static VALUE rescued_sample_from_postponed_job(VALUE self_instance);
198
- static VALUE handle_sampling_failure(VALUE self_instance, VALUE exception);
199
240
  static VALUE _native_current_sigprof_signal_handler(DDTRACE_UNUSED VALUE self);
200
241
  static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance);
201
242
  static VALUE _native_is_running(DDTRACE_UNUSED VALUE self, VALUE instance);
243
+ static VALUE _native_failure_exception_during_operation(DDTRACE_UNUSED VALUE self, VALUE instance);
202
244
  static void testing_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
203
245
  static VALUE _native_install_testing_signal_handler(DDTRACE_UNUSED VALUE self);
204
246
  static VALUE _native_remove_testing_signal_handler(DDTRACE_UNUSED VALUE self);
247
+ static VALUE _native_install_sigprof_handler_on_altstack(DDTRACE_UNUSED VALUE self);
205
248
  static VALUE _native_trigger_sample(DDTRACE_UNUSED VALUE self);
206
249
  static VALUE _native_gc_tracepoint(DDTRACE_UNUSED VALUE self, VALUE instance);
207
250
  static void on_gc_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused);
208
251
  static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused);
209
- static VALUE safely_call(VALUE (*function_to_call_safely)(VALUE), VALUE function_to_call_safely_arg, VALUE instance);
252
+ static VALUE safely_call(
253
+ VALUE (*function_to_call_safely)(VALUE),
254
+ VALUE function_to_call_safely_arg,
255
+ VALUE instance,
256
+ VALUE (*handle_sampling_failure)(VALUE, VALUE)
257
+ );
210
258
  static VALUE _native_simulate_handle_sampling_signal(DDTRACE_UNUSED VALUE self);
211
259
  static VALUE _native_simulate_sample_from_postponed_job(DDTRACE_UNUSED VALUE self);
212
260
  static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE instance);
@@ -215,22 +263,35 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance);
215
263
  static VALUE _native_stats_reset_not_thread_safe(DDTRACE_UNUSED VALUE self, VALUE instance);
216
264
  void *simulate_sampling_signal_delivery(DDTRACE_UNUSED void *_unused);
217
265
  static void grab_gvl_and_sample(void);
218
- static void reset_stats_not_thread_safe(struct cpu_and_wall_time_worker_state *state);
266
+ static void reset_stats_not_thread_safe(cpu_and_wall_time_worker_state *state);
219
267
  static void sleep_for(uint64_t time_ns);
220
268
  static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self);
221
269
  static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *unused2);
222
- static void disable_tracepoints(struct cpu_and_wall_time_worker_state *state);
270
+ static void disable_hooks(cpu_and_wall_time_worker_state *state);
223
271
  static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self);
224
272
  static VALUE rescued_sample_allocation(VALUE tracepoint_data);
225
- static void delayed_error(struct cpu_and_wall_time_worker_state *state, const char *error);
273
+ static VALUE rescued_commit_heap_recordings_may_lose_gvl(VALUE self_instance);
274
+ static void delayed_error(cpu_and_wall_time_worker_state *state, const char *error);
275
+ static void delayed_error_clock_failure(cpu_and_wall_time_worker_state *state);
226
276
  static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg);
227
277
  static VALUE _native_hold_signals(DDTRACE_UNUSED VALUE self);
228
278
  static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self);
229
279
  #ifndef NO_GVL_INSTRUMENTATION
230
- static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused);
231
- static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused);
280
+ static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused);
281
+ static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused);
282
+ static VALUE rescued_after_gvl_running_from_postponed_job(VALUE self_instance);
283
+ static VALUE handle_sampling_failure_rescued_after_gvl_running_from_postponed_job(VALUE self_instance, VALUE exception);
232
284
  #endif
233
285
  static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE instance);
286
+ static VALUE handle_sampling_failure_rescued_sample_from_postponed_job(VALUE self_instance, VALUE exception);
287
+ static VALUE handle_sampling_failure_thread_context_collector_sample_after_gc(VALUE self_instance, VALUE exception);
288
+ static VALUE handle_sampling_failure_thread_context_collector_heap_update(VALUE self_instance, VALUE exception);
289
+ static VALUE handle_sampling_failure_rescued_sample_allocation(VALUE self_instance, VALUE exception);
290
+ static VALUE handle_sampling_failure_rescued_commit_heap_recordings(VALUE self_instance, VALUE exception);
291
+ static inline void during_sample_enter(cpu_and_wall_time_worker_state* state);
292
+ static inline void during_sample_exit(cpu_and_wall_time_worker_state* state);
293
+ static VALUE during_sample_exit_rescue(VALUE state_ptr);
294
+ static void commit_heap_recordings_from_postponed_job_may_lose_gvl(DDTRACE_UNUSED void *_unused);
234
295
 
235
296
  // We're using `on_newobj_event` function with `rb_add_event_hook2`, which requires in its public signature a function
236
297
  // with signature `rb_event_hook_func_t` which doesn't match `on_newobj_event`.
@@ -255,7 +316,17 @@ static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE
255
316
  // This global state is needed because a bunch of functions on this file need to access it from situations
256
317
  // (e.g. signal handler) where it's impossible or just awkward to pass it as an argument.
257
318
  static VALUE active_sampler_instance = Qnil;
258
- static struct cpu_and_wall_time_worker_state *active_sampler_instance_state = NULL;
319
+ static cpu_and_wall_time_worker_state *active_sampler_instance_state = NULL;
320
+ static VALUE clock_failure_exception_class = Qnil;
321
+
322
+ // Stats that live outside of any particular `cpu_and_wall_time_worker_state`, so that they can always be safely
323
+ // touched, even when we're not sure it's safe to touch the state (e.g. signal handler, no GVL, etc).
324
+ typedef struct {
325
+ // How many times we skipped sampling because we were running on the alternate signal stack (e.g. nested inside
326
+ // another signal handler, presumably GC compaction).
327
+ unsigned int signal_handler_skipped_sample_on_altstack;
328
+ } global_stats_t;
329
+ static global_stats_t global_stats;
259
330
 
260
331
  // See handle_sampling_signal for details on what this does
261
332
  #ifdef NO_POSTPONED_TRIGGER
@@ -277,13 +348,16 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
277
348
  sample_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, sample_from_postponed_job, NULL);
278
349
  after_gc_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_gc_from_postponed_job, NULL);
279
350
  after_gvl_running_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_gvl_running_from_postponed_job, NULL);
351
+ commit_heap_recordings_from_postponed_job_may_lose_gvl_handle =
352
+ rb_postponed_job_preregister(unused_flags, commit_heap_recordings_from_postponed_job_may_lose_gvl, NULL);
280
353
 
281
354
  if (
282
355
  sample_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
283
356
  after_gc_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
284
- after_gvl_running_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID
357
+ after_gvl_running_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
358
+ commit_heap_recordings_from_postponed_job_may_lose_gvl_handle == POSTPONED_JOB_HANDLE_INVALID
285
359
  ) {
286
- rb_raise(rb_eRuntimeError, "Failed to register profiler postponed jobs (got POSTPONED_JOB_HANDLE_INVALID)");
360
+ raise_error(rb_eRuntimeError, "Failed to register profiler postponed jobs (got POSTPONED_JOB_HANDLE_INVALID)");
287
361
  }
288
362
  #else
289
363
  gc_finalize_deferred_workaround = objspace_ptr_for_gc_finalize_deferred_workaround();
@@ -293,6 +367,8 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
293
367
  VALUE collectors_cpu_and_wall_time_worker_class = rb_define_class_under(collectors_module, "CpuAndWallTimeWorker", rb_cObject);
294
368
  // Hosts methods used for testing the native code using RSpec
295
369
  VALUE testing_module = rb_define_module_under(collectors_cpu_and_wall_time_worker_class, "Testing");
370
+ clock_failure_exception_class = rb_define_class_under(collectors_cpu_and_wall_time_worker_class, "ClockFailure", rb_eRuntimeError);
371
+ rb_gc_register_mark_object(clock_failure_exception_class);
296
372
 
297
373
  // Instances of the CpuAndWallTimeWorker class are "TypedData" objects.
298
374
  // "TypedData" objects are special objects in the Ruby VM that can wrap C structs.
@@ -306,19 +382,20 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
306
382
 
307
383
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_initialize", _native_initialize, -1);
308
384
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_sampling_loop", _native_sampling_loop, 1);
385
+ rb_define_method(collectors_cpu_and_wall_time_worker_class, "_native_profiler_internal_thread_done", _native_profiler_internal_thread_done, 0);
309
386
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_stop", _native_stop, 2);
310
387
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
311
388
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_stats", _native_stats, 1);
312
389
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_stats_reset_not_thread_safe", _native_stats_reset_not_thread_safe, 1);
313
390
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_allocation_count", _native_allocation_count, 0);
314
391
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_is_running?", _native_is_running, 1);
392
+ rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_failure_exception_during_operation", _native_failure_exception_during_operation, 1);
315
393
  rb_define_singleton_method(testing_module, "_native_current_sigprof_signal_handler", _native_current_sigprof_signal_handler, 0);
316
394
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_hold_signals", _native_hold_signals, 0);
317
395
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_resume_signals", _native_resume_signals, 0);
318
- // TODO: Remove `_native_is_running` from `testing_module` (should be in class) once `prof-correctness` has been updated to not need it
319
- rb_define_singleton_method(testing_module, "_native_is_running?", _native_is_running, 1);
320
396
  rb_define_singleton_method(testing_module, "_native_install_testing_signal_handler", _native_install_testing_signal_handler, 0);
321
397
  rb_define_singleton_method(testing_module, "_native_remove_testing_signal_handler", _native_remove_testing_signal_handler, 0);
398
+ rb_define_singleton_method(testing_module, "_native_install_sigprof_handler_on_altstack", _native_install_sigprof_handler_on_altstack, 0);
322
399
  rb_define_singleton_method(testing_module, "_native_trigger_sample", _native_trigger_sample, 0);
323
400
  rb_define_singleton_method(testing_module, "_native_gc_tracepoint", _native_gc_tracepoint, 1);
324
401
  rb_define_singleton_method(testing_module, "_native_simulate_handle_sampling_signal", _native_simulate_handle_sampling_signal, 0);
@@ -329,7 +406,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
329
406
  rb_define_singleton_method(testing_module, "_native_gvl_profiling_hook_active", _native_gvl_profiling_hook_active, 1);
330
407
  }
331
408
 
332
- // This structure is used to define a Ruby object that stores a pointer to a struct cpu_and_wall_time_worker_state
409
+ // This structure is used to define a Ruby object that stores a pointer to a cpu_and_wall_time_worker_state
333
410
  // See also https://github.com/ruby/ruby/blob/master/doc/extension.rdoc for how this works
334
411
  static const rb_data_type_t cpu_and_wall_time_worker_typed_data = {
335
412
  .wrap_struct_name = "Datadog::Profiling::Collectors::CpuAndWallTimeWorker",
@@ -345,7 +422,7 @@ static const rb_data_type_t cpu_and_wall_time_worker_typed_data = {
345
422
  static VALUE _native_new(VALUE klass) {
346
423
  long now = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
347
424
 
348
- struct cpu_and_wall_time_worker_state *state = ruby_xcalloc(1, sizeof(struct cpu_and_wall_time_worker_state));
425
+ cpu_and_wall_time_worker_state *state = ruby_xcalloc(1, sizeof(cpu_and_wall_time_worker_state));
349
426
 
350
427
  // Note: Any exceptions raised from this note until the TypedData_Wrap_Struct call will lead to the state memory
351
428
  // being leaked.
@@ -357,6 +434,9 @@ static VALUE _native_new(VALUE klass) {
357
434
  state->allocation_counting_enabled = false;
358
435
  state->gvl_profiling_enabled = false;
359
436
  state->skip_idle_samples_for_testing = false;
437
+ state->sighandler_sampling_enabled = false;
438
+ state->cpu_sampling_interval_ms = 10;
439
+ state->waiting_for_gvl_threshold_ns = 10 * 1000 * 1000;
360
440
  state->thread_context_collector_instance = Qnil;
361
441
  state->idle_sampling_helper_instance = Qnil;
362
442
  state->owner_thread = Qnil;
@@ -365,9 +445,10 @@ static VALUE _native_new(VALUE klass) {
365
445
 
366
446
  atomic_init(&state->should_run, false);
367
447
  state->failure_exception = Qnil;
448
+ state->failure_exception_during_operation = NULL;
368
449
  state->stop_thread = Qnil;
369
450
 
370
- state->during_sample = false;
451
+ during_sample_exit(state);
371
452
 
372
453
  #ifndef NO_GVL_INSTRUMENTATION
373
454
  state->gvl_profiling_hook = NULL;
@@ -399,6 +480,9 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
399
480
  VALUE allocation_counting_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("allocation_counting_enabled")));
400
481
  VALUE gvl_profiling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("gvl_profiling_enabled")));
401
482
  VALUE skip_idle_samples_for_testing = rb_hash_fetch(options, ID2SYM(rb_intern("skip_idle_samples_for_testing")));
483
+ VALUE sighandler_sampling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("sighandler_sampling_enabled")));
484
+ VALUE cpu_sampling_interval_ms = rb_hash_fetch(options, ID2SYM(rb_intern("cpu_sampling_interval_ms")));
485
+ VALUE waiting_for_gvl_threshold_ns = rb_hash_fetch(options, ID2SYM(rb_intern("waiting_for_gvl_threshold_ns")));
402
486
 
403
487
  ENFORCE_BOOLEAN(gc_profiling_enabled);
404
488
  ENFORCE_BOOLEAN(no_signals_workaround_enabled);
@@ -408,9 +492,12 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
408
492
  ENFORCE_BOOLEAN(allocation_counting_enabled);
409
493
  ENFORCE_BOOLEAN(gvl_profiling_enabled);
410
494
  ENFORCE_BOOLEAN(skip_idle_samples_for_testing)
495
+ ENFORCE_BOOLEAN(sighandler_sampling_enabled)
496
+ ENFORCE_TYPE(cpu_sampling_interval_ms, T_FIXNUM);
497
+ ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
411
498
 
412
- struct cpu_and_wall_time_worker_state *state;
413
- TypedData_Get_Struct(self_instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
499
+ cpu_and_wall_time_worker_state *state;
500
+ TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
414
501
 
415
502
  state->gc_profiling_enabled = (gc_profiling_enabled == Qtrue);
416
503
  state->no_signals_workaround_enabled = (no_signals_workaround_enabled == Qtrue);
@@ -419,6 +506,9 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
419
506
  state->allocation_counting_enabled = (allocation_counting_enabled == Qtrue);
420
507
  state->gvl_profiling_enabled = (gvl_profiling_enabled == Qtrue);
421
508
  state->skip_idle_samples_for_testing = (skip_idle_samples_for_testing == Qtrue);
509
+ state->sighandler_sampling_enabled = (sighandler_sampling_enabled == Qtrue);
510
+ state->cpu_sampling_interval_ms = NUM2INT(cpu_sampling_interval_ms);
511
+ state->waiting_for_gvl_threshold_ns = NUM2UINT(waiting_for_gvl_threshold_ns);
422
512
 
423
513
  double total_overhead_target_percentage = NUM2DBL(dynamic_sampling_rate_overhead_target_percentage);
424
514
  if (!state->allocation_profiling_enabled) {
@@ -440,7 +530,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
440
530
 
441
531
  // Since our state contains references to Ruby objects, we need to tell the Ruby GC about them
442
532
  static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr) {
443
- struct cpu_and_wall_time_worker_state *state = (struct cpu_and_wall_time_worker_state *) state_ptr;
533
+ cpu_and_wall_time_worker_state *state = (cpu_and_wall_time_worker_state *) state_ptr;
444
534
 
445
535
  rb_gc_mark(state->thread_context_collector_instance);
446
536
  rb_gc_mark(state->idle_sampling_helper_instance);
@@ -452,31 +542,28 @@ static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr) {
452
542
 
453
543
  // Called in a background thread created in CpuAndWallTimeWorker#start
454
544
  static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
455
- struct cpu_and_wall_time_worker_state *state;
456
- TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
545
+ cpu_and_wall_time_worker_state *state;
546
+ TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
457
547
 
458
548
  // If we already got a delayed exception registered even before starting, raise before starting
459
549
  if (state->failure_exception != Qnil) {
460
- disable_tracepoints(state);
550
+ disable_hooks(state);
461
551
  rb_exc_raise(state->failure_exception);
462
552
  }
463
553
 
464
- struct cpu_and_wall_time_worker_state *old_state = active_sampler_instance_state;
554
+ cpu_and_wall_time_worker_state *old_state = active_sampler_instance_state;
465
555
  if (old_state != NULL) {
466
556
  if (is_thread_alive(old_state->owner_thread)) {
467
- rb_raise(
468
- rb_eRuntimeError,
469
- "Could not start CpuAndWallTimeWorker: There's already another instance of CpuAndWallTimeWorker active in a different thread"
470
- );
557
+ raise_error(rb_eRuntimeError, "Could not start CpuAndWallTimeWorker: There's already another instance of CpuAndWallTimeWorker active in a different thread");
471
558
  } else {
472
559
  // The previously active thread seems to have died without cleaning up after itself.
473
- // In this case, we can still go ahead and start the profiler BUT we make sure to disable any existing tracepoint
560
+ // In this case, we can still go ahead and start the profiler BUT we make sure to disable any existing hooks
474
561
  // first as:
475
- // a) If this is a new instance of the CpuAndWallTimeWorker, we don't want the tracepoint from the old instance
562
+ // a) If this is a new instance of the CpuAndWallTimeWorker, we don't want the hooks from the old instance
476
563
  // being kept around
477
564
  // b) If this is the same instance of the CpuAndWallTimeWorker if we call enable on a tracepoint that is already
478
565
  // enabled, it will start firing more than once, see https://bugs.ruby-lang.org/issues/19114 for details.
479
- disable_tracepoints(old_state);
566
+ disable_hooks(old_state);
480
567
  }
481
568
  }
482
569
 
@@ -484,11 +571,20 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
484
571
  // situation we stop immediately and never even start the sampling trigger loop.
485
572
  if (state->stop_thread == rb_thread_current()) return Qnil;
486
573
 
574
+ thread_context_collector_profiler_internal_thread_started();
575
+
487
576
  // Reset the dynamic sampling rate state, if any (reminder: the monotonic clock reference may change after a fork)
488
577
  dynamic_sampling_rate_reset(&state->cpu_dynamic_sampling_rate);
489
578
  long now = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
490
579
  discrete_dynamic_sampler_reset(&state->allocation_sampler, now);
491
580
 
581
+ // Reset per-thread state, if any. This ensures there's no leftover state from a previous profiler run that would
582
+ // affect or be included in samples taken by this profiler about to run.
583
+ //
584
+ // NOTE: This needs to be called before we enable any hooks or anything that could trigger samples (e.g.
585
+ // reset cannot be concurrent with any sampling activity)
586
+ thread_context_collector_reset_all_per_thread_contexts(state->thread_context_collector_instance);
587
+
492
588
  // This write to a global is thread-safe BECAUSE we're still holding on to the global VM lock at this point
493
589
  active_sampler_instance_state = state;
494
590
  active_sampler_instance = instance;
@@ -498,13 +594,21 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
498
594
 
499
595
  block_sigprof_signal_handler_from_running_in_current_thread(); // We want to interrupt the thread with the global VM lock, never this one
500
596
 
597
+ #ifdef __APPLE__
598
+ // Promote the native thread that will run the sampling loop to a realtime scheduling policy so
599
+ // its periodic wake-ups are not subject to the default ~1-2ms timeshare wake latency. The matching
600
+ // demote happens below in this same function, alongside the SIGPROF unblock dance, so both per-thread
601
+ // policy changes are reverted before Ruby reuses this native thread for an unrelated Ruby thread.
602
+ promote_sampler_thread_to_realtime(MILLIS_AS_NS(state->cpu_sampling_interval_ms));
603
+ #endif
604
+
501
605
  // Release GVL, get to the actual work!
502
606
  int exception_state;
503
607
  rb_protect(release_gvl_and_run_sampling_trigger_loop, instance, &exception_state);
504
608
 
505
609
  // The sample trigger loop finished (either cleanly or with an error); let's clean up
506
610
 
507
- disable_tracepoints(state);
611
+ disable_hooks(state);
508
612
 
509
613
  active_sampler_instance_state = NULL;
510
614
  active_sampler_instance = Qnil;
@@ -519,6 +623,14 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
519
623
  // had SIGPROF delivery blocked. :hide_the_pain_harold:
520
624
  unblock_sigprof_signal_handler_from_running_in_current_thread();
521
625
 
626
+ #ifdef __APPLE__
627
+ // Pairs with promote_sampler_thread_to_realtime above. Same reasoning as the SIGPROF unblock:
628
+ // Ruby may reuse this native thread for an unrelated Ruby thread that would otherwise inherit
629
+ // our scheduler policy. Doing it here also covers the exception path, since grab_gvl_and_sample
630
+ // can raise and unwind out of the sampling loop.
631
+ demote_sampler_thread_from_realtime();
632
+ #endif
633
+
522
634
  // Why replace and not use remove the signal handler? We do this because when a process receives a SIGPROF without
523
635
  // having an explicit signal handler set up, the process will instantly terminate with a confusing
524
636
  // "Profiling timer expired" message left behind. (This message doesn't come from us -- it's the default message for
@@ -529,8 +641,9 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
529
641
  // @ivoanjo: I suspect this will never happen, but the cost of getting it wrong is really high (VM terminates) so this
530
642
  // is a just-in-case situation.
531
643
  //
532
- // Note 2: This can raise exceptions as well, so make sure that all cleanups are done by the time we get here.
533
- replace_sigprof_signal_handler_with_empty_handler(handle_sampling_signal);
644
+ // Note 2: If exception_state is set, we have a pending exception that we'll re-raise below.
645
+ // In that case, we don't want replace_sigprof_signal_handler_with_empty_handler to raise another exception.
646
+ replace_sigprof_signal_handler_with_empty_handler(handle_sampling_signal, !exception_state);
534
647
 
535
648
  // Ensure that instance is not garbage collected while the native sampling loop is running; this is probably not needed, but just in case
536
649
  RB_GC_GUARD(instance);
@@ -540,69 +653,138 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
540
653
  return Qnil;
541
654
  }
542
655
 
656
+ static VALUE _native_profiler_internal_thread_done(VALUE self_instance) {
657
+ cpu_and_wall_time_worker_state *state;
658
+ TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
659
+
660
+ during_sample_enter(state);
661
+ return rb_ensure(
662
+ thread_context_collector_profiler_internal_thread_done, state->thread_context_collector_instance,
663
+ during_sample_exit_rescue, (VALUE) state
664
+ );
665
+ }
666
+
543
667
  static VALUE _native_stop(DDTRACE_UNUSED VALUE _self, VALUE self_instance, VALUE worker_thread) {
544
- struct cpu_and_wall_time_worker_state *state;
545
- TypedData_Get_Struct(self_instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
668
+ cpu_and_wall_time_worker_state *state;
669
+ TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
546
670
 
547
671
  state->stop_thread = worker_thread;
548
672
 
549
- return stop(self_instance, /* optional_exception: */ Qnil);
673
+ return stop(self_instance, Qnil, NULL);
550
674
  }
551
675
 
552
- static void stop_state(struct cpu_and_wall_time_worker_state *state, VALUE optional_exception) {
676
+ // When providing an `optional_exception`, `optional_exception_during_operation` should be provided as well
677
+ static void stop_state(cpu_and_wall_time_worker_state *state, VALUE optional_exception, const char *optional_exception_during_operation) {
553
678
  atomic_store(&state->should_run, false);
554
679
  state->failure_exception = optional_exception;
680
+ state->failure_exception_during_operation = optional_exception_during_operation;
555
681
 
556
- // Disable the tracepoints as soon as possible, so the VM doesn't keep on calling them
557
- disable_tracepoints(state);
682
+ // Disable the hooks as soon as possible, so the VM doesn't keep on calling them
683
+ disable_hooks(state);
558
684
  }
559
685
 
560
- static VALUE stop(VALUE self_instance, VALUE optional_exception) {
561
- struct cpu_and_wall_time_worker_state *state;
562
- TypedData_Get_Struct(self_instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
686
+ static VALUE stop(VALUE self_instance, VALUE optional_exception, const char *optional_exception_during_operation) {
687
+ cpu_and_wall_time_worker_state *state;
688
+ TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
563
689
 
564
- stop_state(state, optional_exception);
690
+ stop_state(state, optional_exception, optional_exception_during_operation);
565
691
 
566
692
  return Qtrue;
567
693
  }
568
694
 
695
+ // Our SIGPROF handler is not installed with SA_ONSTACK (see `install_sigprof_signal_handler`).
696
+ // If we detect our signal handler is running on the alt stack, it means we interrupted another signal handler
697
+ // that IS running on the alt stack -- in practice, Ruby's GC compaction read-barrier handler.
698
+ #ifdef __APPLE__
699
+ // On macOS, `ucontext->uc_stack.ss_sp` reports the (live) stack pointer near the top of the alt stack rather than the
700
+ // alt-stack base like Linux does, and `ss_size` is the full alt-stack size. Thus the linux version of the check
701
+ // didn't work for macOS.
702
+ // As an alternative, we query `sigaltstack()`; we save/restore errno since `sigaltstack()` may clobber it.
703
+ static bool is_running_on_alternate_signal_stack(DDTRACE_UNUSED void *ucontext) {
704
+ int old_errno = errno;
705
+ stack_t current_alt_stack;
706
+ bool on_alt_stack = sigaltstack(NULL, &current_alt_stack) == 0 && (current_alt_stack.ss_flags & SS_ONSTACK) != 0;
707
+ errno = old_errno;
708
+ return on_alt_stack;
709
+ }
710
+ #else
711
+ // NOTE: On Linux we use the alt-stack bounds from the `ucontext` rather than `sigaltstack()`, allowing us to avoid one syscall.
712
+ static bool is_running_on_alternate_signal_stack(void *ucontext) {
713
+ if (ucontext == NULL) return false;
714
+
715
+ stack_t alt_stack = ((ucontext_t *) ucontext)->uc_stack;
716
+ if (alt_stack.ss_size == 0) return false; // No alternate signal stack registered on this thread
717
+
718
+ char stack_local;
719
+ uintptr_t current_sp = (uintptr_t) &stack_local;
720
+ uintptr_t alt_stack_start = (uintptr_t) alt_stack.ss_sp;
721
+
722
+ return current_sp >= alt_stack_start && current_sp < alt_stack_start + alt_stack.ss_size;
723
+ }
724
+ #endif
725
+
569
726
  // NOTE: Remember that this will run in the thread and within the scope of user code, including user C code.
570
727
  // We need to be careful not to change any state that may be observed OR to restore it if we do. For instance, if anything
571
728
  // we do here can set `errno`, then we must be careful to restore the old `errno` after the fact.
572
- static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext) {
573
- struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
574
-
575
- // This can potentially happen if the CpuAndWallTimeWorker was stopped while the signal delivery was happening; nothing to do
576
- if (state == NULL) return;
729
+ static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, void *ucontext) {
730
+ // If we're running on the alternate signal stack, we've interrupted another signal handler that's running
731
+ // there -- in practice, Ruby's GC compaction read-barrier handler.
732
+ // During GC compaction, Ruby protects pages containing Ruby objects, so many of the checks we do below are unsafe
733
+ // since they can touch those pages, and thus we just bail out here in this situation to avoid crashes.
734
+ if (is_running_on_alternate_signal_stack(ucontext)) {
735
+ global_stats.signal_handler_skipped_sample_on_altstack++;
736
+ return;
737
+ }
577
738
 
739
+ // We must first check that we landed on the correct thread and can proceed.
740
+ // We must never touch the state before we confirm that we have landed on the thread that is holding the GVL on the main
741
+ // ractor as otherwise we may be concurrent with the profiler shutting down and removing its state.
578
742
  if (
579
743
  !ruby_native_thread_p() || // Not a Ruby thread
580
744
  !is_current_thread_holding_the_gvl() || // Not safe to enqueue a sample from this thread
581
745
  !ddtrace_rb_ractor_main_p() // We're not on the main Ractor; we currently don't support profiling non-main Ractors
582
- ) {
583
- state->stats.signal_handler_wrong_thread++;
584
- return;
585
- }
746
+ ) return;
747
+
748
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
749
+
750
+ // This can potentially happen if the CpuAndWallTimeWorker was stopped while the signal delivery was happening; nothing to do
751
+ if (state == NULL) return;
586
752
 
587
- // We implicitly assume there can be no concurrent nor nested calls to handle_sampling_signal because
588
- // a) we get triggered using SIGPROF, and the docs state a second SIGPROF will not interrupt an existing one
753
+ // We assume there can be no concurrent nor nested calls to handle_sampling_signal because
754
+ // a) we get triggered using SIGPROF, and the docs state a second SIGPROF will not interrupt an existing one (see sigaction docs on sa_mask)
589
755
  // b) we validate we are in the thread that has the global VM lock; if a different thread gets a signal, it will return early
590
756
  // because it will not have the global VM lock
591
-
592
- // Note: rb_postponed_job_register_one ensures that if there's a previous sample_from_postponed_job queued for execution
593
- // then we will not queue a second one. It does this by doing a linear scan on the existing jobs; in the future we
594
- // may want to implement that check ourselves.
757
+ // c) `simulate_sampling_signal_delivery` calls us directly instead of via a real SIGPROF, and blocks SIGPROF delivery on its
758
+ // thread for the duration of that call, so it can't be nested into by a real SIGPROF either
595
759
 
596
760
  state->stats.signal_handler_enqueued_sample++;
597
761
 
598
- // Note: If we ever want to get rid of rb_postponed_job_register_one, remember not to clobber Ruby exceptions, as
599
- // this function does this helpful job for us now -- https://github.com/ruby/ruby/commit/a98e343d39c4d7bf1e2190b076720f32d9f298b3.
762
+ bool sample_from_signal_handler =
763
+ state->sighandler_sampling_enabled &&
764
+ // Don't sample if we're already in the middle of processing a sample
765
+ !state->during_sample;
766
+
767
+ if (sample_from_signal_handler) {
768
+ if (rb_during_gc()) {
769
+ // During GC Ruby might be marking our buffer or compacting (moving) where iseqs live so we skip preparing the
770
+ // sample immediately, and instead ask on_gc_finish to do the job when GC is at its end.
771
+ //
772
+ // When GC profiling is disabled, we just fall back to sampling the stack after GC finishes, e.g. it's the
773
+ // equivalent of disabling `sample_from_signal_handler` for samples that happen during GC.
774
+ thread_context_collector_request_prepare_on_gc_finish();
775
+ } else {
776
+ // Buffer current stack trace. Note that this will not actually record the sample, for that we still need to wait
777
+ // until the postponed job below gets run.
778
+ bool prepared = thread_context_collector_prepare_sample_inside_signal_handler();
779
+
780
+ if (prepared) state->stats.signal_handler_prepared_sample++;
781
+ }
782
+ }
783
+
600
784
  #ifndef NO_POSTPONED_TRIGGER // Ruby 3.3+
601
785
  rb_postponed_job_trigger(sample_from_postponed_job_handle);
602
- state->stats.postponed_job_success++; // Always succeeds
603
786
  #else
604
-
605
- // This is a workaround for https://bugs.ruby-lang.org/issues/19991 (for Ruby < 3.3)
787
+ // Passing in `gc_finalize_deferred_workaround` is a workaround for https://bugs.ruby-lang.org/issues/19991 (for Ruby < 3.3)
606
788
  //
607
789
  // TL;DR the `rb_postponed_job_register_one` API is not atomic (which is why it got replaced by `rb_postponed_job_trigger`)
608
790
  // and in rare cases can cause VM crashes.
@@ -626,28 +808,15 @@ static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED si
626
808
  //
627
809
  // Thus, our workaround is simple: we pass in objspace as our argument, just in case the clobbering happens.
628
810
  // In the happy path, we never use this argument so it makes no difference. In the buggy path, we avoid crashing the VM.
629
- int result = rb_postponed_job_register(0, sample_from_postponed_job, gc_finalize_deferred_workaround /* instead of NULL */);
630
-
631
- // Officially, the result of rb_postponed_job_register_one is documented as being opaque, but in practice it does not
632
- // seem to have changed between Ruby 2.3 and 3.2, and so we track it as a debugging mechanism
633
- switch (result) {
634
- case 0:
635
- state->stats.postponed_job_full++; break;
636
- case 1:
637
- state->stats.postponed_job_success++; break;
638
- case 2:
639
- state->stats.postponed_job_skipped_already_existed++; break;
640
- default:
641
- state->stats.postponed_job_unknown_result++;
642
- }
811
+ rb_postponed_job_register(0, sample_from_postponed_job, gc_finalize_deferred_workaround /* instead of NULL */);
643
812
  #endif
644
813
  }
645
814
 
646
815
  // The actual sampling trigger loop always runs **without** the global vm lock.
647
816
  static void *run_sampling_trigger_loop(void *state_ptr) {
648
- struct cpu_and_wall_time_worker_state *state = (struct cpu_and_wall_time_worker_state *) state_ptr;
817
+ cpu_and_wall_time_worker_state *state = (cpu_and_wall_time_worker_state *) state_ptr;
649
818
 
650
- uint64_t minimum_time_between_signals = MILLIS_AS_NS(10);
819
+ uint64_t minimum_time_between_signals = MILLIS_AS_NS(state->cpu_sampling_interval_ms);
651
820
 
652
821
  while (atomic_load(&state->should_run)) {
653
822
  state->stats.trigger_sample_attempts++;
@@ -669,6 +838,10 @@ static void *run_sampling_trigger_loop(void *state_ptr) {
669
838
  // we're doing this, so we may still not signal the correct thread from time to time, but our signal handler
670
839
  // includes a check to see if it got called in the right thread
671
840
  state->stats.interrupt_thread_attempts++;
841
+
842
+ // Pick up any last-minute attempts to stop before we send the signal
843
+ if (!atomic_load(&state->should_run)) return NULL;
844
+
672
845
  pthread_kill(owner.owner, SIGPROF);
673
846
  } else {
674
847
  if (state->skip_idle_samples_for_testing) {
@@ -696,7 +869,10 @@ static void *run_sampling_trigger_loop(void *state_ptr) {
696
869
  // `dynamic_sampling_rate_get_sleep` may have changed while the above sleep was ongoing.
697
870
  uint64_t extra_sleep =
698
871
  dynamic_sampling_rate_get_sleep(&state->cpu_dynamic_sampling_rate, monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE));
699
- if (state->dynamic_sampling_rate_enabled && extra_sleep > 0) sleep_for(extra_sleep);
872
+ if (state->dynamic_sampling_rate_enabled && extra_sleep > 0) {
873
+ state->stats.trigger_sample_extra_sleep++;
874
+ sleep_for(extra_sleep);
875
+ }
700
876
  }
701
877
 
702
878
  return NULL; // Unused
@@ -704,13 +880,15 @@ static void *run_sampling_trigger_loop(void *state_ptr) {
704
880
 
705
881
  // This is called by the Ruby VM when it wants to shut down the background thread
706
882
  static void interrupt_sampling_trigger_loop(void *state_ptr) {
707
- struct cpu_and_wall_time_worker_state *state = (struct cpu_and_wall_time_worker_state *) state_ptr;
883
+ cpu_and_wall_time_worker_state *state = (cpu_and_wall_time_worker_state *) state_ptr;
708
884
 
709
885
  atomic_store(&state->should_run, false);
710
886
  }
711
887
 
888
+ // Note: If we ever want to get rid of the postponed job execution, remember not to clobber Ruby exceptions, as
889
+ // this function does this helpful job for us now -- https://github.com/ruby/ruby/commit/a98e343d39c4d7bf1e2190b076720f32d9f298b3.
712
890
  static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused) {
713
- struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
891
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
714
892
 
715
893
  // This can potentially happen if the CpuAndWallTimeWorker was stopped while the postponed job was waiting to be executed; nothing to do
716
894
  if (state == NULL) return;
@@ -721,29 +899,40 @@ static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused) {
721
899
  return; // We're not on the main Ractor; we currently don't support profiling non-main Ractors
722
900
  }
723
901
 
724
- state->during_sample = true;
902
+ during_sample_enter(state);
725
903
 
726
904
  // Rescue against any exceptions that happen during sampling
727
- safely_call(rescued_sample_from_postponed_job, state->self_instance, state->self_instance);
905
+ VALUE needs_otel_span_key = safely_call(
906
+ rescued_sample_from_postponed_job,
907
+ state->self_instance,
908
+ state->self_instance,
909
+ handle_sampling_failure_rescued_sample_from_postponed_job
910
+ );
728
911
 
729
- state->during_sample = false;
912
+ during_sample_exit(state);
913
+
914
+ // Extracting the otel span key can lose the GVL, so we move it outside `during_sample`
915
+ // (It can't raise: it rescues its own exceptions)
916
+ if (needs_otel_span_key == Qtrue) {
917
+ thread_context_collector_resolve_otel_span_key_may_lose_gvl(state->thread_context_collector_instance);
918
+ }
730
919
  }
731
920
 
732
921
  static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
733
- struct cpu_and_wall_time_worker_state *state;
734
- TypedData_Get_Struct(self_instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
922
+ cpu_and_wall_time_worker_state *state;
923
+ TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
735
924
 
736
925
  long wall_time_ns_before_sample = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
737
926
 
738
927
  if (state->dynamic_sampling_rate_enabled && !dynamic_sampling_rate_should_sample(&state->cpu_dynamic_sampling_rate, wall_time_ns_before_sample)) {
739
928
  state->stats.cpu_skipped++;
740
- return Qnil;
929
+ return Qfalse;
741
930
  }
742
931
 
743
932
  state->stats.cpu_sampled++;
744
933
 
745
- VALUE profiler_overhead_stack_thread = state->owner_thread; // Used to attribute profiler overhead to a different stack
746
- thread_context_collector_sample(state->thread_context_collector_instance, wall_time_ns_before_sample, profiler_overhead_stack_thread);
934
+ bool needs_otel_span_key =
935
+ thread_context_collector_sample(state->thread_context_collector_instance, wall_time_ns_before_sample);
747
936
 
748
937
  long wall_time_ns_after_sample = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
749
938
  long delta_ns = wall_time_ns_after_sample - wall_time_ns_before_sample;
@@ -757,13 +946,7 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
757
946
 
758
947
  dynamic_sampling_rate_after_sample(&state->cpu_dynamic_sampling_rate, wall_time_ns_after_sample, sampling_time_ns);
759
948
 
760
- // Return a dummy VALUE because we're called from rb_rescue2 which requires it
761
- return Qnil;
762
- }
763
-
764
- static VALUE handle_sampling_failure(VALUE self_instance, VALUE exception) {
765
- stop(self_instance, exception);
766
- return Qnil;
949
+ return needs_otel_span_key ? Qtrue : Qfalse;
767
950
  }
768
951
 
769
952
  // This method exists only to enable testing Datadog::Profiling::Collectors::CpuAndWallTimeWorker behavior using RSpec.
@@ -786,10 +969,10 @@ static VALUE _native_current_sigprof_signal_handler(DDTRACE_UNUSED VALUE self) {
786
969
  }
787
970
 
788
971
  static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
789
- struct cpu_and_wall_time_worker_state *state;
790
- TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
972
+ cpu_and_wall_time_worker_state *state;
973
+ TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
791
974
 
792
- // Final preparations: Setup signal handler and enable tracepoints. We run these here and not in `_native_sampling_loop`
975
+ // Final preparations: Setup signal handler and enable hooks. We run these here and not in `_native_sampling_loop`
793
976
  // because they may raise exceptions.
794
977
  install_sigprof_signal_handler(handle_sampling_signal, "handle_sampling_signal");
795
978
  if (state->gc_profiling_enabled) rb_tracepoint_enable(state->gc_tracepoint);
@@ -804,22 +987,19 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
804
987
 
805
988
  if (state->gvl_profiling_enabled) {
806
989
  #ifndef NO_GVL_INSTRUMENTATION
807
- #ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS
808
- gvl_profiling_state_thread_tracking_workaround();
809
- #endif
810
-
811
990
  state->gvl_profiling_hook = rb_internal_thread_add_event_hook(
812
991
  on_gvl_event,
813
992
  (
814
993
  // For now we're only asking for these events, even though there's more
815
994
  // (e.g. check docs or gvl-tracing gem)
995
+ RUBY_INTERNAL_THREAD_EVENT_SUSPENDED | /* released gvl */
816
996
  RUBY_INTERNAL_THREAD_EVENT_READY /* waiting for gvl */ |
817
997
  RUBY_INTERNAL_THREAD_EVENT_RESUMED /* running/runnable */
818
998
  ),
819
999
  NULL
820
1000
  );
821
1001
  #else
822
- rb_raise(rb_eArgError, "GVL profiling is not supported in this Ruby version");
1002
+ raise_error(rb_eArgError, "GVL profiling is not supported in this Ruby version");
823
1003
  #endif
824
1004
  }
825
1005
 
@@ -831,17 +1011,28 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
831
1011
  // If we stopped sampling due to an exception, re-raise it (now in the worker thread)
832
1012
  if (state->failure_exception != Qnil) rb_exc_raise(state->failure_exception);
833
1013
 
1014
+ thread_context_collector_profiler_internal_thread_done(state->thread_context_collector_instance);
1015
+
834
1016
  return Qnil;
835
1017
  }
836
1018
 
837
1019
  // This method exists only to enable testing Datadog::Profiling::Collectors::CpuAndWallTimeWorker behavior using RSpec.
838
1020
  // It SHOULD NOT be used for other purposes.
839
1021
  static VALUE _native_is_running(DDTRACE_UNUSED VALUE self, VALUE instance) {
840
- struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1022
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
841
1023
 
842
1024
  return (state != NULL && is_thread_alive(state->owner_thread) && state->self_instance == instance) ? Qtrue : Qfalse;
843
1025
  }
844
1026
 
1027
+ static VALUE _native_failure_exception_during_operation(DDTRACE_UNUSED VALUE self, VALUE instance) {
1028
+ cpu_and_wall_time_worker_state *state;
1029
+ TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1030
+
1031
+ if (state->failure_exception_during_operation == NULL) return Qnil;
1032
+
1033
+ return rb_str_new_cstr(state->failure_exception_during_operation);
1034
+ }
1035
+
845
1036
  static void testing_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext) {
846
1037
  /* Does nothing on purpose */
847
1038
  }
@@ -860,6 +1051,28 @@ static VALUE _native_remove_testing_signal_handler(DDTRACE_UNUSED VALUE self) {
860
1051
  return Qtrue;
861
1052
  }
862
1053
 
1054
+ // Reproduces a profiler sample being taken in the middle of GC compaction: during compaction a SIGPROF can be
1055
+ // delivered while nested inside Ruby's read-barrier signal handler, which runs on the alternate signal stack. We
1056
+ // simulate that here by re-installing the production `handle_sampling_signal` with the `SA_ONSTACK` flag, so it runs
1057
+ // on the alternate signal stack just like it would in that scenario.
1058
+ //
1059
+ // The `SA_ONSTACK` will be undone by any next `replace_sigprof_signal_handler_with_empty_handler` /
1060
+ // `install_sigprof_signal_handler_internal` / `remove_sigprof_signal_handle` so this doesn't need a specific, symmetric
1061
+ // `uninstall` to undo this.
1062
+ static VALUE _native_install_sigprof_handler_on_altstack(DDTRACE_UNUSED VALUE self) {
1063
+ struct sigaction signal_handler_config = {
1064
+ .sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK,
1065
+ .sa_sigaction = handle_sampling_signal,
1066
+ };
1067
+ sigemptyset(&signal_handler_config.sa_mask);
1068
+
1069
+ if (sigaction(SIGPROF, &signal_handler_config, NULL) != 0) {
1070
+ rb_sys_fail("Failed to install SA_ONSTACK SIGPROF signal handler for testing");
1071
+ }
1072
+
1073
+ return Qtrue;
1074
+ }
1075
+
863
1076
  // This method exists only to enable testing Datadog::Profiling::Collectors::CpuAndWallTimeWorker behavior using RSpec.
864
1077
  // It SHOULD NOT be used for other purposes.
865
1078
  static VALUE _native_trigger_sample(DDTRACE_UNUSED VALUE self) {
@@ -870,8 +1083,8 @@ static VALUE _native_trigger_sample(DDTRACE_UNUSED VALUE self) {
870
1083
  // This method exists only to enable testing Datadog::Profiling::Collectors::CpuAndWallTimeWorker behavior using RSpec.
871
1084
  // It SHOULD NOT be used for other purposes.
872
1085
  static VALUE _native_gc_tracepoint(DDTRACE_UNUSED VALUE self, VALUE instance) {
873
- struct cpu_and_wall_time_worker_state *state;
874
- TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1086
+ cpu_and_wall_time_worker_state *state;
1087
+ TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
875
1088
 
876
1089
  return state->gc_tracepoint;
877
1090
  }
@@ -897,7 +1110,7 @@ static void on_gc_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
897
1110
  int event = rb_tracearg_event_flag(rb_tracearg_from_tracepoint(tracepoint_data));
898
1111
  if (event != RUBY_INTERNAL_EVENT_GC_ENTER && event != RUBY_INTERNAL_EVENT_GC_EXIT) return; // Unknown event
899
1112
 
900
- struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1113
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
901
1114
 
902
1115
  // This should not happen in a normal situation because the tracepoint is always enabled after the instance is set
903
1116
  // and disabled before it is cleared, but just in case...
@@ -906,7 +1119,10 @@ static void on_gc_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
906
1119
  if (event == RUBY_INTERNAL_EVENT_GC_ENTER) {
907
1120
  thread_context_collector_on_gc_start(state->thread_context_collector_instance);
908
1121
  } else if (event == RUBY_INTERNAL_EVENT_GC_EXIT) {
909
- bool should_flush = thread_context_collector_on_gc_finish(state->thread_context_collector_instance);
1122
+ bool allow_prepare_sample = !state->during_sample;
1123
+ if (allow_prepare_sample) during_sample_enter(state);
1124
+ bool should_flush = thread_context_collector_on_gc_finish(state->thread_context_collector_instance, allow_prepare_sample);
1125
+ if (allow_prepare_sample) during_sample_exit(state);
910
1126
 
911
1127
  // We use rb_postponed_job_register_one to ask Ruby to run thread_context_collector_sample_after_gc when the
912
1128
  // thread collector flags it's time to flush.
@@ -921,7 +1137,7 @@ static void on_gc_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
921
1137
  }
922
1138
 
923
1139
  static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused) {
924
- struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1140
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
925
1141
 
926
1142
  // This can potentially happen if the CpuAndWallTimeWorker was stopped while the postponed job was waiting to be executed; nothing to do
927
1143
  if (state == NULL) return;
@@ -932,16 +1148,35 @@ static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused) {
932
1148
  return; // We're not on the main Ractor; we currently don't support profiling non-main Ractors
933
1149
  }
934
1150
 
935
- state->during_sample = true;
1151
+ during_sample_enter(state);
936
1152
 
937
- safely_call(thread_context_collector_sample_after_gc, state->thread_context_collector_instance, state->self_instance);
1153
+ safely_call(
1154
+ thread_context_collector_sample_after_gc,
1155
+ state->thread_context_collector_instance,
1156
+ state->self_instance,
1157
+ handle_sampling_failure_thread_context_collector_sample_after_gc
1158
+ );
938
1159
 
939
- state->during_sample = false;
1160
+ during_sample_exit(state);
1161
+
1162
+ // This part runs separately from above because it may lose the GVL and we don't want `during_sample` to be set in
1163
+ // such a situation
1164
+ safely_call(
1165
+ thread_context_collector_heap_update_may_lose_gvl,
1166
+ state->thread_context_collector_instance,
1167
+ state->self_instance,
1168
+ handle_sampling_failure_thread_context_collector_heap_update
1169
+ );
940
1170
  }
941
1171
 
942
1172
  // Equivalent to Ruby begin/rescue call, where we call a C function and jump to the exception handler if an
943
1173
  // exception gets raised within
944
- static VALUE safely_call(VALUE (*function_to_call_safely)(VALUE), VALUE function_to_call_safely_arg, VALUE instance) {
1174
+ static VALUE safely_call(
1175
+ VALUE (*function_to_call_safely)(VALUE),
1176
+ VALUE function_to_call_safely_arg,
1177
+ VALUE instance,
1178
+ VALUE (*handle_sampling_failure)(VALUE, VALUE)
1179
+ ) {
945
1180
  VALUE exception_handler_function_arg = instance;
946
1181
  return rb_rescue2(
947
1182
  function_to_call_safely,
@@ -969,18 +1204,21 @@ static VALUE _native_simulate_sample_from_postponed_job(DDTRACE_UNUSED VALUE sel
969
1204
 
970
1205
  // After the Ruby VM forks, this method gets called in the child process to clean up any leftover state from the parent.
971
1206
  //
972
- // Assumption: This method gets called BEFORE restarting profiling. Note that profiling-related tracepoints may still
1207
+ // Assumption: This method gets called BEFORE restarting profiling. Note that profiling-related hooks may still
973
1208
  // be active, so we make sure to disable them before calling into anything else, so that there are no components
974
1209
  // attempting to trigger samples at the same time as the reset is done.
975
1210
  //
976
- // In the future, if we add more other components with tracepoints, we will need to coordinate stopping all such
977
- // tracepoints before doing the other cleaning steps.
1211
+ // In the future, if we add more other components with hooks, we will need to coordinate stopping all such
1212
+ // hooks before doing the other cleaning steps.
1213
+ //
1214
+ // Note that tests call this method directly in the same process without forking,
1215
+ // and in such a case non-current Threads keep running.
978
1216
  static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE instance) {
979
- struct cpu_and_wall_time_worker_state *state;
980
- TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1217
+ cpu_and_wall_time_worker_state *state;
1218
+ TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
981
1219
 
982
- // Disable all tracepoints, so that there are no more attempts to mutate the profile
983
- disable_tracepoints(state);
1220
+ // Disable all hooks, so that there are no more attempts to mutate the profile
1221
+ disable_hooks(state);
984
1222
 
985
1223
  reset_stats_not_thread_safe(state);
986
1224
 
@@ -995,8 +1233,8 @@ static VALUE _native_is_sigprof_blocked_in_current_thread(DDTRACE_UNUSED VALUE s
995
1233
  }
996
1234
 
997
1235
  static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
998
- struct cpu_and_wall_time_worker_state *state;
999
- TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1236
+ cpu_and_wall_time_worker_state *state;
1237
+ TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1000
1238
 
1001
1239
  unsigned long total_cpu_samples_attempted = state->stats.cpu_sampled + state->stats.cpu_skipped;
1002
1240
  VALUE effective_cpu_sample_rate =
@@ -1011,14 +1249,12 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
1011
1249
  VALUE stats_as_hash = rb_hash_new();
1012
1250
  VALUE arguments[] = {
1013
1251
  ID2SYM(rb_intern("trigger_sample_attempts")), /* => */ UINT2NUM(state->stats.trigger_sample_attempts),
1252
+ ID2SYM(rb_intern("trigger_sample_extra_sleep")), /* => */ UINT2NUM(state->stats.trigger_sample_extra_sleep),
1014
1253
  ID2SYM(rb_intern("trigger_simulated_signal_delivery_attempts")), /* => */ UINT2NUM(state->stats.trigger_simulated_signal_delivery_attempts),
1015
1254
  ID2SYM(rb_intern("simulated_signal_delivery")), /* => */ UINT2NUM(state->stats.simulated_signal_delivery),
1016
1255
  ID2SYM(rb_intern("signal_handler_enqueued_sample")), /* => */ UINT2NUM(state->stats.signal_handler_enqueued_sample),
1017
- ID2SYM(rb_intern("signal_handler_wrong_thread")), /* => */ UINT2NUM(state->stats.signal_handler_wrong_thread),
1018
- ID2SYM(rb_intern("postponed_job_skipped_already_existed")), /* => */ UINT2NUM(state->stats.postponed_job_skipped_already_existed),
1019
- ID2SYM(rb_intern("postponed_job_success")), /* => */ UINT2NUM(state->stats.postponed_job_success),
1020
- ID2SYM(rb_intern("postponed_job_full")), /* => */ UINT2NUM(state->stats.postponed_job_full),
1021
- ID2SYM(rb_intern("postponed_job_unknown_result")), /* => */ UINT2NUM(state->stats.postponed_job_unknown_result),
1256
+ ID2SYM(rb_intern("signal_handler_prepared_sample")), /* => */ UINT2NUM(state->stats.signal_handler_prepared_sample),
1257
+ ID2SYM(rb_intern("signal_handler_skipped_sample_on_altstack")), /* => */ UINT2NUM(global_stats.signal_handler_skipped_sample_on_altstack),
1022
1258
  ID2SYM(rb_intern("interrupt_thread_attempts")), /* => */ UINT2NUM(state->stats.interrupt_thread_attempts),
1023
1259
 
1024
1260
  // CPU Stats
@@ -1042,47 +1278,69 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
1042
1278
  ID2SYM(rb_intern("allocations_during_sample")), /* => */ state->allocation_profiling_enabled ? UINT2NUM(state->stats.allocations_during_sample) : Qnil,
1043
1279
 
1044
1280
  // GVL profiling stats
1045
- ID2SYM(rb_intern("after_gvl_running")), /* => */ UINT2NUM(state->stats.after_gvl_running),
1281
+ ID2SYM(rb_intern("after_gvl_running")), /* => */ UINT2NUM(state->stats.after_gvl_running),
1282
+ ID2SYM(rb_intern("gvl_dont_sample")), /* => */ UINT2NUM(state->stats.gvl_dont_sample),
1283
+ ID2SYM(rb_intern("gvl_sampling_time_ns_min")), /* => */ RUBY_NUM_OR_NIL(state->stats.gvl_sampling_time_ns_min, != UINT64_MAX, ULL2NUM),
1284
+ ID2SYM(rb_intern("gvl_sampling_time_ns_max")), /* => */ RUBY_NUM_OR_NIL(state->stats.gvl_sampling_time_ns_max, > 0, ULL2NUM),
1285
+ ID2SYM(rb_intern("gvl_sampling_time_ns_total")), /* => */ RUBY_NUM_OR_NIL(state->stats.gvl_sampling_time_ns_total, > 0, ULL2NUM),
1286
+ ID2SYM(rb_intern("gvl_sampling_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats.gvl_sampling_time_ns_total, state->stats.after_gvl_running),
1287
+ ID2SYM(rb_intern("gvl_waiting_time_ns_total")), /* => */ state->gvl_profiling_enabled ? ULL2NUM(state->stats.vm_metrics.gvl_waiting_time_ns_total) : Qnil,
1046
1288
  };
1047
1289
  for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
1290
+
1291
+ thread_context_collector_stats(state->thread_context_collector_instance, stats_as_hash);
1292
+
1048
1293
  return stats_as_hash;
1049
1294
  }
1050
1295
 
1051
1296
  static VALUE _native_stats_reset_not_thread_safe(DDTRACE_UNUSED VALUE self, VALUE instance) {
1052
- struct cpu_and_wall_time_worker_state *state;
1053
- TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1297
+ cpu_and_wall_time_worker_state *state;
1298
+ TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1054
1299
  reset_stats_not_thread_safe(state);
1300
+ thread_context_collector_stats_reset_not_thread_safe(state->thread_context_collector_instance);
1055
1301
  return Qnil;
1056
1302
  }
1057
1303
 
1058
1304
  void *simulate_sampling_signal_delivery(DDTRACE_UNUSED void *_unused) {
1059
- struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1305
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1060
1306
 
1061
1307
  // This can potentially happen if the CpuAndWallTimeWorker was stopped while the IdleSamplingHelper was trying to execute this action
1062
1308
  if (state == NULL) return NULL;
1063
1309
 
1310
+ // Since this is not a real signal firing, we need to block SIGPROF delivery on this thread to avoid an actual SIGPROF
1311
+ // signal coming in nested and interrupting us on this thread. Thus we respect the invariant of "no nesting" for `handle_sampling_signal`.
1312
+ //
1313
+ // Not needed when `no_signals_workaround_enabled` is set: no SIGPROFs are ever sent in that mode, so there's nothing to mask.
1314
+ if (!state->no_signals_workaround_enabled) block_sigprof_signal_handler_from_running_in_current_thread();
1315
+
1064
1316
  state->stats.simulated_signal_delivery++;
1065
1317
 
1066
- // @ivoanjo: We could instead directly call sample_from_postponed_job, but I chose to go through the signal handler
1067
- // so that the simulated case is as close to the original one as well (including any metrics increases, etc).
1318
+ // `handle_sampling_signal` does a few things extra on top of `sample_from_postponed_job` so that's why we don't shortcut here
1068
1319
  handle_sampling_signal(0, NULL, NULL);
1069
1320
 
1321
+ if (!state->no_signals_workaround_enabled) unblock_sigprof_signal_handler_from_running_in_current_thread();
1322
+
1070
1323
  return NULL; // Unused
1071
1324
  }
1072
1325
 
1073
1326
  static void grab_gvl_and_sample(void) { rb_thread_call_with_gvl(simulate_sampling_signal_delivery, NULL); }
1074
1327
 
1075
- static void reset_stats_not_thread_safe(struct cpu_and_wall_time_worker_state *state) {
1328
+ static void reset_stats_not_thread_safe(cpu_and_wall_time_worker_state *state) {
1076
1329
  // NOTE: This is not really thread safe so ongoing sampling operations that are concurrent with a reset can have their stats:
1077
1330
  // * Lost (writes after stats retrieval but before reset).
1078
1331
  // * Included in the previous stats window (writes before stats retrieval and reset).
1079
1332
  // * Included in the following stats window (writes after stats retrieval and reset).
1080
1333
  // Given the expected infrequency of resetting (~once per 60s profile) and the auxiliary/non-critical nature of these stats
1081
1334
  // this momentary loss of accuracy is deemed acceptable to keep overhead to a minimum.
1335
+
1082
1336
  state->stats = (struct stats) {
1083
- .cpu_sampling_time_ns_min = UINT64_MAX, // Since we always take the min between existing and latest sample
1084
- .allocation_sampling_time_ns_min = UINT64_MAX, // Since we always take the min between existing and latest sample
1337
+ // All these values are initialized to their highest value possible since we always take the min between existing and latest sample
1338
+ .cpu_sampling_time_ns_min = UINT64_MAX,
1339
+ .allocation_sampling_time_ns_min = UINT64_MAX,
1340
+ .gvl_sampling_time_ns_min = UINT64_MAX,
1341
+ // Other fields are reset to 0
1085
1342
  };
1343
+ global_stats = (global_stats_t) {0};
1086
1344
  }
1087
1345
 
1088
1346
  static void sleep_for(uint64_t time_ns) {
@@ -1104,7 +1362,7 @@ static void sleep_for(uint64_t time_ns) {
1104
1362
  }
1105
1363
 
1106
1364
  static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self) {
1107
- struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state;
1365
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state;
1108
1366
 
1109
1367
  bool are_allocations_being_tracked = state != NULL && state->allocation_profiling_enabled && state->allocation_counting_enabled;
1110
1368
 
@@ -1114,7 +1372,7 @@ static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self) {
1114
1372
  #define HANDLE_CLOCK_FAILURE(call) ({ \
1115
1373
  long _result = (call); \
1116
1374
  if (_result == 0) { \
1117
- delayed_error(state, ERR_CLOCK_FAIL); \
1375
+ delayed_error_clock_failure(state); \
1118
1376
  return; \
1119
1377
  } \
1120
1378
  _result; \
@@ -1123,6 +1381,9 @@ static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self) {
1123
1381
  // Implements memory-related profiling events. This function is called by Ruby via the `rb_add_event_hook2`
1124
1382
  // when the RUBY_INTERNAL_EVENT_NEWOBJ event is triggered.
1125
1383
  //
1384
+ // This function is called from the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
1385
+ // Ruby heap nor release the GVL (https://github.com/DataDog/dd-trace-rb/pull/4240).
1386
+ //
1126
1387
  // When allocation sampling is enabled, this function gets called for almost all* objects allocated by the Ruby VM.
1127
1388
  // (*In some weird cases the VM may skip this tracepoint.)
1128
1389
  //
@@ -1137,7 +1398,7 @@ static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self) {
1137
1398
  // call `rb_tracearg_from_tracepoint(anything)` anywhere during this function or its callees to get the data, so that's
1138
1399
  // why it's not being passed as an argument.
1139
1400
  static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *unused2) {
1140
- struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1401
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1141
1402
 
1142
1403
  // This should not happen in a normal situation because the tracepoint is always enabled after the instance is set
1143
1404
  // and disabled before it is cleared, but just in case...
@@ -1159,6 +1420,25 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
1159
1420
  return;
1160
1421
  }
1161
1422
 
1423
+ VALUE current_thread = rb_thread_current();
1424
+
1425
+ // If Ruby is in the middle of raising an exception, we don't want to try to sample. This is because if we accidentally
1426
+ // trigger an exception inside the profiler code, bad things will happen (specifically, Ruby will try to kill off the
1427
+ // thread even though we may try to catch the exception).
1428
+ //
1429
+ // Note that "in the middle of raising an exception" means the exception itself has already been allocated.
1430
+ // What's getting allocated now is probably the backtrace objects (@ivoanjo or at least that's what I've observed)
1431
+ if (is_raised_flag_set(current_thread)) {
1432
+ return;
1433
+ }
1434
+
1435
+ per_thread_context *thread_context = get_per_thread_context(current_thread);
1436
+ if (!thread_context) {
1437
+ // Context is created eagerly via on_thread_begin_event, so this should not normally be NULL.
1438
+ // We keep the guard since we can't allocate here (inside on_newobj_event).
1439
+ return;
1440
+ }
1441
+
1162
1442
  // Hot path: Dynamic sampling rate is usually enabled and the sampling decision is usually false
1163
1443
  if (RB_LIKELY(state->dynamic_sampling_rate_enabled && !discrete_dynamic_sampler_should_sample(&state->allocation_sampler))) {
1164
1444
  state->stats.allocation_skipped++;
@@ -1185,19 +1465,20 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
1185
1465
  &state->allocation_sampler, HANDLE_CLOCK_FAILURE(monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE))
1186
1466
  );
1187
1467
 
1188
- // @ivoanjo: Strictly speaking, this is not needed because Ruby should not call the same tracepoint while a previous
1189
- // invocation is still pending, (e.g. it wouldn't call `on_newobj_event` while it's already running), but I decided
1190
- // to keep this here for consistency -- every call to the thread context (other than the special gc calls which are
1191
- // defined as not being able to allocate) sets this.
1192
- state->during_sample = true;
1468
+ during_sample_enter(state);
1193
1469
 
1194
1470
  // Rescue against any exceptions that happen during sampling
1195
- safely_call(rescued_sample_allocation, Qnil, state->self_instance);
1471
+ safely_call(
1472
+ rescued_sample_allocation,
1473
+ (VALUE) thread_context,
1474
+ state->self_instance,
1475
+ handle_sampling_failure_rescued_sample_allocation
1476
+ );
1196
1477
 
1197
1478
  if (state->dynamic_sampling_rate_enabled) {
1198
1479
  long now = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
1199
1480
  if (now == 0) {
1200
- delayed_error(state, ERR_CLOCK_FAIL);
1481
+ delayed_error_clock_failure(state);
1201
1482
  // NOTE: Not short-circuiting here to make sure cleanup happens
1202
1483
  }
1203
1484
  uint64_t sampling_time_ns = discrete_dynamic_sampler_after_sample(&state->allocation_sampler, now);
@@ -1210,10 +1491,10 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
1210
1491
 
1211
1492
  state->stats.allocation_sampled++;
1212
1493
 
1213
- state->during_sample = false;
1494
+ during_sample_exit(state);
1214
1495
  }
1215
1496
 
1216
- static void disable_tracepoints(struct cpu_and_wall_time_worker_state *state) {
1497
+ static void disable_hooks(cpu_and_wall_time_worker_state *state) {
1217
1498
  if (state->gc_tracepoint != Qnil) {
1218
1499
  rb_tracepoint_disable(state->gc_tracepoint);
1219
1500
  }
@@ -1241,8 +1522,9 @@ static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self) {
1241
1522
  }
1242
1523
  }
1243
1524
 
1244
- static VALUE rescued_sample_allocation(DDTRACE_UNUSED VALUE unused) {
1245
- struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1525
+ static VALUE rescued_sample_allocation(VALUE arg) {
1526
+ per_thread_context *thread_context = (per_thread_context*) arg;
1527
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1246
1528
 
1247
1529
  // This should not happen in a normal situation because on_newobj_event already checked for this, but just in case...
1248
1530
  if (state == NULL) return Qnil;
@@ -1260,27 +1542,40 @@ static VALUE rescued_sample_allocation(DDTRACE_UNUSED VALUE unused) {
1260
1542
  // To control bias from sampling, we clamp the maximum weight attributed to a single allocation sample. This avoids
1261
1543
  // assigning a very large number to a sample, if for instance the dynamic sampling mechanism chose a really big interval.
1262
1544
  unsigned int weight = allocations_since_last_sample > MAX_ALLOC_WEIGHT ? MAX_ALLOC_WEIGHT : (unsigned int) allocations_since_last_sample;
1263
- thread_context_collector_sample_allocation(state->thread_context_collector_instance, weight, new_object);
1545
+ bool needs_commit = thread_context_collector_sample_allocation(state->thread_context_collector_instance, thread_context, weight, new_object);
1264
1546
  // ...but we still represent the skipped samples in the profile, thus the data will account for all allocations.
1265
1547
  if (weight < allocations_since_last_sample) {
1266
1548
  uint32_t skipped_samples = (uint32_t) uint64_min_of(allocations_since_last_sample - weight, UINT32_MAX);
1267
1549
  thread_context_collector_sample_skipped_allocation_samples(state->thread_context_collector_instance, skipped_samples);
1268
1550
  }
1269
1551
 
1552
+ if (needs_commit) {
1553
+ #ifndef NO_POSTPONED_TRIGGER
1554
+ rb_postponed_job_trigger(commit_heap_recordings_from_postponed_job_may_lose_gvl_handle);
1555
+ #else
1556
+ rb_postponed_job_register_one(0, commit_heap_recordings_from_postponed_job_may_lose_gvl, NULL);
1557
+ #endif
1558
+ }
1559
+
1270
1560
  // Return a dummy VALUE because we're called from rb_rescue2 which requires it
1271
1561
  return Qnil;
1272
1562
  }
1273
1563
 
1274
- static void delayed_error(struct cpu_and_wall_time_worker_state *state, const char *error) {
1564
+ static void delayed_error(cpu_and_wall_time_worker_state *state, const char *error) {
1565
+ // If we can't raise an immediate exception at the calling site, use the asynchronous flow through the main worker loop.
1566
+ stop_state(state, rb_exc_new_cstr(rb_eRuntimeError, error), "delayed_error");
1567
+ }
1568
+
1569
+ static void delayed_error_clock_failure(cpu_and_wall_time_worker_state *state) {
1275
1570
  // If we can't raise an immediate exception at the calling site, use the asynchronous flow through the main worker loop.
1276
- stop_state(state, rb_exc_new_cstr(rb_eRuntimeError, error));
1571
+ stop_state(state, rb_exc_new_cstr(clock_failure_exception_class, "failed to get clock time"), "delayed_error_clock_failure");
1277
1572
  }
1278
1573
 
1279
1574
  static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg) {
1280
1575
  ENFORCE_TYPE(error_msg, T_STRING);
1281
1576
 
1282
- struct cpu_and_wall_time_worker_state *state;
1283
- TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1577
+ cpu_and_wall_time_worker_state *state;
1578
+ TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1284
1579
 
1285
1580
  delayed_error(state, rb_string_value_cstr(&error_msg));
1286
1581
 
@@ -1304,64 +1599,210 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
1304
1599
  #ifndef NO_GVL_INSTRUMENTATION
1305
1600
  static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused) {
1306
1601
  // Be very careful about touching the `state` here or doing anything at all:
1307
- // This function gets called without the GVL, and potentially from background Ractors!
1602
+ // This function gets called without the GVL, and potentially from non-main Ractors!
1308
1603
  //
1309
- // In fact, the `target_thread` that this event is about may not even be the current thread. (So be careful with thread locals that
1310
- // are not directly tied to the `target_thread` object and the like)
1311
- gvl_profiling_thread target_thread = thread_from_event(event_data);
1604
+ // Note, even though these events can get called without the GVL, they synchronize-with enabling/disabling of
1605
+ // the hook that calls us using a read-write lock. Thus, disabling the hook cannot be concurrent with calling this function,
1606
+ // and once the disable finishes there can't be "late" calls into this function.
1607
+
1608
+ // The thread that this event is about may not be the current thread
1609
+ // (as documented on rb_internal_thread_add_event_hook(), and this is notably the case for READY on Ruby 4.0),
1610
+ // so be careful with native thread locals that are not directly tied to the thread object and the like.
1611
+
1612
+ #ifdef HAVE_RUBY_THREAD_STORAGE_API
1613
+ VALUE target_thread = event_data->thread;
1614
+ #else
1615
+ // On Ruby 3.2 the event does not carry the thread, but all events fire on the thread itself.
1616
+ // However, during early thread startup rb_thread_current() can crash because the execution context (Fiber) isn't
1617
+ // stored in TLS yet; ruby_native_thread_p() guards against this.
1618
+ if (!ruby_native_thread_p()) return;
1619
+ VALUE target_thread = rb_thread_current();
1620
+ #endif
1621
+
1622
+ per_thread_context* thread_context = get_per_thread_context(target_thread);
1623
+ if (!thread_context) return;
1624
+ // If non-NULL the thread is profiled and from the main Ractor
1312
1625
 
1313
- if (event_id == RUBY_INTERNAL_THREAD_EVENT_READY) { /* waiting for gvl */
1314
- thread_context_collector_on_gvl_waiting(target_thread);
1626
+ if (event_id == RUBY_INTERNAL_THREAD_EVENT_SUSPENDED) { /* released gvl */
1627
+ thread_context_collector_on_gvl_released(thread_context);
1628
+ } else if (event_id == RUBY_INTERNAL_THREAD_EVENT_READY) { /* waiting for gvl */
1629
+ thread_context_collector_on_gvl_waiting(thread_context);
1315
1630
  } else if (event_id == RUBY_INTERNAL_THREAD_EVENT_RESUMED) { /* running/runnable */
1316
- // Interesting note: A RUBY_INTERNAL_THREAD_EVENT_RESUMED is guaranteed to be called with the GVL being acquired.
1317
- // (And... I think target_thread will be == rb_thread_current()?)
1318
- // But we're not sure if we're on the main Ractor yet. The thread context collector actually can actually help here:
1319
- // it tags threads it's tracking, so if a thread is tagged then by definition we know that thread belongs to the main
1320
- // Ractor. Thus, if we really really wanted to access the state, we could do it after making sure we're on the correct Ractor.
1321
-
1322
- #ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS
1323
- target_thread = gvl_profiling_state_maybe_initialize();
1324
- #endif
1631
+ // We must only use async-signal-safe functions here and not call arbitrary Ruby APIs and not allocate!
1632
+ // One might assume RUBY_INTERNAL_THREAD_EVENT_RESUMED means having the GVL and running that thread.
1633
+ // However, the reality is more complicated (https://bugs.ruby-lang.org/issues/22098),
1634
+ // it only "sort of" has the GVL but not fully, and it's called while holding the scheduler lock,
1635
+ // so we do as little work as possible here, and perform the sample in a postponed_job.
1636
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1637
+ if (state == NULL) return; // This should not happen, but just in case...
1638
+
1639
+ // on_gvl_running prepares a stack sample so we need to gate it with `during_sample_enter` to avoid a
1640
+ // SIGPROF signal coming in during that function and potentially causing a corrupted final state.
1641
+ //
1642
+ // TODO: Currently, the sample prepared in on_gvl_running can still be clobbered if the signal handler runs
1643
+ // after `during_sample_exit` but before `after_gvl_running_from_postponed_job` gets to run. We'll need to fix
1644
+ // that next.
1645
+ during_sample_enter(state);
1646
+
1647
+ on_gvl_running_result result =
1648
+ thread_context_collector_on_gvl_running(target_thread, thread_context, state->waiting_for_gvl_threshold_ns);
1325
1649
 
1326
- bool should_sample = thread_context_collector_on_gvl_running(target_thread);
1650
+ during_sample_exit(state);
1327
1651
 
1328
- if (should_sample) {
1329
- // should_sample is only true if a thread belongs to the main Ractor, so we're good to go
1652
+ if (result.waiting_for_gvl_duration_ns > 0) {
1653
+ state->stats.vm_metrics.gvl_waiting_time_ns_total += (uint64_t) result.waiting_for_gvl_duration_ns;
1654
+ }
1655
+
1656
+ if (result.action == ON_GVL_RUNNING_SAMPLE) {
1330
1657
  #ifndef NO_POSTPONED_TRIGGER
1331
1658
  rb_postponed_job_trigger(after_gvl_running_from_postponed_job_handle);
1332
1659
  #else
1333
1660
  rb_postponed_job_register_one(0, after_gvl_running_from_postponed_job, NULL);
1334
1661
  #endif
1662
+ } else if (result.action == ON_GVL_RUNNING_DONT_SAMPLE) {
1663
+ state->stats.gvl_dont_sample++;
1335
1664
  }
1336
1665
  } else {
1337
- // This is a very delicate time and it's hard for us to raise an exception so let's at least complain to stderr
1338
- fprintf(stderr, "[ddtrace] Unexpected value in on_gvl_event (%d)\n", event_id);
1666
+ rb_bug("[ddtrace] Unexpected value in on_gvl_event (%d)\n", event_id);
1339
1667
  }
1340
1668
  }
1341
1669
 
1342
1670
  static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused) {
1343
- struct cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1671
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1344
1672
 
1345
1673
  // This can potentially happen if the CpuAndWallTimeWorker was stopped while the postponed job was waiting to be executed; nothing to do
1346
1674
  if (state == NULL) return;
1347
1675
 
1348
- state->during_sample = true;
1676
+ during_sample_enter(state);
1677
+
1678
+ // Rescue against any exceptions that happen during sampling
1679
+ safely_call(
1680
+ rescued_after_gvl_running_from_postponed_job,
1681
+ state->self_instance,
1682
+ state->self_instance,
1683
+ handle_sampling_failure_rescued_after_gvl_running_from_postponed_job
1684
+ );
1685
+
1686
+ during_sample_exit(state);
1687
+ }
1688
+
1689
+ static VALUE rescued_after_gvl_running_from_postponed_job(VALUE self_instance) {
1690
+ cpu_and_wall_time_worker_state *state;
1691
+ TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1692
+
1693
+ long wall_time_ns_before_sample = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
1694
+ thread_context_collector_sample_after_gvl_running(state->thread_context_collector_instance, rb_thread_current(), wall_time_ns_before_sample);
1695
+ long wall_time_ns_after_sample = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
1349
1696
 
1350
- safely_call(thread_context_collector_sample_after_gvl_running, state->thread_context_collector_instance, state->self_instance);
1697
+ long delta_ns = wall_time_ns_after_sample - wall_time_ns_before_sample;
1698
+
1699
+ // Guard against wall-time going backwards, see https://github.com/DataDog/dd-trace-rb/pull/2336 for discussion.
1700
+ uint64_t sampling_time_ns = delta_ns < 0 ? 0 : delta_ns;
1701
+
1702
+ state->stats.gvl_sampling_time_ns_min = uint64_min_of(sampling_time_ns, state->stats.gvl_sampling_time_ns_min);
1703
+ state->stats.gvl_sampling_time_ns_max = uint64_max_of(sampling_time_ns, state->stats.gvl_sampling_time_ns_max);
1704
+ state->stats.gvl_sampling_time_ns_total += sampling_time_ns;
1351
1705
 
1352
1706
  state->stats.after_gvl_running++;
1353
1707
 
1354
- state->during_sample = false;
1708
+ return Qnil;
1355
1709
  }
1356
1710
 
1357
1711
  static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE instance) {
1358
- struct cpu_and_wall_time_worker_state *state;
1359
- TypedData_Get_Struct(instance, struct cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1712
+ cpu_and_wall_time_worker_state *state;
1713
+ TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1360
1714
 
1361
1715
  return state->gvl_profiling_hook != NULL ? Qtrue : Qfalse;
1362
1716
  }
1717
+
1718
+ static VALUE handle_sampling_failure_rescued_after_gvl_running_from_postponed_job(VALUE self_instance, VALUE exception) {
1719
+ stop(self_instance, exception, "rescued_after_gvl_running_from_postponed_job");
1720
+ return Qnil;
1721
+ }
1363
1722
  #else
1364
1723
  static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, DDTRACE_UNUSED VALUE instance) {
1365
1724
  return Qfalse;
1366
1725
  }
1367
1726
  #endif
1727
+
1728
+ static VALUE handle_sampling_failure_rescued_sample_from_postponed_job(VALUE self_instance, VALUE exception) {
1729
+ stop(self_instance, exception, "rescued_sample_from_postponed_job");
1730
+ return Qnil;
1731
+ }
1732
+
1733
+ static VALUE handle_sampling_failure_thread_context_collector_sample_after_gc(VALUE self_instance, VALUE exception) {
1734
+ stop(self_instance, exception, "thread_context_collector_sample_after_gc");
1735
+ return Qnil;
1736
+ }
1737
+
1738
+ static VALUE handle_sampling_failure_thread_context_collector_heap_update(VALUE self_instance, VALUE exception) {
1739
+ stop(self_instance, exception, "thread_context_collector_heap_update_may_lose_gvl");
1740
+ return Qnil;
1741
+ }
1742
+
1743
+ static VALUE handle_sampling_failure_rescued_sample_allocation(VALUE self_instance, VALUE exception) {
1744
+ stop(self_instance, exception, "rescued_sample_allocation");
1745
+ return Qnil;
1746
+ }
1747
+
1748
+ static VALUE handle_sampling_failure_rescued_commit_heap_recordings(VALUE self_instance, VALUE exception) {
1749
+ stop(self_instance, exception, "rescued_commit_heap_recordings_may_lose_gvl");
1750
+ return Qnil;
1751
+ }
1752
+
1753
+ static VALUE rescued_commit_heap_recordings_may_lose_gvl(VALUE self_instance) {
1754
+ cpu_and_wall_time_worker_state *state;
1755
+ TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1756
+
1757
+ thread_context_collector_commit_heap_recordings_may_lose_gvl(state->thread_context_collector_instance);
1758
+
1759
+ // Return a dummy VALUE because we're called from rb_rescue2 which requires it
1760
+ return Qnil;
1761
+ }
1762
+
1763
+ // This postponed job callback is used to commit heap allocation recordings.
1764
+ // During on_newobj_event, we can't take the weak reference the heap recorder needs to track the object, so we defer
1765
+ // that until after the event completes.
1766
+ static void commit_heap_recordings_from_postponed_job_may_lose_gvl(DDTRACE_UNUSED void *_unused) {
1767
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state;
1768
+
1769
+ if (state == NULL || !ddtrace_rb_ractor_main_p()) return;
1770
+
1771
+ if (state->during_sample) {
1772
+ delayed_error(state, "commit_heap_recordings_from_postponed_job_may_lose_gvl called during_sample");
1773
+ return;
1774
+ }
1775
+
1776
+ // NOTE: We're not updating the allocation_sampler here.
1777
+ // This means work done in this function isn't accounted for as profiler overhead.
1778
+ // This is acceptable as the amount of work done here is expected to be small.
1779
+ safely_call(
1780
+ rescued_commit_heap_recordings_may_lose_gvl,
1781
+ state->self_instance,
1782
+ state->self_instance,
1783
+ handle_sampling_failure_rescued_commit_heap_recordings
1784
+ );
1785
+ }
1786
+
1787
+ static inline void during_sample_enter(cpu_and_wall_time_worker_state* state) {
1788
+ // Tell the compiler it's not allowed to reorder the `during_sample` flag with anything that happens after.
1789
+ //
1790
+ // In a few cases, we may be checking this flag from a signal handler, so we need to make sure the compiler didn't
1791
+ // get clever and reordered things in such a way that makes us miss the flag update.
1792
+ //
1793
+ // See https://github.com/ruby/ruby/pull/11036 for a similar change made to the Ruby VM with more context.
1794
+ state->during_sample = true;
1795
+ atomic_signal_fence(memory_order_seq_cst);
1796
+ }
1797
+
1798
+ static inline void during_sample_exit(cpu_and_wall_time_worker_state* state) {
1799
+ // See `during_sample_enter` for more context; in this case we set the fence before to make sure anything that
1800
+ // happens before the fence is not reordered with the flag update.
1801
+ atomic_signal_fence(memory_order_seq_cst);
1802
+ state->during_sample = false;
1803
+ }
1804
+
1805
+ static VALUE during_sample_exit_rescue(VALUE state_ptr) {
1806
+ during_sample_exit((cpu_and_wall_time_worker_state *) state_ptr);
1807
+ return Qnil;
1808
+ }