datadog 2.17.0 → 2.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (765) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +686 -1
  3. data/README.md +0 -1
  4. data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
  5. data/ext/datadog_profiling_native_extension/clock_id.h +9 -1
  6. data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +66 -0
  7. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +3 -2
  8. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +344 -126
  9. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -4
  10. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
  11. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +18 -4
  12. data/ext/datadog_profiling_native_extension/collectors_stack.c +314 -91
  13. data/ext/datadog_profiling_native_extension/collectors_stack.h +26 -3
  14. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +680 -352
  15. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +18 -5
  16. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
  17. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +65 -1
  18. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +40 -1
  19. data/ext/datadog_profiling_native_extension/encoded_profile.c +2 -1
  20. data/ext/datadog_profiling_native_extension/extconf.rb +48 -34
  21. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +4 -43
  22. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -47
  23. data/ext/datadog_profiling_native_extension/heap_recorder.c +455 -430
  24. data/ext/datadog_profiling_native_extension/heap_recorder.h +16 -7
  25. data/ext/datadog_profiling_native_extension/http_transport.c +66 -68
  26. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +18 -15
  27. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -10
  28. data/ext/datadog_profiling_native_extension/macos_sampler_thread.h +55 -0
  29. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -13
  30. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +73 -69
  31. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +10 -4
  32. data/ext/datadog_profiling_native_extension/profiling.c +65 -19
  33. data/ext/datadog_profiling_native_extension/ruby_helpers.c +64 -59
  34. data/ext/datadog_profiling_native_extension/ruby_helpers.h +20 -16
  35. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +32 -10
  36. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +2 -1
  37. data/ext/datadog_profiling_native_extension/stack_recorder.c +253 -131
  38. data/ext/datadog_profiling_native_extension/stack_recorder.h +3 -1
  39. data/ext/datadog_profiling_native_extension/time_helpers.h +1 -0
  40. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +2 -1
  41. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +9 -4
  42. data/ext/libdatadog_api/crashtracker.c +16 -15
  43. data/ext/libdatadog_api/crashtracker_report_exception.c +126 -0
  44. data/ext/libdatadog_api/datadog_ruby_common.c +65 -1
  45. data/ext/libdatadog_api/datadog_ruby_common.h +40 -1
  46. data/ext/libdatadog_api/ddsketch.c +102 -0
  47. data/ext/libdatadog_api/di.c +203 -0
  48. data/ext/libdatadog_api/extconf.rb +21 -25
  49. data/ext/libdatadog_api/feature_flags.c +554 -0
  50. data/ext/libdatadog_api/feature_flags.h +5 -0
  51. data/ext/libdatadog_api/helpers.h +27 -0
  52. data/ext/libdatadog_api/init.c +13 -1
  53. data/ext/libdatadog_api/library_config.c +88 -37
  54. data/ext/libdatadog_api/library_config.h +6 -0
  55. data/ext/libdatadog_api/process_discovery.c +25 -24
  56. data/ext/libdatadog_extconf_helpers.rb +107 -14
  57. data/lib/datadog/ai_guard/api_client.rb +84 -0
  58. data/lib/datadog/ai_guard/autoload.rb +10 -0
  59. data/lib/datadog/ai_guard/component.rb +44 -0
  60. data/lib/datadog/ai_guard/configuration/ext.rb +17 -0
  61. data/lib/datadog/ai_guard/configuration.rb +115 -0
  62. data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -0
  63. data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
  64. data/lib/datadog/ai_guard/contrib/rack/integration.rb +42 -0
  65. data/lib/datadog/ai_guard/contrib/rack/patcher.rb +26 -0
  66. data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +97 -0
  67. data/lib/datadog/ai_guard/contrib/rails/integration.rb +41 -0
  68. data/lib/datadog/ai_guard/contrib/rails/patcher.rb +97 -0
  69. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +80 -0
  70. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
  71. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +30 -0
  72. data/lib/datadog/ai_guard/evaluation/content_builder.rb +31 -0
  73. data/lib/datadog/ai_guard/evaluation/content_part.rb +36 -0
  74. data/lib/datadog/ai_guard/evaluation/message.rb +25 -0
  75. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +36 -0
  76. data/lib/datadog/ai_guard/evaluation/request.rb +86 -0
  77. data/lib/datadog/ai_guard/evaluation/result.rb +45 -0
  78. data/lib/datadog/ai_guard/evaluation/tool_call.rb +18 -0
  79. data/lib/datadog/ai_guard/evaluation.rb +108 -0
  80. data/lib/datadog/ai_guard/ext.rb +29 -0
  81. data/lib/datadog/ai_guard.rb +181 -0
  82. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
  83. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +66 -0
  84. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +36 -0
  85. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
  86. data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
  87. data/lib/datadog/appsec/api_security/route_extractor.rb +104 -0
  88. data/lib/datadog/appsec/api_security/sampler.rb +62 -0
  89. data/lib/datadog/appsec/api_security.rb +14 -0
  90. data/lib/datadog/appsec/assets/blocked.html +10 -1
  91. data/lib/datadog/appsec/assets/blocked.json +1 -1
  92. data/lib/datadog/appsec/assets/blocked.text +3 -1
  93. data/lib/datadog/appsec/assets/waf_rules/README.md +30 -36
  94. data/lib/datadog/appsec/assets/waf_rules/recommended.json +601 -74
  95. data/lib/datadog/appsec/assets/waf_rules/strict.json +48 -75
  96. data/lib/datadog/appsec/assets.rb +1 -1
  97. data/lib/datadog/appsec/autoload.rb +2 -2
  98. data/lib/datadog/appsec/component.rb +37 -64
  99. data/lib/datadog/appsec/compressed_json.rb +3 -3
  100. data/lib/datadog/appsec/configuration.rb +427 -1
  101. data/lib/datadog/appsec/context.rb +77 -16
  102. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
  103. data/lib/datadog/appsec/contrib/active_record/integration.rb +1 -1
  104. data/lib/datadog/appsec/contrib/active_record/patcher.rb +4 -1
  105. data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +75 -0
  106. data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +39 -0
  107. data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +30 -0
  108. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +144 -0
  109. data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
  110. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +4 -2
  111. data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +2 -1
  112. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +1 -1
  113. data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
  114. data/lib/datadog/appsec/contrib/excon/patcher.rb +3 -1
  115. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +152 -13
  116. data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
  117. data/lib/datadog/appsec/contrib/faraday/patcher.rb +1 -1
  118. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +138 -13
  119. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +74 -24
  120. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +4 -2
  121. data/lib/datadog/appsec/contrib/graphql/integration.rb +2 -1
  122. data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
  123. data/lib/datadog/appsec/contrib/rack/ext.rb +1 -1
  124. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +47 -13
  125. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +40 -19
  126. data/lib/datadog/appsec/contrib/rack/input_peeker.rb +79 -0
  127. data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
  128. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +80 -62
  129. data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
  130. data/lib/datadog/appsec/contrib/rails/gateway/request.rb +35 -1
  131. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +47 -5
  132. data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
  133. data/lib/datadog/appsec/contrib/rails/patcher.rb +52 -26
  134. data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +29 -0
  135. data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +33 -0
  136. data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
  137. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +3 -1
  138. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +167 -12
  139. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +63 -10
  140. data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
  141. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +11 -21
  142. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +31 -0
  143. data/lib/datadog/appsec/counter_sampler.rb +25 -0
  144. data/lib/datadog/appsec/default_header_tags.rb +52 -0
  145. data/lib/datadog/appsec/event.rb +14 -47
  146. data/lib/datadog/appsec/ext.rb +3 -0
  147. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +17 -1
  148. data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +2 -3
  149. data/lib/datadog/appsec/instrumentation/gateway.rb +2 -15
  150. data/lib/datadog/appsec/metrics/collector.rb +47 -5
  151. data/lib/datadog/appsec/metrics/exporter.rb +16 -3
  152. data/lib/datadog/appsec/metrics/telemetry.rb +21 -4
  153. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +47 -0
  154. data/lib/datadog/appsec/metrics.rb +5 -4
  155. data/lib/datadog/appsec/monitor/gateway/watcher.rb +8 -6
  156. data/lib/datadog/appsec/processor/rule_loader.rb +5 -6
  157. data/lib/datadog/appsec/remote.rb +51 -76
  158. data/lib/datadog/appsec/response.rb +18 -4
  159. data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
  160. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
  161. data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
  162. data/lib/datadog/appsec/route_normalizer.rb +80 -0
  163. data/lib/datadog/appsec/security_engine/engine.rb +197 -0
  164. data/lib/datadog/appsec/security_engine/result.rb +45 -9
  165. data/lib/datadog/appsec/security_engine/runner.rb +44 -21
  166. data/lib/datadog/appsec/security_event.rb +5 -7
  167. data/lib/datadog/appsec/thread_safe_ref.rb +61 -0
  168. data/lib/datadog/appsec/trace_keeper.rb +36 -0
  169. data/lib/datadog/appsec/utils/hash_coercion.rb +23 -0
  170. data/lib/datadog/appsec/utils/http/body.rb +38 -0
  171. data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
  172. data/lib/datadog/appsec/utils/http/media_range.rb +2 -1
  173. data/lib/datadog/appsec/utils/http/media_type.rb +32 -26
  174. data/lib/datadog/appsec/utils/http/url_encoded.rb +52 -0
  175. data/lib/datadog/appsec.rb +10 -17
  176. data/lib/datadog/auto_instrument_base.rb +2 -1
  177. data/lib/datadog/core/configuration/agent_settings.rb +52 -0
  178. data/lib/datadog/core/configuration/agent_settings_resolver.rb +5 -47
  179. data/lib/datadog/core/configuration/base.rb +17 -5
  180. data/lib/datadog/core/configuration/components.rb +192 -20
  181. data/lib/datadog/core/configuration/components_state.rb +6 -1
  182. data/lib/datadog/core/configuration/config_helper.rb +109 -0
  183. data/lib/datadog/core/configuration/deprecations.rb +36 -0
  184. data/lib/datadog/core/configuration/ext.rb +0 -1
  185. data/lib/datadog/core/configuration/option.rb +95 -65
  186. data/lib/datadog/core/configuration/option_definition.rb +42 -23
  187. data/lib/datadog/core/configuration/options.rb +57 -18
  188. data/lib/datadog/core/configuration/settings.rb +160 -25
  189. data/lib/datadog/core/configuration/stable_config.rb +11 -2
  190. data/lib/datadog/core/configuration/supported_configurations.rb +413 -0
  191. data/lib/datadog/core/configuration.rb +4 -4
  192. data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
  193. data/lib/datadog/core/contrib/rails/utils.rb +7 -3
  194. data/lib/datadog/core/crashtracking/component.rb +73 -21
  195. data/lib/datadog/core/crashtracking/tag_builder.rb +9 -21
  196. data/lib/datadog/core/ddsketch.rb +19 -0
  197. data/lib/datadog/core/deprecations.rb +2 -2
  198. data/lib/datadog/core/diagnostics/environment_logger.rb +3 -1
  199. data/lib/datadog/core/environment/agent_info.rb +65 -1
  200. data/lib/datadog/core/environment/cgroup.rb +52 -25
  201. data/lib/datadog/core/environment/container.rb +140 -46
  202. data/lib/datadog/core/environment/ext.rb +13 -2
  203. data/lib/datadog/core/environment/git.rb +2 -2
  204. data/lib/datadog/core/environment/identity.rb +40 -4
  205. data/lib/datadog/core/environment/process.rb +122 -0
  206. data/lib/datadog/core/environment/socket.rb +13 -0
  207. data/lib/datadog/core/environment/variable_helpers.rb +3 -3
  208. data/lib/datadog/core/environment/yjit.rb +2 -1
  209. data/lib/datadog/core/error.rb +6 -6
  210. data/lib/datadog/core/evp.rb +11 -0
  211. data/lib/datadog/core/feature_flags.rb +61 -0
  212. data/lib/datadog/core/knuth_sampler.rb +57 -0
  213. data/lib/datadog/core/logger.rb +1 -1
  214. data/lib/datadog/core/metrics/client.rb +7 -7
  215. data/lib/datadog/core/metrics/logging.rb +1 -1
  216. data/lib/datadog/core/pin.rb +8 -8
  217. data/lib/datadog/core/process_discovery/tracer_memfd.rb +13 -0
  218. data/lib/datadog/core/process_discovery.rb +51 -21
  219. data/lib/datadog/core/rate_limiter.rb +11 -1
  220. data/lib/datadog/core/remote/client/capabilities.rb +49 -6
  221. data/lib/datadog/core/remote/client.rb +15 -7
  222. data/lib/datadog/core/remote/component.rb +64 -36
  223. data/lib/datadog/core/remote/configuration/content.rb +15 -2
  224. data/lib/datadog/core/remote/configuration/digest.rb +14 -7
  225. data/lib/datadog/core/remote/configuration/repository.rb +13 -1
  226. data/lib/datadog/core/remote/configuration/target.rb +13 -6
  227. data/lib/datadog/core/remote/transport/config.rb +9 -36
  228. data/lib/datadog/core/remote/transport/http/config.rb +15 -55
  229. data/lib/datadog/core/remote/transport/http/negotiation.rb +14 -44
  230. data/lib/datadog/core/remote/transport/http.rb +15 -24
  231. data/lib/datadog/core/remote/transport/negotiation.rb +8 -33
  232. data/lib/datadog/core/remote/worker.rb +25 -37
  233. data/lib/datadog/core/runtime/ext.rb +0 -1
  234. data/lib/datadog/core/runtime/metrics.rb +11 -2
  235. data/lib/datadog/core/semaphore.rb +1 -4
  236. data/lib/datadog/core/tag_builder.rb +52 -0
  237. data/lib/datadog/core/tag_normalizer.rb +84 -0
  238. data/lib/datadog/core/telemetry/component.rb +65 -17
  239. data/lib/datadog/core/telemetry/emitter.rb +6 -6
  240. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -3
  241. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
  242. data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
  243. data/lib/datadog/core/telemetry/event/app_started.rb +203 -89
  244. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  245. data/lib/datadog/core/telemetry/event.rb +2 -7
  246. data/lib/datadog/core/telemetry/ext.rb +1 -0
  247. data/lib/datadog/core/telemetry/logger.rb +5 -2
  248. data/lib/datadog/core/telemetry/logging.rb +22 -4
  249. data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
  250. data/lib/datadog/core/telemetry/request.rb +17 -5
  251. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +8 -34
  252. data/lib/datadog/core/telemetry/transport/http.rb +21 -16
  253. data/lib/datadog/core/telemetry/transport/telemetry.rb +8 -17
  254. data/lib/datadog/core/telemetry/worker.rb +108 -32
  255. data/lib/datadog/core/transport/ext.rb +3 -0
  256. data/lib/datadog/core/transport/http/adapters/net.rb +17 -2
  257. data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
  258. data/lib/datadog/core/transport/http/api/instance.rb +4 -21
  259. data/lib/datadog/core/transport/http/builder.rb +11 -7
  260. data/lib/datadog/core/transport/http/client.rb +80 -0
  261. data/lib/datadog/core/transport/http/env.rb +8 -0
  262. data/lib/datadog/core/transport/http/response.rb +4 -0
  263. data/lib/datadog/core/transport/http.rb +24 -19
  264. data/lib/datadog/core/transport/parcel.rb +61 -9
  265. data/lib/datadog/core/transport/response.rb +16 -2
  266. data/lib/datadog/core/transport/transport.rb +90 -0
  267. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
  268. data/lib/datadog/core/utils/{base64.rb → base64_codec.rb} +4 -3
  269. data/lib/datadog/core/utils/enumerable_compat.rb +29 -0
  270. data/lib/datadog/core/utils/fnv.rb +26 -0
  271. data/lib/datadog/core/utils/forking.rb +3 -1
  272. data/lib/datadog/core/utils/hash.rb +0 -23
  273. data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -14
  274. data/lib/datadog/core/utils/network.rb +22 -1
  275. data/lib/datadog/core/utils/only_once.rb +1 -1
  276. data/lib/datadog/core/utils/only_once_successful.rb +8 -2
  277. data/lib/datadog/core/utils/safe_dup.rb +2 -2
  278. data/lib/datadog/core/utils/sequence.rb +2 -0
  279. data/lib/datadog/core/utils/spawn_monkey_patch.rb +68 -0
  280. data/lib/datadog/core/utils/time.rb +1 -1
  281. data/lib/datadog/core/utils.rb +10 -1
  282. data/lib/datadog/core/workers/async.rb +11 -2
  283. data/lib/datadog/core/workers/interval_loop.rb +51 -3
  284. data/lib/datadog/core/workers/polling.rb +2 -0
  285. data/lib/datadog/core/workers/queue.rb +96 -1
  286. data/lib/datadog/core/workers/runtime_metrics.rb +9 -1
  287. data/lib/datadog/core.rb +11 -2
  288. data/lib/datadog/data_streams/configuration.rb +50 -0
  289. data/lib/datadog/data_streams/ext.rb +11 -0
  290. data/lib/datadog/data_streams/extensions.rb +16 -0
  291. data/lib/datadog/data_streams/pathway_context.rb +169 -0
  292. data/lib/datadog/data_streams/processor.rb +511 -0
  293. data/lib/datadog/data_streams/transport/http/stats.rb +58 -0
  294. data/lib/datadog/data_streams/transport/http.rb +36 -0
  295. data/lib/datadog/data_streams/transport/stats.rb +39 -0
  296. data/lib/datadog/data_streams.rb +100 -0
  297. data/lib/datadog/di/base.rb +15 -7
  298. data/lib/datadog/di/boot.rb +11 -1
  299. data/lib/datadog/di/code_tracker.rb +217 -15
  300. data/lib/datadog/di/component.rb +143 -48
  301. data/lib/datadog/di/configuration.rb +235 -2
  302. data/lib/datadog/di/context.rb +76 -0
  303. data/lib/datadog/di/contrib/active_record.rb +34 -5
  304. data/lib/datadog/di/el/compiler.rb +168 -0
  305. data/lib/datadog/di/el/evaluator.rb +159 -0
  306. data/lib/datadog/di/el/expression.rb +42 -0
  307. data/lib/datadog/di/el.rb +5 -0
  308. data/lib/datadog/di/error.rb +44 -0
  309. data/lib/datadog/di/fatal_exceptions.rb +26 -0
  310. data/lib/datadog/di/instrumenter.rb +620 -132
  311. data/lib/datadog/di/logger.rb +2 -2
  312. data/lib/datadog/di/probe.rb +78 -15
  313. data/lib/datadog/di/probe_builder.rb +42 -3
  314. data/lib/datadog/di/probe_file_loader/railtie.rb +15 -0
  315. data/lib/datadog/di/probe_file_loader.rb +87 -0
  316. data/lib/datadog/di/probe_manager.rb +195 -100
  317. data/lib/datadog/di/probe_notification_builder.rb +285 -100
  318. data/lib/datadog/di/probe_notifier_worker.rb +120 -36
  319. data/lib/datadog/di/probe_repository.rb +198 -0
  320. data/lib/datadog/di/proc_responder.rb +36 -0
  321. data/lib/datadog/di/redactor.rb +24 -2
  322. data/lib/datadog/di/remote.rb +258 -95
  323. data/lib/datadog/di/serializer.rb +297 -20
  324. data/lib/datadog/di/transport/diagnostics.rb +12 -42
  325. data/lib/datadog/di/transport/http/diagnostics.rb +4 -34
  326. data/lib/datadog/di/transport/http/input.rb +12 -34
  327. data/lib/datadog/di/transport/http.rb +43 -19
  328. data/lib/datadog/di/transport/input.rb +121 -39
  329. data/lib/datadog/di/utils.rb +42 -14
  330. data/lib/datadog/di.rb +203 -10
  331. data/lib/datadog/error_tracking/collector.rb +2 -1
  332. data/lib/datadog/error_tracking/component.rb +2 -2
  333. data/lib/datadog/error_tracking/configuration.rb +55 -2
  334. data/lib/datadog/error_tracking/filters.rb +2 -2
  335. data/lib/datadog/kit/appsec/events/v2.rb +253 -0
  336. data/lib/datadog/kit/appsec/events.rb +11 -10
  337. data/lib/datadog/kit/enable_core_dumps.rb +1 -1
  338. data/lib/datadog/kit/identity.rb +17 -11
  339. data/lib/datadog/kit/tracing/method_tracer.rb +135 -0
  340. data/lib/datadog/open_feature/component.rb +100 -0
  341. data/lib/datadog/open_feature/configuration.rb +37 -0
  342. data/lib/datadog/open_feature/evaluation_engine.rb +70 -0
  343. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  344. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  345. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  346. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  347. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  348. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  349. data/lib/datadog/open_feature/ext.rb +16 -0
  350. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
  351. data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
  352. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
  353. data/lib/datadog/open_feature/hooks/flag_eval_metrics_hook.rb +49 -0
  354. data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
  355. data/lib/datadog/open_feature/native_evaluator.rb +60 -0
  356. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  357. data/lib/datadog/open_feature/provider.rb +191 -0
  358. data/lib/datadog/open_feature/remote.rb +67 -0
  359. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  360. data/lib/datadog/open_feature/transport.rb +121 -0
  361. data/lib/datadog/open_feature.rb +19 -0
  362. data/lib/datadog/opentelemetry/api/baggage.rb +3 -3
  363. data/lib/datadog/opentelemetry/api/context.rb +10 -9
  364. data/lib/datadog/opentelemetry/configuration/settings.rb +226 -0
  365. data/lib/datadog/opentelemetry/ext.rb +9 -0
  366. data/lib/datadog/opentelemetry/logs.rb +98 -0
  367. data/lib/datadog/opentelemetry/metrics.rb +90 -0
  368. data/lib/datadog/opentelemetry/sdk/configurator.rb +66 -2
  369. data/lib/datadog/opentelemetry/sdk/id_generator.rb +16 -10
  370. data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +30 -0
  371. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +28 -0
  372. data/lib/datadog/opentelemetry/sdk/propagator.rb +13 -7
  373. data/lib/datadog/opentelemetry/sdk/span_processor.rb +12 -9
  374. data/lib/datadog/opentelemetry/sdk/trace/span.rb +14 -10
  375. data/lib/datadog/opentelemetry/sdk.rb +19 -0
  376. data/lib/datadog/opentelemetry/signal_configuration.rb +53 -0
  377. data/lib/datadog/opentelemetry/trace.rb +4 -4
  378. data/lib/datadog/opentelemetry.rb +4 -0
  379. data/lib/datadog/profiling/collectors/code_provenance.rb +83 -15
  380. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +52 -3
  381. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +13 -4
  382. data/lib/datadog/profiling/collectors/info.rb +59 -4
  383. data/lib/datadog/profiling/collectors/thread_context.rb +17 -5
  384. data/lib/datadog/profiling/component.rb +73 -49
  385. data/lib/datadog/profiling/exporter.rb +48 -13
  386. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +24 -5
  387. data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
  388. data/lib/datadog/profiling/ext.rb +2 -15
  389. data/lib/datadog/profiling/flush.rb +23 -11
  390. data/lib/datadog/profiling/http_transport.rb +24 -11
  391. data/lib/datadog/profiling/load_native_extension.rb +2 -2
  392. data/lib/datadog/profiling/profiler.rb +31 -12
  393. data/lib/datadog/profiling/scheduler.rb +12 -10
  394. data/lib/datadog/profiling/sequence_tracker.rb +44 -0
  395. data/lib/datadog/profiling/stack_recorder.rb +5 -13
  396. data/lib/datadog/profiling/tag_builder.rb +42 -38
  397. data/lib/datadog/profiling/tasks/exec.rb +10 -5
  398. data/lib/datadog/profiling/tasks/help.rb +1 -0
  399. data/lib/datadog/profiling/tasks/setup.rb +4 -2
  400. data/lib/datadog/profiling.rb +8 -11
  401. data/lib/datadog/ruby_version.rb +25 -0
  402. data/lib/datadog/single_step_instrument.rb +10 -1
  403. data/lib/datadog/symbol_database/component.rb +734 -0
  404. data/lib/datadog/symbol_database/configuration.rb +73 -0
  405. data/lib/datadog/symbol_database/extensions.rb +19 -0
  406. data/lib/datadog/symbol_database/extractor.rb +1110 -0
  407. data/lib/datadog/symbol_database/file_hash.rb +48 -0
  408. data/lib/datadog/symbol_database/logger.rb +43 -0
  409. data/lib/datadog/symbol_database/remote.rb +180 -0
  410. data/lib/datadog/symbol_database/scope.rb +102 -0
  411. data/lib/datadog/symbol_database/scope_batcher.rb +292 -0
  412. data/lib/datadog/symbol_database/service_version.rb +66 -0
  413. data/lib/datadog/symbol_database/symbol.rb +69 -0
  414. data/lib/datadog/symbol_database/transport/http/endpoint.rb +28 -0
  415. data/lib/datadog/symbol_database/transport/http.rb +45 -0
  416. data/lib/datadog/symbol_database/transport.rb +54 -0
  417. data/lib/datadog/symbol_database/uploader.rb +230 -0
  418. data/lib/datadog/symbol_database.rb +71 -0
  419. data/lib/datadog/tracing/analytics.rb +1 -1
  420. data/lib/datadog/tracing/buffer.rb +10 -10
  421. data/lib/datadog/tracing/component.rb +17 -17
  422. data/lib/datadog/tracing/configuration/dynamic.rb +6 -8
  423. data/lib/datadog/tracing/configuration/ext.rb +25 -5
  424. data/lib/datadog/tracing/configuration/settings.rb +125 -11
  425. data/lib/datadog/tracing/context.rb +2 -2
  426. data/lib/datadog/tracing/contrib/action_cable/event.rb +1 -1
  427. data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +4 -1
  428. data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +4 -1
  429. data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +4 -1
  430. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +4 -1
  431. data/lib/datadog/tracing/contrib/action_cable/integration.rb +1 -1
  432. data/lib/datadog/tracing/contrib/action_mailer/event.rb +4 -1
  433. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +1 -1
  434. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +29 -11
  435. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +47 -12
  436. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +3 -1
  437. data/lib/datadog/tracing/contrib/action_pack/ext.rb +2 -0
  438. data/lib/datadog/tracing/contrib/action_pack/integration.rb +1 -1
  439. data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
  440. data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +4 -1
  441. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +6 -3
  442. data/lib/datadog/tracing/contrib/active_job/event.rb +8 -8
  443. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +6 -3
  444. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +6 -3
  445. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +6 -3
  446. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +6 -3
  447. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +6 -3
  448. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +6 -3
  449. data/lib/datadog/tracing/contrib/active_job/integration.rb +1 -1
  450. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
  451. data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
  452. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +2 -2
  453. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +4 -4
  454. data/lib/datadog/tracing/contrib/active_model_serializers/integration.rb +1 -2
  455. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +3 -3
  456. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +2 -2
  457. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +7 -10
  458. data/lib/datadog/tracing/contrib/active_record/integration.rb +1 -1
  459. data/lib/datadog/tracing/contrib/active_record/utils.rb +16 -16
  460. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +15 -12
  461. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +1 -4
  462. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +13 -0
  463. data/lib/datadog/tracing/contrib/active_support/integration.rb +1 -1
  464. data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +2 -1
  465. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +8 -10
  466. data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
  467. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +8 -10
  468. data/lib/datadog/tracing/contrib/aws/parsed_context.rb +3 -1
  469. data/lib/datadog/tracing/contrib/aws/patcher.rb +5 -1
  470. data/lib/datadog/tracing/contrib/aws/service/base.rb +2 -1
  471. data/lib/datadog/tracing/contrib/aws/service/dynamodb.rb +1 -1
  472. data/lib/datadog/tracing/contrib/aws/service/eventbridge.rb +1 -1
  473. data/lib/datadog/tracing/contrib/aws/service/kinesis.rb +1 -1
  474. data/lib/datadog/tracing/contrib/aws/service/s3.rb +1 -1
  475. data/lib/datadog/tracing/contrib/aws/service/sns.rb +1 -1
  476. data/lib/datadog/tracing/contrib/aws/service/sqs.rb +1 -1
  477. data/lib/datadog/tracing/contrib/aws/service/states.rb +1 -1
  478. data/lib/datadog/tracing/contrib/aws/services.rb +7 -7
  479. data/lib/datadog/tracing/contrib/component.rb +3 -3
  480. data/lib/datadog/tracing/contrib/concurrent_ruby/async_patch.rb +1 -1
  481. data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +1 -1
  482. data/lib/datadog/tracing/contrib/concurrent_ruby/future_patch.rb +1 -1
  483. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +1 -1
  484. data/lib/datadog/tracing/contrib/concurrent_ruby/promises_future_patch.rb +1 -1
  485. data/lib/datadog/tracing/contrib/configurable.rb +22 -7
  486. data/lib/datadog/tracing/contrib/configuration/resolver.rb +14 -4
  487. data/lib/datadog/tracing/contrib/configuration/resolvers/pattern_resolver.rb +4 -4
  488. data/lib/datadog/tracing/contrib/dalli/ext.rb +3 -2
  489. data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +1 -5
  490. data/lib/datadog/tracing/contrib/dalli/integration.rb +5 -2
  491. data/lib/datadog/tracing/contrib/dalli/quantize.rb +1 -1
  492. data/lib/datadog/tracing/contrib/delayed_job/integration.rb +1 -1
  493. data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +2 -0
  494. data/lib/datadog/tracing/contrib/delayed_job/server_internal_tracer/worker.rb +1 -0
  495. data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +3 -2
  496. data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +4 -4
  497. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +47 -53
  498. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +7 -7
  499. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +5 -1
  500. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +7 -8
  501. data/lib/datadog/tracing/contrib/ethon/ext.rb +4 -2
  502. data/lib/datadog/tracing/contrib/ethon/integration.rb +1 -1
  503. data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +1 -8
  504. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +16 -5
  505. data/lib/datadog/tracing/contrib/excon/ext.rb +4 -2
  506. data/lib/datadog/tracing/contrib/excon/integration.rb +1 -1
  507. data/lib/datadog/tracing/contrib/excon/middleware.rb +5 -9
  508. data/lib/datadog/tracing/contrib/ext.rb +6 -4
  509. data/lib/datadog/tracing/contrib/extensions.rb +28 -11
  510. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +16 -9
  511. data/lib/datadog/tracing/contrib/faraday/ext.rb +4 -2
  512. data/lib/datadog/tracing/contrib/faraday/integration.rb +1 -1
  513. data/lib/datadog/tracing/contrib/faraday/middleware.rb +7 -9
  514. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
  515. data/lib/datadog/tracing/contrib/grape/endpoint.rb +18 -15
  516. data/lib/datadog/tracing/contrib/grape/instrumentation.rb +13 -8
  517. data/lib/datadog/tracing/contrib/grape/integration.rb +1 -1
  518. data/lib/datadog/tracing/contrib/grape/patcher.rb +6 -1
  519. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
  520. data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
  521. data/lib/datadog/tracing/contrib/graphql/integration.rb +1 -1
  522. data/lib/datadog/tracing/contrib/graphql/patcher.rb +2 -2
  523. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +105 -63
  524. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +5 -2
  525. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +11 -15
  526. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +6 -10
  527. data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +1 -1
  528. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +2 -0
  529. data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
  530. data/lib/datadog/tracing/contrib/grpc/integration.rb +1 -1
  531. data/lib/datadog/tracing/contrib/grpc.rb +1 -0
  532. data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +1 -0
  533. data/lib/datadog/tracing/contrib/hanami/ext.rb +2 -2
  534. data/lib/datadog/tracing/contrib/hanami/integration.rb +1 -1
  535. data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +2 -1
  536. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +10 -11
  537. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +16 -5
  538. data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +4 -4
  539. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +2 -0
  540. data/lib/datadog/tracing/contrib/http/ext.rb +4 -2
  541. data/lib/datadog/tracing/contrib/http/instrumentation.rb +9 -13
  542. data/lib/datadog/tracing/contrib/http/integration.rb +0 -2
  543. data/lib/datadog/tracing/contrib/http.rb +1 -0
  544. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +16 -5
  545. data/lib/datadog/tracing/contrib/httpclient/ext.rb +4 -2
  546. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +10 -10
  547. data/lib/datadog/tracing/contrib/httpclient/integration.rb +1 -1
  548. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +16 -5
  549. data/lib/datadog/tracing/contrib/httprb/ext.rb +4 -2
  550. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +8 -12
  551. data/lib/datadog/tracing/contrib/httprb/integration.rb +1 -1
  552. data/lib/datadog/tracing/contrib/kafka/event.rb +2 -1
  553. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +1 -1
  554. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +1 -1
  555. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +1 -1
  556. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +1 -1
  557. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/join_group.rb +1 -1
  558. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/leave_group.rb +1 -1
  559. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/sync_group.rb +1 -1
  560. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +1 -1
  561. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +1 -1
  562. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  563. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  564. data/lib/datadog/tracing/contrib/kafka/integration.rb +1 -1
  565. data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
  566. data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +5 -1
  567. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
  568. data/lib/datadog/tracing/contrib/karafka/ext.rb +1 -0
  569. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  570. data/lib/datadog/tracing/contrib/karafka/monitor.rb +24 -13
  571. data/lib/datadog/tracing/contrib/karafka/patcher.rb +39 -8
  572. data/lib/datadog/tracing/contrib/karafka.rb +1 -0
  573. data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +1 -1
  574. data/lib/datadog/tracing/contrib/lograge/integration.rb +1 -1
  575. data/lib/datadog/tracing/contrib/lograge/patcher.rb +4 -2
  576. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +1 -1
  577. data/lib/datadog/tracing/contrib/mongodb/ext.rb +1 -1
  578. data/lib/datadog/tracing/contrib/mongodb/integration.rb +1 -1
  579. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +6 -6
  580. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +11 -14
  581. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +6 -0
  582. data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -1
  583. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +19 -12
  584. data/lib/datadog/tracing/contrib/mysql2/integration.rb +1 -1
  585. data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +1 -1
  586. data/lib/datadog/tracing/contrib/opensearch/ext.rb +3 -2
  587. data/lib/datadog/tracing/contrib/opensearch/integration.rb +1 -2
  588. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +64 -70
  589. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +7 -7
  590. data/lib/datadog/tracing/contrib/patcher.rb +7 -9
  591. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +6 -0
  592. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +3 -6
  593. data/lib/datadog/tracing/contrib/pg/integration.rb +1 -1
  594. data/lib/datadog/tracing/contrib/presto/ext.rb +1 -1
  595. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +9 -11
  596. data/lib/datadog/tracing/contrib/presto/integration.rb +1 -1
  597. data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +1 -1
  598. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +13 -0
  599. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +6 -2
  600. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +25 -1
  601. data/lib/datadog/tracing/contrib/que/configuration/settings.rb +5 -2
  602. data/lib/datadog/tracing/contrib/que/ext.rb +1 -0
  603. data/lib/datadog/tracing/contrib/que/integration.rb +1 -1
  604. data/lib/datadog/tracing/contrib/que/tracer.rb +1 -0
  605. data/lib/datadog/tracing/contrib/racecar/event.rb +2 -6
  606. data/lib/datadog/tracing/contrib/racecar/events/batch.rb +2 -2
  607. data/lib/datadog/tracing/contrib/racecar/events/consume.rb +1 -1
  608. data/lib/datadog/tracing/contrib/racecar/events/message.rb +2 -2
  609. data/lib/datadog/tracing/contrib/racecar/integration.rb +1 -1
  610. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +11 -1
  611. data/lib/datadog/tracing/contrib/rack/ext.rb +28 -0
  612. data/lib/datadog/tracing/contrib/rack/header_collection.rb +1 -1
  613. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +55 -32
  614. data/lib/datadog/tracing/contrib/rack/integration.rb +1 -1
  615. data/lib/datadog/tracing/contrib/rack/middlewares.rb +83 -44
  616. data/lib/datadog/tracing/contrib/rack/patcher.rb +2 -2
  617. data/lib/datadog/tracing/contrib/rack/request_queue.rb +4 -3
  618. data/lib/datadog/tracing/contrib/rack/route_inference.rb +67 -0
  619. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +126 -2
  620. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +6 -3
  621. data/lib/datadog/tracing/contrib/rails/ext.rb +3 -1
  622. data/lib/datadog/tracing/contrib/rails/integration.rb +2 -2
  623. data/lib/datadog/tracing/contrib/rails/log_injection.rb +2 -2
  624. data/lib/datadog/tracing/contrib/rails/middlewares.rb +3 -3
  625. data/lib/datadog/tracing/contrib/rails/patcher.rb +4 -2
  626. data/lib/datadog/tracing/contrib/rails/runner.rb +64 -40
  627. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +10 -8
  628. data/lib/datadog/tracing/contrib/rake/integration.rb +1 -1
  629. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +2 -2
  630. data/lib/datadog/tracing/contrib/redis/ext.rb +3 -2
  631. data/lib/datadog/tracing/contrib/redis/integration.rb +2 -2
  632. data/lib/datadog/tracing/contrib/redis/patcher.rb +4 -4
  633. data/lib/datadog/tracing/contrib/redis/quantize.rb +2 -2
  634. data/lib/datadog/tracing/contrib/redis/tags.rb +2 -7
  635. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +6 -4
  636. data/lib/datadog/tracing/contrib/registerable.rb +11 -0
  637. data/lib/datadog/tracing/contrib/registry.rb +1 -1
  638. data/lib/datadog/tracing/contrib/resque/integration.rb +1 -1
  639. data/lib/datadog/tracing/contrib/resque/resque_job.rb +2 -1
  640. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +5 -2
  641. data/lib/datadog/tracing/contrib/rest_client/ext.rb +4 -2
  642. data/lib/datadog/tracing/contrib/rest_client/integration.rb +1 -1
  643. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +8 -9
  644. data/lib/datadog/tracing/contrib/roda/ext.rb +1 -0
  645. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +8 -3
  646. data/lib/datadog/tracing/contrib/roda/integration.rb +1 -1
  647. data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +1 -1
  648. data/lib/datadog/tracing/contrib/semantic_logger/integration.rb +1 -1
  649. data/lib/datadog/tracing/contrib/sequel/database.rb +6 -5
  650. data/lib/datadog/tracing/contrib/sequel/dataset.rb +2 -1
  651. data/lib/datadog/tracing/contrib/sequel/integration.rb +1 -1
  652. data/lib/datadog/tracing/contrib/sequel/utils.rb +1 -6
  653. data/lib/datadog/tracing/contrib/shoryuken/integration.rb +1 -1
  654. data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +1 -0
  655. data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +1 -0
  656. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +5 -1
  657. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +2 -0
  658. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +2 -0
  659. data/lib/datadog/tracing/contrib/sidekiq/integration.rb +1 -1
  660. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/heartbeat.rb +2 -0
  661. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/job_fetch.rb +1 -0
  662. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/redis_info.rb +1 -0
  663. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/scheduled_poller.rb +2 -0
  664. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/stop.rb +1 -0
  665. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +8 -4
  666. data/lib/datadog/tracing/contrib/sidekiq/utils.rb +1 -1
  667. data/lib/datadog/tracing/contrib/sidekiq.rb +1 -0
  668. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +5 -1
  669. data/lib/datadog/tracing/contrib/sinatra/ext.rb +1 -0
  670. data/lib/datadog/tracing/contrib/sinatra/integration.rb +1 -1
  671. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +1 -0
  672. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +40 -39
  673. data/lib/datadog/tracing/contrib/sneakers/integration.rb +15 -4
  674. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -0
  675. data/lib/datadog/tracing/contrib/span_attribute_schema.rb +1 -1
  676. data/lib/datadog/tracing/contrib/status_range_matcher.rb +13 -1
  677. data/lib/datadog/tracing/contrib/stripe/integration.rb +1 -1
  678. data/lib/datadog/tracing/contrib/stripe/request.rb +2 -2
  679. data/lib/datadog/tracing/contrib/sucker_punch/instrumentation.rb +1 -0
  680. data/lib/datadog/tracing/contrib/sucker_punch/integration.rb +1 -1
  681. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +6 -0
  682. data/lib/datadog/tracing/contrib/trilogy/ext.rb +1 -1
  683. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -6
  684. data/lib/datadog/tracing/contrib/trilogy/integration.rb +1 -1
  685. data/lib/datadog/tracing/contrib/utils/quantization/{hash.rb → hash_formatter.rb} +15 -13
  686. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +6 -6
  687. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +31 -0
  688. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +50 -0
  689. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +18 -0
  690. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  691. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  692. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +49 -0
  693. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  694. data/lib/datadog/tracing/contrib/waterdrop.rb +42 -0
  695. data/lib/datadog/tracing/contrib.rb +9 -0
  696. data/lib/datadog/tracing/diagnostics/environment_logger.rb +12 -4
  697. data/lib/datadog/tracing/distributed/b3_single.rb +1 -1
  698. data/lib/datadog/tracing/distributed/baggage.rb +133 -12
  699. data/lib/datadog/tracing/distributed/datadog.rb +17 -16
  700. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +11 -26
  701. data/lib/datadog/tracing/distributed/helpers.rb +1 -1
  702. data/lib/datadog/tracing/distributed/none.rb +4 -2
  703. data/lib/datadog/tracing/distributed/propagation.rb +39 -4
  704. data/lib/datadog/tracing/distributed/propagation_policy.rb +1 -1
  705. data/lib/datadog/tracing/distributed/trace_context.rb +107 -78
  706. data/lib/datadog/tracing/event.rb +5 -7
  707. data/lib/datadog/tracing/ext.rb +9 -0
  708. data/lib/datadog/tracing/flush.rb +1 -1
  709. data/lib/datadog/tracing/metadata/analytics.rb +1 -1
  710. data/lib/datadog/tracing/metadata/ext.rb +23 -1
  711. data/lib/datadog/tracing/metadata/tagging.rb +6 -6
  712. data/lib/datadog/tracing/pipeline/span_filter.rb +3 -1
  713. data/lib/datadog/tracing/pipeline/span_processor.rb +3 -1
  714. data/lib/datadog/tracing/pipeline.rb +2 -2
  715. data/lib/datadog/tracing/remote.rb +36 -13
  716. data/lib/datadog/tracing/sampling/ext.rb +2 -2
  717. data/lib/datadog/tracing/sampling/priority_sampler.rb +16 -1
  718. data/lib/datadog/tracing/sampling/rate_sampler.rb +8 -19
  719. data/lib/datadog/tracing/sampling/rule.rb +1 -1
  720. data/lib/datadog/tracing/sampling/rule_sampler.rb +77 -48
  721. data/lib/datadog/tracing/sampling/span/rule_parser.rb +3 -3
  722. data/lib/datadog/tracing/sampling/span/sampler.rb +0 -7
  723. data/lib/datadog/tracing/span.rb +2 -2
  724. data/lib/datadog/tracing/span_event.rb +12 -12
  725. data/lib/datadog/tracing/span_link.rb +12 -12
  726. data/lib/datadog/tracing/span_operation.rb +63 -19
  727. data/lib/datadog/tracing/sync_writer.rb +1 -2
  728. data/lib/datadog/tracing/trace_digest.rb +28 -23
  729. data/lib/datadog/tracing/trace_operation.rb +209 -98
  730. data/lib/datadog/tracing/trace_segment.rb +2 -2
  731. data/lib/datadog/tracing/tracer.rb +113 -52
  732. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  733. data/lib/datadog/tracing/transport/http/traces.rb +4 -52
  734. data/lib/datadog/tracing/transport/http.rb +15 -9
  735. data/lib/datadog/tracing/transport/io/client.rb +11 -14
  736. data/lib/datadog/tracing/transport/io/traces.rb +28 -34
  737. data/lib/datadog/tracing/transport/statistics.rb +1 -1
  738. data/lib/datadog/tracing/transport/trace_formatter.rb +22 -0
  739. data/lib/datadog/tracing/transport/traces.rb +14 -82
  740. data/lib/datadog/tracing/workers.rb +4 -3
  741. data/lib/datadog/tracing/writer.rb +1 -1
  742. data/lib/datadog/tracing.rb +2 -2
  743. data/lib/datadog/version.rb +2 -2
  744. data/lib/datadog.rb +14 -0
  745. metadata +201 -40
  746. data/ext/datadog_profiling_native_extension/clock_id_noop.c +0 -21
  747. data/ext/libdatadog_api/macos_development.md +0 -26
  748. data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -321
  749. data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -1023
  750. data/lib/datadog/appsec/configuration/settings.rb +0 -342
  751. data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
  752. data/lib/datadog/appsec/processor/rule_merger.rb +0 -171
  753. data/lib/datadog/appsec/processor.rb +0 -107
  754. data/lib/datadog/core/remote/transport/http/api.rb +0 -53
  755. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  756. data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
  757. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  758. data/lib/datadog/core/transport/http/api/spec.rb +0 -36
  759. data/lib/datadog/di/configuration/settings.rb +0 -212
  760. data/lib/datadog/di/transport/http/api.rb +0 -42
  761. data/lib/datadog/di/transport/http/client.rb +0 -47
  762. data/lib/datadog/error_tracking/configuration/settings.rb +0 -63
  763. data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
  764. data/lib/datadog/tracing/transport/http/api.rb +0 -44
  765. data/lib/datadog/tracing/workers/trace_writer.rb +0 -199
@@ -17,6 +17,10 @@
17
17
  #include "setup_signal_handler.h"
18
18
  #include "time_helpers.h"
19
19
 
20
+ #ifdef __APPLE__
21
+ #include "macos_sampler_thread.h"
22
+ #endif
23
+
20
24
  // Used to trigger the execution of Collectors::ThreadContext, which implements all of the sampling logic
21
25
  // itself; this class only implements the "when to do it" part.
22
26
  //
@@ -76,8 +80,6 @@
76
80
  //
77
81
  // ---
78
82
 
79
- #define ERR_CLOCK_FAIL "failed to get clock time"
80
-
81
83
  // Maximum allowed value for an allocation weight. Attempts to use higher values will result in clamping.
82
84
  // See https://docs.google.com/document/d/1lWLB714wlLBBq6T4xZyAc4a5wtWhSmr4-hgiPKeErlA/edit#heading=h.ugp0zxcj5iqh
83
85
  // (Datadog-only link) for research backing the choice of this value.
@@ -89,6 +91,7 @@ unsigned int MAX_ALLOC_WEIGHT = 10000;
89
91
  static rb_postponed_job_handle_t sample_from_postponed_job_handle;
90
92
  static rb_postponed_job_handle_t after_gc_from_postponed_job_handle;
91
93
  static rb_postponed_job_handle_t after_gvl_running_from_postponed_job_handle;
94
+ static rb_postponed_job_handle_t after_allocation_from_postponed_job_handle;
92
95
  #endif
93
96
 
94
97
  // Contains state for a single CpuAndWallTimeWorker instance
@@ -102,6 +105,8 @@ typedef struct {
102
105
  bool allocation_counting_enabled;
103
106
  bool gvl_profiling_enabled;
104
107
  bool skip_idle_samples_for_testing;
108
+ bool sighandler_sampling_enabled;
109
+ uint32_t cpu_sampling_interval_ms;
105
110
  VALUE self_instance;
106
111
  VALUE thread_context_collector_instance;
107
112
  VALUE idle_sampling_helper_instance;
@@ -116,13 +121,18 @@ typedef struct {
116
121
  // When something goes wrong during sampling, we record the Ruby exception here, so that it can be "re-raised" on
117
122
  // the CpuAndWallTimeWorker thread
118
123
  VALUE failure_exception;
124
+ const char *failure_exception_during_operation;
119
125
  // Used by `_native_stop` to flag the worker thread to start (see comment on `_native_sampling_loop`)
120
126
  VALUE stop_thread;
121
127
 
122
128
  // Others
123
129
 
124
130
  // Used to detect/avoid nested sampling, e.g. when on_newobj_event gets triggered by a memory allocation
125
- // that happens during another sample.
131
+ // that happens during another sample, or when the signal handler gets triggered while we're already in the middle of
132
+ // sampling.
133
+ //
134
+ // @ivoanjo: Right now we always sample inside `safely_call`; if that ever changes, this flag may need to become
135
+ // volatile/atomic/have some barriers to ensure it's visible during e.g. signal handlers.
126
136
  bool during_sample;
127
137
 
128
138
  #ifndef NO_GVL_INSTRUMENTATION
@@ -134,27 +144,21 @@ typedef struct {
134
144
  // # Generic stats
135
145
  // How many times we tried to trigger a sample
136
146
  unsigned int trigger_sample_attempts;
147
+ // How many times extra sleep was triggered by dynamic sampling rate
148
+ unsigned int trigger_sample_extra_sleep;
137
149
  // How many times we tried to simulate signal delivery
138
150
  unsigned int trigger_simulated_signal_delivery_attempts;
139
151
  // How many times we actually simulated signal delivery
140
152
  unsigned int simulated_signal_delivery;
141
- // How many times we actually called rb_postponed_job_register_one from a signal handler
153
+ // How many times we actually called rb_postponed_job_register_one from the signal handler
142
154
  unsigned int signal_handler_enqueued_sample;
155
+ // How many times we prepared a sample (sampled directly) from the signal handler
156
+ unsigned int signal_handler_prepared_sample;
143
157
  // How many times the signal handler was called from the wrong thread
144
158
  unsigned int signal_handler_wrong_thread;
145
159
  // How many times we actually tried to interrupt a thread for sampling
146
160
  unsigned int interrupt_thread_attempts;
147
161
 
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
162
  // # CPU/Walltime sampling stats
159
163
  // How many times we actually CPU/wall sampled
160
164
  unsigned int cpu_sampled;
@@ -178,6 +182,11 @@ typedef struct {
178
182
  unsigned int allocations_during_sample;
179
183
 
180
184
  // # GVL profiling stats
185
+ // Note that this tracks two kind of samples from RESUMED:
186
+ // * samples when Waiting for GVL for more than the threshold
187
+ // * samples for the skip-samples-while-without-GVL optimization,
188
+ // which are needed to attribute the time without GVL to the correct Ruby stack
189
+
181
190
  // How many times we triggered the after_gvl_running sampling
182
191
  unsigned int after_gvl_running;
183
192
  // How many times we skipped the after_gvl_running sampling
@@ -186,6 +195,11 @@ typedef struct {
186
195
  uint64_t gvl_sampling_time_ns_min;
187
196
  uint64_t gvl_sampling_time_ns_max;
188
197
  uint64_t gvl_sampling_time_ns_total;
198
+
199
+ struct vm_metrics {
200
+ // Total time spent waiting for the GVL
201
+ uint64_t gvl_waiting_time_ns_total;
202
+ } vm_metrics;
189
203
  } stats;
190
204
  } cpu_and_wall_time_worker_state;
191
205
 
@@ -194,17 +208,17 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
194
208
  static void cpu_and_wall_time_worker_typed_data_mark(void *state_ptr);
195
209
  static VALUE _native_sampling_loop(VALUE self, VALUE instance);
196
210
  static VALUE _native_stop(DDTRACE_UNUSED VALUE _self, VALUE self_instance, VALUE worker_thread);
197
- static VALUE stop(VALUE self_instance, VALUE optional_exception);
198
- static void stop_state(cpu_and_wall_time_worker_state *state, VALUE optional_exception);
211
+ static VALUE stop(VALUE self_instance, VALUE optional_exception, const char *optional_exception_during_operation);
212
+ static void stop_state(cpu_and_wall_time_worker_state *state, VALUE optional_exception, const char *optional_operation_name);
199
213
  static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
200
214
  static void *run_sampling_trigger_loop(void *state_ptr);
201
215
  static void interrupt_sampling_trigger_loop(void *state_ptr);
202
216
  static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused);
203
217
  static VALUE rescued_sample_from_postponed_job(VALUE self_instance);
204
- static VALUE handle_sampling_failure(VALUE self_instance, VALUE exception);
205
218
  static VALUE _native_current_sigprof_signal_handler(DDTRACE_UNUSED VALUE self);
206
219
  static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance);
207
220
  static VALUE _native_is_running(DDTRACE_UNUSED VALUE self, VALUE instance);
221
+ static VALUE _native_failure_exception_during_operation(DDTRACE_UNUSED VALUE self, VALUE instance);
208
222
  static void testing_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
209
223
  static VALUE _native_install_testing_signal_handler(DDTRACE_UNUSED VALUE self);
210
224
  static VALUE _native_remove_testing_signal_handler(DDTRACE_UNUSED VALUE self);
@@ -212,7 +226,12 @@ static VALUE _native_trigger_sample(DDTRACE_UNUSED VALUE self);
212
226
  static VALUE _native_gc_tracepoint(DDTRACE_UNUSED VALUE self, VALUE instance);
213
227
  static void on_gc_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused);
214
228
  static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused);
215
- static VALUE safely_call(VALUE (*function_to_call_safely)(VALUE), VALUE function_to_call_safely_arg, VALUE instance);
229
+ static VALUE safely_call(
230
+ VALUE (*function_to_call_safely)(VALUE),
231
+ VALUE function_to_call_safely_arg,
232
+ VALUE instance,
233
+ VALUE (*handle_sampling_failure)(VALUE, VALUE)
234
+ );
216
235
  static VALUE _native_simulate_handle_sampling_signal(DDTRACE_UNUSED VALUE self);
217
236
  static VALUE _native_simulate_sample_from_postponed_job(DDTRACE_UNUSED VALUE self);
218
237
  static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE instance);
@@ -228,16 +247,26 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
228
247
  static void disable_tracepoints(cpu_and_wall_time_worker_state *state);
229
248
  static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self);
230
249
  static VALUE rescued_sample_allocation(VALUE tracepoint_data);
250
+ static VALUE rescued_after_allocation(VALUE self_instance);
231
251
  static void delayed_error(cpu_and_wall_time_worker_state *state, const char *error);
252
+ static void delayed_error_clock_failure(cpu_and_wall_time_worker_state *state);
232
253
  static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg);
233
254
  static VALUE _native_hold_signals(DDTRACE_UNUSED VALUE self);
234
255
  static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self);
235
256
  #ifndef NO_GVL_INSTRUMENTATION
236
- static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused);
237
- static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused);
257
+ static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused);
258
+ static void after_gvl_running_from_postponed_job(DDTRACE_UNUSED void *_unused);
259
+ static VALUE rescued_after_gvl_running_from_postponed_job(VALUE self_instance);
260
+ static VALUE handle_sampling_failure_rescued_after_gvl_running_from_postponed_job(VALUE self_instance, VALUE exception);
238
261
  #endif
239
- static VALUE rescued_after_gvl_running_from_postponed_job(VALUE self_instance);
240
262
  static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE instance);
263
+ static VALUE handle_sampling_failure_rescued_sample_from_postponed_job(VALUE self_instance, VALUE exception);
264
+ static VALUE handle_sampling_failure_thread_context_collector_sample_after_gc(VALUE self_instance, VALUE exception);
265
+ static VALUE handle_sampling_failure_rescued_sample_allocation(VALUE self_instance, VALUE exception);
266
+ static VALUE handle_sampling_failure_rescued_after_allocation(VALUE self_instance, VALUE exception);
267
+ static inline void during_sample_enter(cpu_and_wall_time_worker_state* state);
268
+ static inline void during_sample_exit(cpu_and_wall_time_worker_state* state);
269
+ static void after_allocation_from_postponed_job(DDTRACE_UNUSED void *_unused);
241
270
 
242
271
  // We're using `on_newobj_event` function with `rb_add_event_hook2`, which requires in its public signature a function
243
272
  // with signature `rb_event_hook_func_t` which doesn't match `on_newobj_event`.
@@ -263,6 +292,7 @@ static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, VALUE
263
292
  // (e.g. signal handler) where it's impossible or just awkward to pass it as an argument.
264
293
  static VALUE active_sampler_instance = Qnil;
265
294
  static cpu_and_wall_time_worker_state *active_sampler_instance_state = NULL;
295
+ static VALUE clock_failure_exception_class = Qnil;
266
296
 
267
297
  // See handle_sampling_signal for details on what this does
268
298
  #ifdef NO_POSTPONED_TRIGGER
@@ -284,13 +314,15 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
284
314
  sample_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, sample_from_postponed_job, NULL);
285
315
  after_gc_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_gc_from_postponed_job, NULL);
286
316
  after_gvl_running_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_gvl_running_from_postponed_job, NULL);
317
+ after_allocation_from_postponed_job_handle = rb_postponed_job_preregister(unused_flags, after_allocation_from_postponed_job, NULL);
287
318
 
288
319
  if (
289
320
  sample_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
290
321
  after_gc_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
291
- after_gvl_running_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID
322
+ after_gvl_running_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID ||
323
+ after_allocation_from_postponed_job_handle == POSTPONED_JOB_HANDLE_INVALID
292
324
  ) {
293
- rb_raise(rb_eRuntimeError, "Failed to register profiler postponed jobs (got POSTPONED_JOB_HANDLE_INVALID)");
325
+ raise_error(rb_eRuntimeError, "Failed to register profiler postponed jobs (got POSTPONED_JOB_HANDLE_INVALID)");
294
326
  }
295
327
  #else
296
328
  gc_finalize_deferred_workaround = objspace_ptr_for_gc_finalize_deferred_workaround();
@@ -300,6 +332,8 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
300
332
  VALUE collectors_cpu_and_wall_time_worker_class = rb_define_class_under(collectors_module, "CpuAndWallTimeWorker", rb_cObject);
301
333
  // Hosts methods used for testing the native code using RSpec
302
334
  VALUE testing_module = rb_define_module_under(collectors_cpu_and_wall_time_worker_class, "Testing");
335
+ clock_failure_exception_class = rb_define_class_under(collectors_cpu_and_wall_time_worker_class, "ClockFailure", rb_eRuntimeError);
336
+ rb_gc_register_mark_object(clock_failure_exception_class);
303
337
 
304
338
  // Instances of the CpuAndWallTimeWorker class are "TypedData" objects.
305
339
  // "TypedData" objects are special objects in the Ruby VM that can wrap C structs.
@@ -319,6 +353,7 @@ void collectors_cpu_and_wall_time_worker_init(VALUE profiling_module) {
319
353
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_stats_reset_not_thread_safe", _native_stats_reset_not_thread_safe, 1);
320
354
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_allocation_count", _native_allocation_count, 0);
321
355
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_is_running?", _native_is_running, 1);
356
+ rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_failure_exception_during_operation", _native_failure_exception_during_operation, 1);
322
357
  rb_define_singleton_method(testing_module, "_native_current_sigprof_signal_handler", _native_current_sigprof_signal_handler, 0);
323
358
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_hold_signals", _native_hold_signals, 0);
324
359
  rb_define_singleton_method(collectors_cpu_and_wall_time_worker_class, "_native_resume_signals", _native_resume_signals, 0);
@@ -362,6 +397,8 @@ static VALUE _native_new(VALUE klass) {
362
397
  state->allocation_counting_enabled = false;
363
398
  state->gvl_profiling_enabled = false;
364
399
  state->skip_idle_samples_for_testing = false;
400
+ state->sighandler_sampling_enabled = false;
401
+ state->cpu_sampling_interval_ms = 10;
365
402
  state->thread_context_collector_instance = Qnil;
366
403
  state->idle_sampling_helper_instance = Qnil;
367
404
  state->owner_thread = Qnil;
@@ -370,9 +407,10 @@ static VALUE _native_new(VALUE klass) {
370
407
 
371
408
  atomic_init(&state->should_run, false);
372
409
  state->failure_exception = Qnil;
410
+ state->failure_exception_during_operation = NULL;
373
411
  state->stop_thread = Qnil;
374
412
 
375
- state->during_sample = false;
413
+ during_sample_exit(state);
376
414
 
377
415
  #ifndef NO_GVL_INSTRUMENTATION
378
416
  state->gvl_profiling_hook = NULL;
@@ -404,6 +442,8 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
404
442
  VALUE allocation_counting_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("allocation_counting_enabled")));
405
443
  VALUE gvl_profiling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("gvl_profiling_enabled")));
406
444
  VALUE skip_idle_samples_for_testing = rb_hash_fetch(options, ID2SYM(rb_intern("skip_idle_samples_for_testing")));
445
+ VALUE sighandler_sampling_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("sighandler_sampling_enabled")));
446
+ VALUE cpu_sampling_interval_ms = rb_hash_fetch(options, ID2SYM(rb_intern("cpu_sampling_interval_ms")));
407
447
 
408
448
  ENFORCE_BOOLEAN(gc_profiling_enabled);
409
449
  ENFORCE_BOOLEAN(no_signals_workaround_enabled);
@@ -413,6 +453,8 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
413
453
  ENFORCE_BOOLEAN(allocation_counting_enabled);
414
454
  ENFORCE_BOOLEAN(gvl_profiling_enabled);
415
455
  ENFORCE_BOOLEAN(skip_idle_samples_for_testing)
456
+ ENFORCE_BOOLEAN(sighandler_sampling_enabled)
457
+ ENFORCE_TYPE(cpu_sampling_interval_ms, T_FIXNUM);
416
458
 
417
459
  cpu_and_wall_time_worker_state *state;
418
460
  TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
@@ -424,6 +466,8 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
424
466
  state->allocation_counting_enabled = (allocation_counting_enabled == Qtrue);
425
467
  state->gvl_profiling_enabled = (gvl_profiling_enabled == Qtrue);
426
468
  state->skip_idle_samples_for_testing = (skip_idle_samples_for_testing == Qtrue);
469
+ state->sighandler_sampling_enabled = (sighandler_sampling_enabled == Qtrue);
470
+ state->cpu_sampling_interval_ms = NUM2INT(cpu_sampling_interval_ms);
427
471
 
428
472
  double total_overhead_target_percentage = NUM2DBL(dynamic_sampling_rate_overhead_target_percentage);
429
473
  if (!state->allocation_profiling_enabled) {
@@ -469,10 +513,7 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
469
513
  cpu_and_wall_time_worker_state *old_state = active_sampler_instance_state;
470
514
  if (old_state != NULL) {
471
515
  if (is_thread_alive(old_state->owner_thread)) {
472
- rb_raise(
473
- rb_eRuntimeError,
474
- "Could not start CpuAndWallTimeWorker: There's already another instance of CpuAndWallTimeWorker active in a different thread"
475
- );
516
+ raise_error(rb_eRuntimeError, "Could not start CpuAndWallTimeWorker: There's already another instance of CpuAndWallTimeWorker active in a different thread");
476
517
  } else {
477
518
  // The previously active thread seems to have died without cleaning up after itself.
478
519
  // In this case, we can still go ahead and start the profiler BUT we make sure to disable any existing tracepoint
@@ -489,6 +530,8 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
489
530
  // situation we stop immediately and never even start the sampling trigger loop.
490
531
  if (state->stop_thread == rb_thread_current()) return Qnil;
491
532
 
533
+ thread_context_collector_profiler_internal_thread_started();
534
+
492
535
  // Reset the dynamic sampling rate state, if any (reminder: the monotonic clock reference may change after a fork)
493
536
  dynamic_sampling_rate_reset(&state->cpu_dynamic_sampling_rate);
494
537
  long now = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
@@ -503,6 +546,14 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
503
546
 
504
547
  block_sigprof_signal_handler_from_running_in_current_thread(); // We want to interrupt the thread with the global VM lock, never this one
505
548
 
549
+ #ifdef __APPLE__
550
+ // Promote the native thread that will run the sampling loop to a realtime scheduling policy so
551
+ // its periodic wake-ups are not subject to the default ~1-2ms timeshare wake latency. The matching
552
+ // demote happens below in this same function, alongside the SIGPROF unblock dance, so both per-thread
553
+ // policy changes are reverted before Ruby reuses this native thread for an unrelated Ruby thread.
554
+ promote_sampler_thread_to_realtime(MILLIS_AS_NS(state->cpu_sampling_interval_ms));
555
+ #endif
556
+
506
557
  // Release GVL, get to the actual work!
507
558
  int exception_state;
508
559
  rb_protect(release_gvl_and_run_sampling_trigger_loop, instance, &exception_state);
@@ -524,6 +575,14 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
524
575
  // had SIGPROF delivery blocked. :hide_the_pain_harold:
525
576
  unblock_sigprof_signal_handler_from_running_in_current_thread();
526
577
 
578
+ #ifdef __APPLE__
579
+ // Pairs with promote_sampler_thread_to_realtime above. Same reasoning as the SIGPROF unblock:
580
+ // Ruby may reuse this native thread for an unrelated Ruby thread that would otherwise inherit
581
+ // our scheduler policy. Doing it here also covers the exception path, since grab_gvl_and_sample
582
+ // can raise and unwind out of the sampling loop.
583
+ demote_sampler_thread_from_realtime();
584
+ #endif
585
+
527
586
  // Why replace and not use remove the signal handler? We do this because when a process receives a SIGPROF without
528
587
  // having an explicit signal handler set up, the process will instantly terminate with a confusing
529
588
  // "Profiling timer expired" message left behind. (This message doesn't come from us -- it's the default message for
@@ -534,8 +593,9 @@ static VALUE _native_sampling_loop(DDTRACE_UNUSED VALUE _self, VALUE instance) {
534
593
  // @ivoanjo: I suspect this will never happen, but the cost of getting it wrong is really high (VM terminates) so this
535
594
  // is a just-in-case situation.
536
595
  //
537
- // Note 2: This can raise exceptions as well, so make sure that all cleanups are done by the time we get here.
538
- replace_sigprof_signal_handler_with_empty_handler(handle_sampling_signal);
596
+ // Note 2: If exception_state is set, we have a pending exception that we'll re-raise below.
597
+ // In that case, we don't want replace_sigprof_signal_handler_with_empty_handler to raise another exception.
598
+ replace_sigprof_signal_handler_with_empty_handler(handle_sampling_signal, !exception_state);
539
599
 
540
600
  // Ensure that instance is not garbage collected while the native sampling loop is running; this is probably not needed, but just in case
541
601
  RB_GC_GUARD(instance);
@@ -551,22 +611,24 @@ static VALUE _native_stop(DDTRACE_UNUSED VALUE _self, VALUE self_instance, VALUE
551
611
 
552
612
  state->stop_thread = worker_thread;
553
613
 
554
- return stop(self_instance, /* optional_exception: */ Qnil);
614
+ return stop(self_instance, Qnil, NULL);
555
615
  }
556
616
 
557
- static void stop_state(cpu_and_wall_time_worker_state *state, VALUE optional_exception) {
617
+ // When providing an `optional_exception`, `optional_exception_during_operation` should be provided as well
618
+ static void stop_state(cpu_and_wall_time_worker_state *state, VALUE optional_exception, const char *optional_exception_during_operation) {
558
619
  atomic_store(&state->should_run, false);
559
620
  state->failure_exception = optional_exception;
621
+ state->failure_exception_during_operation = optional_exception_during_operation;
560
622
 
561
623
  // Disable the tracepoints as soon as possible, so the VM doesn't keep on calling them
562
624
  disable_tracepoints(state);
563
625
  }
564
626
 
565
- static VALUE stop(VALUE self_instance, VALUE optional_exception) {
627
+ static VALUE stop(VALUE self_instance, VALUE optional_exception, const char *optional_exception_during_operation) {
566
628
  cpu_and_wall_time_worker_state *state;
567
629
  TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
568
630
 
569
- stop_state(state, optional_exception);
631
+ stop_state(state, optional_exception, optional_exception_during_operation);
570
632
 
571
633
  return Qtrue;
572
634
  }
@@ -589,25 +651,30 @@ static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED si
589
651
  return;
590
652
  }
591
653
 
592
- // We implicitly assume there can be no concurrent nor nested calls to handle_sampling_signal because
593
- // a) we get triggered using SIGPROF, and the docs state a second SIGPROF will not interrupt an existing one
654
+ // We assume there can be no concurrent nor nested calls to handle_sampling_signal because
655
+ // 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)
594
656
  // 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
595
657
  // because it will not have the global VM lock
596
658
 
597
- // Note: rb_postponed_job_register_one ensures that if there's a previous sample_from_postponed_job queued for execution
598
- // then we will not queue a second one. It does this by doing a linear scan on the existing jobs; in the future we
599
- // may want to implement that check ourselves.
600
-
601
659
  state->stats.signal_handler_enqueued_sample++;
602
660
 
603
- // Note: If we ever want to get rid of rb_postponed_job_register_one, remember not to clobber Ruby exceptions, as
604
- // this function does this helpful job for us now -- https://github.com/ruby/ruby/commit/a98e343d39c4d7bf1e2190b076720f32d9f298b3.
661
+ bool sample_from_signal_handler =
662
+ state->sighandler_sampling_enabled &&
663
+ // Don't sample if we're already in the middle of processing a sample
664
+ !state->during_sample;
665
+
666
+ if (sample_from_signal_handler) {
667
+ // Buffer current stack trace. Note that this will not actually record the sample, for that we still need to wait
668
+ // until the postponed job below gets run.
669
+ bool prepared = thread_context_collector_prepare_sample_inside_signal_handler();
670
+
671
+ if (prepared) state->stats.signal_handler_prepared_sample++;
672
+ }
673
+
605
674
  #ifndef NO_POSTPONED_TRIGGER // Ruby 3.3+
606
675
  rb_postponed_job_trigger(sample_from_postponed_job_handle);
607
- state->stats.postponed_job_success++; // Always succeeds
608
676
  #else
609
-
610
- // This is a workaround for https://bugs.ruby-lang.org/issues/19991 (for Ruby < 3.3)
677
+ // Passing in `gc_finalize_deferred_workaround` is a workaround for https://bugs.ruby-lang.org/issues/19991 (for Ruby < 3.3)
611
678
  //
612
679
  // TL;DR the `rb_postponed_job_register_one` API is not atomic (which is why it got replaced by `rb_postponed_job_trigger`)
613
680
  // and in rare cases can cause VM crashes.
@@ -631,20 +698,7 @@ static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED si
631
698
  //
632
699
  // Thus, our workaround is simple: we pass in objspace as our argument, just in case the clobbering happens.
633
700
  // In the happy path, we never use this argument so it makes no difference. In the buggy path, we avoid crashing the VM.
634
- int result = rb_postponed_job_register(0, sample_from_postponed_job, gc_finalize_deferred_workaround /* instead of NULL */);
635
-
636
- // Officially, the result of rb_postponed_job_register_one is documented as being opaque, but in practice it does not
637
- // seem to have changed between Ruby 2.3 and 3.2, and so we track it as a debugging mechanism
638
- switch (result) {
639
- case 0:
640
- state->stats.postponed_job_full++; break;
641
- case 1:
642
- state->stats.postponed_job_success++; break;
643
- case 2:
644
- state->stats.postponed_job_skipped_already_existed++; break;
645
- default:
646
- state->stats.postponed_job_unknown_result++;
647
- }
701
+ rb_postponed_job_register(0, sample_from_postponed_job, gc_finalize_deferred_workaround /* instead of NULL */);
648
702
  #endif
649
703
  }
650
704
 
@@ -652,7 +706,7 @@ static void handle_sampling_signal(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED si
652
706
  static void *run_sampling_trigger_loop(void *state_ptr) {
653
707
  cpu_and_wall_time_worker_state *state = (cpu_and_wall_time_worker_state *) state_ptr;
654
708
 
655
- uint64_t minimum_time_between_signals = MILLIS_AS_NS(10);
709
+ uint64_t minimum_time_between_signals = MILLIS_AS_NS(state->cpu_sampling_interval_ms);
656
710
 
657
711
  while (atomic_load(&state->should_run)) {
658
712
  state->stats.trigger_sample_attempts++;
@@ -674,6 +728,10 @@ static void *run_sampling_trigger_loop(void *state_ptr) {
674
728
  // we're doing this, so we may still not signal the correct thread from time to time, but our signal handler
675
729
  // includes a check to see if it got called in the right thread
676
730
  state->stats.interrupt_thread_attempts++;
731
+
732
+ // Pick up any last-minute attempts to stop before we send the signal
733
+ if (!atomic_load(&state->should_run)) return NULL;
734
+
677
735
  pthread_kill(owner.owner, SIGPROF);
678
736
  } else {
679
737
  if (state->skip_idle_samples_for_testing) {
@@ -701,7 +759,10 @@ static void *run_sampling_trigger_loop(void *state_ptr) {
701
759
  // `dynamic_sampling_rate_get_sleep` may have changed while the above sleep was ongoing.
702
760
  uint64_t extra_sleep =
703
761
  dynamic_sampling_rate_get_sleep(&state->cpu_dynamic_sampling_rate, monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE));
704
- if (state->dynamic_sampling_rate_enabled && extra_sleep > 0) sleep_for(extra_sleep);
762
+ if (state->dynamic_sampling_rate_enabled && extra_sleep > 0) {
763
+ state->stats.trigger_sample_extra_sleep++;
764
+ sleep_for(extra_sleep);
765
+ }
705
766
  }
706
767
 
707
768
  return NULL; // Unused
@@ -714,6 +775,8 @@ static void interrupt_sampling_trigger_loop(void *state_ptr) {
714
775
  atomic_store(&state->should_run, false);
715
776
  }
716
777
 
778
+ // Note: If we ever want to get rid of the postponed job execution, remember not to clobber Ruby exceptions, as
779
+ // this function does this helpful job for us now -- https://github.com/ruby/ruby/commit/a98e343d39c4d7bf1e2190b076720f32d9f298b3.
717
780
  static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused) {
718
781
  cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
719
782
 
@@ -726,12 +789,17 @@ static void sample_from_postponed_job(DDTRACE_UNUSED void *_unused) {
726
789
  return; // We're not on the main Ractor; we currently don't support profiling non-main Ractors
727
790
  }
728
791
 
729
- state->during_sample = true;
792
+ during_sample_enter(state);
730
793
 
731
794
  // Rescue against any exceptions that happen during sampling
732
- safely_call(rescued_sample_from_postponed_job, state->self_instance, state->self_instance);
795
+ safely_call(
796
+ rescued_sample_from_postponed_job,
797
+ state->self_instance,
798
+ state->self_instance,
799
+ handle_sampling_failure_rescued_sample_from_postponed_job
800
+ );
733
801
 
734
- state->during_sample = false;
802
+ during_sample_exit(state);
735
803
  }
736
804
 
737
805
  static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
@@ -747,8 +815,7 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
747
815
 
748
816
  state->stats.cpu_sampled++;
749
817
 
750
- VALUE profiler_overhead_stack_thread = state->owner_thread; // Used to attribute profiler overhead to a different stack
751
- thread_context_collector_sample(state->thread_context_collector_instance, wall_time_ns_before_sample, profiler_overhead_stack_thread);
818
+ thread_context_collector_sample(state->thread_context_collector_instance, wall_time_ns_before_sample);
752
819
 
753
820
  long wall_time_ns_after_sample = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
754
821
  long delta_ns = wall_time_ns_after_sample - wall_time_ns_before_sample;
@@ -766,11 +833,6 @@ static VALUE rescued_sample_from_postponed_job(VALUE self_instance) {
766
833
  return Qnil;
767
834
  }
768
835
 
769
- static VALUE handle_sampling_failure(VALUE self_instance, VALUE exception) {
770
- stop(self_instance, exception);
771
- return Qnil;
772
- }
773
-
774
836
  // This method exists only to enable testing Datadog::Profiling::Collectors::CpuAndWallTimeWorker behavior using RSpec.
775
837
  // It SHOULD NOT be used for other purposes.
776
838
  static VALUE _native_current_sigprof_signal_handler(DDTRACE_UNUSED VALUE self) {
@@ -809,22 +871,19 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
809
871
 
810
872
  if (state->gvl_profiling_enabled) {
811
873
  #ifndef NO_GVL_INSTRUMENTATION
812
- #ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS
813
- gvl_profiling_state_thread_tracking_workaround();
814
- #endif
815
-
816
874
  state->gvl_profiling_hook = rb_internal_thread_add_event_hook(
817
875
  on_gvl_event,
818
876
  (
819
877
  // For now we're only asking for these events, even though there's more
820
878
  // (e.g. check docs or gvl-tracing gem)
879
+ RUBY_INTERNAL_THREAD_EVENT_SUSPENDED | /* released gvl */
821
880
  RUBY_INTERNAL_THREAD_EVENT_READY /* waiting for gvl */ |
822
881
  RUBY_INTERNAL_THREAD_EVENT_RESUMED /* running/runnable */
823
882
  ),
824
883
  NULL
825
884
  );
826
885
  #else
827
- rb_raise(rb_eArgError, "GVL profiling is not supported in this Ruby version");
886
+ raise_error(rb_eArgError, "GVL profiling is not supported in this Ruby version");
828
887
  #endif
829
888
  }
830
889
 
@@ -836,6 +895,8 @@ static VALUE release_gvl_and_run_sampling_trigger_loop(VALUE instance) {
836
895
  // If we stopped sampling due to an exception, re-raise it (now in the worker thread)
837
896
  if (state->failure_exception != Qnil) rb_exc_raise(state->failure_exception);
838
897
 
898
+ thread_context_collector_profiler_internal_thread_done(state->thread_context_collector_instance);
899
+
839
900
  return Qnil;
840
901
  }
841
902
 
@@ -847,6 +908,15 @@ static VALUE _native_is_running(DDTRACE_UNUSED VALUE self, VALUE instance) {
847
908
  return (state != NULL && is_thread_alive(state->owner_thread) && state->self_instance == instance) ? Qtrue : Qfalse;
848
909
  }
849
910
 
911
+ static VALUE _native_failure_exception_during_operation(DDTRACE_UNUSED VALUE self, VALUE instance) {
912
+ cpu_and_wall_time_worker_state *state;
913
+ TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
914
+
915
+ if (state->failure_exception_during_operation == NULL) return Qnil;
916
+
917
+ return rb_str_new_cstr(state->failure_exception_during_operation);
918
+ }
919
+
850
920
  static void testing_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext) {
851
921
  /* Does nothing on purpose */
852
922
  }
@@ -937,16 +1007,26 @@ static void after_gc_from_postponed_job(DDTRACE_UNUSED void *_unused) {
937
1007
  return; // We're not on the main Ractor; we currently don't support profiling non-main Ractors
938
1008
  }
939
1009
 
940
- state->during_sample = true;
1010
+ during_sample_enter(state);
941
1011
 
942
- safely_call(thread_context_collector_sample_after_gc, state->thread_context_collector_instance, state->self_instance);
1012
+ safely_call(
1013
+ thread_context_collector_sample_after_gc,
1014
+ state->thread_context_collector_instance,
1015
+ state->self_instance,
1016
+ handle_sampling_failure_thread_context_collector_sample_after_gc
1017
+ );
943
1018
 
944
- state->during_sample = false;
1019
+ during_sample_exit(state);
945
1020
  }
946
1021
 
947
1022
  // Equivalent to Ruby begin/rescue call, where we call a C function and jump to the exception handler if an
948
1023
  // exception gets raised within
949
- static VALUE safely_call(VALUE (*function_to_call_safely)(VALUE), VALUE function_to_call_safely_arg, VALUE instance) {
1024
+ static VALUE safely_call(
1025
+ VALUE (*function_to_call_safely)(VALUE),
1026
+ VALUE function_to_call_safely_arg,
1027
+ VALUE instance,
1028
+ VALUE (*handle_sampling_failure)(VALUE, VALUE)
1029
+ ) {
950
1030
  VALUE exception_handler_function_arg = instance;
951
1031
  return rb_rescue2(
952
1032
  function_to_call_safely,
@@ -980,6 +1060,9 @@ static VALUE _native_simulate_sample_from_postponed_job(DDTRACE_UNUSED VALUE sel
980
1060
  //
981
1061
  // In the future, if we add more other components with tracepoints, we will need to coordinate stopping all such
982
1062
  // tracepoints before doing the other cleaning steps.
1063
+ //
1064
+ // Note that tests call this method directly in the same process without forking,
1065
+ // and in such a case non-current Threads keep running.
983
1066
  static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE instance) {
984
1067
  cpu_and_wall_time_worker_state *state;
985
1068
  TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
@@ -1016,14 +1099,12 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
1016
1099
  VALUE stats_as_hash = rb_hash_new();
1017
1100
  VALUE arguments[] = {
1018
1101
  ID2SYM(rb_intern("trigger_sample_attempts")), /* => */ UINT2NUM(state->stats.trigger_sample_attempts),
1102
+ ID2SYM(rb_intern("trigger_sample_extra_sleep")), /* => */ UINT2NUM(state->stats.trigger_sample_extra_sleep),
1019
1103
  ID2SYM(rb_intern("trigger_simulated_signal_delivery_attempts")), /* => */ UINT2NUM(state->stats.trigger_simulated_signal_delivery_attempts),
1020
1104
  ID2SYM(rb_intern("simulated_signal_delivery")), /* => */ UINT2NUM(state->stats.simulated_signal_delivery),
1021
1105
  ID2SYM(rb_intern("signal_handler_enqueued_sample")), /* => */ UINT2NUM(state->stats.signal_handler_enqueued_sample),
1106
+ ID2SYM(rb_intern("signal_handler_prepared_sample")), /* => */ UINT2NUM(state->stats.signal_handler_prepared_sample),
1022
1107
  ID2SYM(rb_intern("signal_handler_wrong_thread")), /* => */ UINT2NUM(state->stats.signal_handler_wrong_thread),
1023
- ID2SYM(rb_intern("postponed_job_skipped_already_existed")), /* => */ UINT2NUM(state->stats.postponed_job_skipped_already_existed),
1024
- ID2SYM(rb_intern("postponed_job_success")), /* => */ UINT2NUM(state->stats.postponed_job_success),
1025
- ID2SYM(rb_intern("postponed_job_full")), /* => */ UINT2NUM(state->stats.postponed_job_full),
1026
- ID2SYM(rb_intern("postponed_job_unknown_result")), /* => */ UINT2NUM(state->stats.postponed_job_unknown_result),
1027
1108
  ID2SYM(rb_intern("interrupt_thread_attempts")), /* => */ UINT2NUM(state->stats.interrupt_thread_attempts),
1028
1109
 
1029
1110
  // CPU Stats
@@ -1053,8 +1134,12 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance) {
1053
1134
  ID2SYM(rb_intern("gvl_sampling_time_ns_max")), /* => */ RUBY_NUM_OR_NIL(state->stats.gvl_sampling_time_ns_max, > 0, ULL2NUM),
1054
1135
  ID2SYM(rb_intern("gvl_sampling_time_ns_total")), /* => */ RUBY_NUM_OR_NIL(state->stats.gvl_sampling_time_ns_total, > 0, ULL2NUM),
1055
1136
  ID2SYM(rb_intern("gvl_sampling_time_ns_avg")), /* => */ RUBY_AVG_OR_NIL(state->stats.gvl_sampling_time_ns_total, state->stats.after_gvl_running),
1137
+ ID2SYM(rb_intern("gvl_waiting_time_ns_total")), /* => */ state->gvl_profiling_enabled ? ULL2NUM(state->stats.vm_metrics.gvl_waiting_time_ns_total) : Qnil,
1056
1138
  };
1057
1139
  for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
1140
+
1141
+ thread_context_collector_stats(state->thread_context_collector_instance, stats_as_hash);
1142
+
1058
1143
  return stats_as_hash;
1059
1144
  }
1060
1145
 
@@ -1062,6 +1147,7 @@ static VALUE _native_stats_reset_not_thread_safe(DDTRACE_UNUSED VALUE self, VALU
1062
1147
  cpu_and_wall_time_worker_state *state;
1063
1148
  TypedData_Get_Struct(instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1064
1149
  reset_stats_not_thread_safe(state);
1150
+ thread_context_collector_stats_reset_not_thread_safe(state->thread_context_collector_instance);
1065
1151
  return Qnil;
1066
1152
  }
1067
1153
 
@@ -1073,8 +1159,7 @@ void *simulate_sampling_signal_delivery(DDTRACE_UNUSED void *_unused) {
1073
1159
 
1074
1160
  state->stats.simulated_signal_delivery++;
1075
1161
 
1076
- // @ivoanjo: We could instead directly call sample_from_postponed_job, but I chose to go through the signal handler
1077
- // so that the simulated case is as close to the original one as well (including any metrics increases, etc).
1162
+ // `handle_sampling_signal` does a few things extra on top of `sample_from_postponed_job` so that's why we don't shortcut here
1078
1163
  handle_sampling_signal(0, NULL, NULL);
1079
1164
 
1080
1165
  return NULL; // Unused
@@ -1094,6 +1179,7 @@ static void reset_stats_not_thread_safe(cpu_and_wall_time_worker_state *state) {
1094
1179
  .cpu_sampling_time_ns_min = UINT64_MAX,
1095
1180
  .allocation_sampling_time_ns_min = UINT64_MAX,
1096
1181
  .gvl_sampling_time_ns_min = UINT64_MAX,
1182
+ // Other fields are reset to 0
1097
1183
  };
1098
1184
  }
1099
1185
 
@@ -1126,7 +1212,7 @@ static VALUE _native_allocation_count(DDTRACE_UNUSED VALUE self) {
1126
1212
  #define HANDLE_CLOCK_FAILURE(call) ({ \
1127
1213
  long _result = (call); \
1128
1214
  if (_result == 0) { \
1129
- delayed_error(state, ERR_CLOCK_FAIL); \
1215
+ delayed_error_clock_failure(state); \
1130
1216
  return; \
1131
1217
  } \
1132
1218
  _result; \
@@ -1171,13 +1257,22 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
1171
1257
  return;
1172
1258
  }
1173
1259
 
1260
+ VALUE current_thread = rb_thread_current();
1261
+
1174
1262
  // If Ruby is in the middle of raising an exception, we don't want to try to sample. This is because if we accidentally
1175
1263
  // trigger an exception inside the profiler code, bad things will happen (specifically, Ruby will try to kill off the
1176
1264
  // thread even though we may try to catch the exception).
1177
1265
  //
1178
1266
  // Note that "in the middle of raising an exception" means the exception itself has already been allocated.
1179
1267
  // What's getting allocated now is probably the backtrace objects (@ivoanjo or at least that's what I've observed)
1180
- if (is_raised_flag_set(rb_thread_current())) {
1268
+ if (is_raised_flag_set(current_thread)) {
1269
+ return;
1270
+ }
1271
+
1272
+ per_thread_context *thread_context = get_per_thread_context(current_thread);
1273
+ if (!thread_context) {
1274
+ // Context is created eagerly via on_thread_begin_event, so this should not normally be NULL.
1275
+ // We keep the guard since we can't allocate here (inside on_newobj_event).
1181
1276
  return;
1182
1277
  }
1183
1278
 
@@ -1207,19 +1302,20 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
1207
1302
  &state->allocation_sampler, HANDLE_CLOCK_FAILURE(monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE))
1208
1303
  );
1209
1304
 
1210
- // @ivoanjo: Strictly speaking, this is not needed because Ruby should not call the same tracepoint while a previous
1211
- // invocation is still pending, (e.g. it wouldn't call `on_newobj_event` while it's already running), but I decided
1212
- // to keep this here for consistency -- every call to the thread context (other than the special gc calls which are
1213
- // defined as not being able to allocate) sets this.
1214
- state->during_sample = true;
1305
+ during_sample_enter(state);
1215
1306
 
1216
1307
  // Rescue against any exceptions that happen during sampling
1217
- safely_call(rescued_sample_allocation, Qnil, state->self_instance);
1308
+ safely_call(
1309
+ rescued_sample_allocation,
1310
+ (VALUE) thread_context,
1311
+ state->self_instance,
1312
+ handle_sampling_failure_rescued_sample_allocation
1313
+ );
1218
1314
 
1219
1315
  if (state->dynamic_sampling_rate_enabled) {
1220
1316
  long now = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
1221
1317
  if (now == 0) {
1222
- delayed_error(state, ERR_CLOCK_FAIL);
1318
+ delayed_error_clock_failure(state);
1223
1319
  // NOTE: Not short-circuiting here to make sure cleanup happens
1224
1320
  }
1225
1321
  uint64_t sampling_time_ns = discrete_dynamic_sampler_after_sample(&state->allocation_sampler, now);
@@ -1232,7 +1328,7 @@ static void on_newobj_event(DDTRACE_UNUSED VALUE unused1, DDTRACE_UNUSED void *u
1232
1328
 
1233
1329
  state->stats.allocation_sampled++;
1234
1330
 
1235
- state->during_sample = false;
1331
+ during_sample_exit(state);
1236
1332
  }
1237
1333
 
1238
1334
  static void disable_tracepoints(cpu_and_wall_time_worker_state *state) {
@@ -1263,7 +1359,8 @@ static VALUE _native_with_blocked_sigprof(DDTRACE_UNUSED VALUE self) {
1263
1359
  }
1264
1360
  }
1265
1361
 
1266
- static VALUE rescued_sample_allocation(DDTRACE_UNUSED VALUE unused) {
1362
+ static VALUE rescued_sample_allocation(VALUE arg) {
1363
+ per_thread_context *thread_context = (per_thread_context*) arg;
1267
1364
  cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1268
1365
 
1269
1366
  // This should not happen in a normal situation because on_newobj_event already checked for this, but just in case...
@@ -1282,20 +1379,33 @@ static VALUE rescued_sample_allocation(DDTRACE_UNUSED VALUE unused) {
1282
1379
  // To control bias from sampling, we clamp the maximum weight attributed to a single allocation sample. This avoids
1283
1380
  // assigning a very large number to a sample, if for instance the dynamic sampling mechanism chose a really big interval.
1284
1381
  unsigned int weight = allocations_since_last_sample > MAX_ALLOC_WEIGHT ? MAX_ALLOC_WEIGHT : (unsigned int) allocations_since_last_sample;
1285
- thread_context_collector_sample_allocation(state->thread_context_collector_instance, weight, new_object);
1382
+ bool needs_after_allocation = thread_context_collector_sample_allocation(state->thread_context_collector_instance, thread_context, weight, new_object);
1286
1383
  // ...but we still represent the skipped samples in the profile, thus the data will account for all allocations.
1287
1384
  if (weight < allocations_since_last_sample) {
1288
1385
  uint32_t skipped_samples = (uint32_t) uint64_min_of(allocations_since_last_sample - weight, UINT32_MAX);
1289
1386
  thread_context_collector_sample_skipped_allocation_samples(state->thread_context_collector_instance, skipped_samples);
1290
1387
  }
1291
1388
 
1389
+ if (needs_after_allocation) {
1390
+ #ifndef NO_POSTPONED_TRIGGER
1391
+ rb_postponed_job_trigger(after_allocation_from_postponed_job_handle);
1392
+ #else
1393
+ // Not needed on legacy rubies
1394
+ #endif
1395
+ }
1396
+
1292
1397
  // Return a dummy VALUE because we're called from rb_rescue2 which requires it
1293
1398
  return Qnil;
1294
1399
  }
1295
1400
 
1296
1401
  static void delayed_error(cpu_and_wall_time_worker_state *state, const char *error) {
1297
1402
  // If we can't raise an immediate exception at the calling site, use the asynchronous flow through the main worker loop.
1298
- stop_state(state, rb_exc_new_cstr(rb_eRuntimeError, error));
1403
+ stop_state(state, rb_exc_new_cstr(rb_eRuntimeError, error), "delayed_error");
1404
+ }
1405
+
1406
+ static void delayed_error_clock_failure(cpu_and_wall_time_worker_state *state) {
1407
+ // If we can't raise an immediate exception at the calling site, use the asynchronous flow through the main worker loop.
1408
+ stop_state(state, rb_exc_new_cstr(clock_failure_exception_class, "failed to get clock time"), "delayed_error_clock_failure");
1299
1409
  }
1300
1410
 
1301
1411
  static VALUE _native_delayed_error(DDTRACE_UNUSED VALUE self, VALUE instance, VALUE error_msg) {
@@ -1326,44 +1436,65 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
1326
1436
  #ifndef NO_GVL_INSTRUMENTATION
1327
1437
  static void on_gvl_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *event_data, DDTRACE_UNUSED void *_unused) {
1328
1438
  // Be very careful about touching the `state` here or doing anything at all:
1329
- // This function gets called without the GVL, and potentially from background Ractors!
1330
- //
1331
- // In fact, the `target_thread` that this event is about may not even be the current thread. (So be careful with thread locals that
1332
- // are not directly tied to the `target_thread` object and the like)
1333
- gvl_profiling_thread target_thread = thread_from_event(event_data);
1439
+ // This function gets called without the GVL, and potentially from non-main Ractors!
1440
+
1441
+ // The thread that this event is about may not be the current thread
1442
+ // (as documented on rb_internal_thread_add_event_hook(), and this is notably the case for READY on Ruby 4.0),
1443
+ // so be careful with native thread locals that are not directly tied to the thread object and the like.
1444
+
1445
+ // On Ruby 3.2 the event does not carry the thread, but all events always fire on the event thread on Ruby 3.2.
1446
+ // However, during early thread startup rb_thread_current() can crash because the execution context (Fiber) isn't
1447
+ // stored in TLS yet; ruby_native_thread_p() guards against this.
1448
+ #ifdef HAVE_RUBY_THREAD_STORAGE_API
1449
+ VALUE target_thread = event_data->thread;
1450
+ #else
1451
+ if (!ruby_native_thread_p()) return;
1452
+ VALUE target_thread = rb_thread_current();
1453
+ #endif
1334
1454
 
1335
- if (event_id == RUBY_INTERNAL_THREAD_EVENT_READY) { /* waiting for gvl */
1336
- thread_context_collector_on_gvl_waiting(target_thread);
1455
+ per_thread_context* thread_context = get_per_thread_context(target_thread);
1456
+ if (!thread_context) return;
1457
+ // If non-NULL the thread is profiled and from the main Ractor
1458
+
1459
+ if (event_id == RUBY_INTERNAL_THREAD_EVENT_SUSPENDED) { /* released gvl */
1460
+ thread_context_collector_on_gvl_released(thread_context);
1461
+ } else if (event_id == RUBY_INTERNAL_THREAD_EVENT_READY) { /* waiting for gvl */
1462
+ thread_context_collector_on_gvl_waiting(thread_context);
1337
1463
  } else if (event_id == RUBY_INTERNAL_THREAD_EVENT_RESUMED) { /* running/runnable */
1338
- // Interesting note: A RUBY_INTERNAL_THREAD_EVENT_RESUMED is guaranteed to be called with the GVL being acquired.
1339
- // (And... I think target_thread will be == rb_thread_current()?)
1464
+ // Interesting note: A RUBY_INTERNAL_THREAD_EVENT_RESUMED is guaranteed to be called with the GVL being acquired
1465
+ // and on the event thread.
1466
+ // However, on_gvl_event() is called while holding the scheduler lock, so we do as little work as possible here,
1467
+ // and perform the sample in a postponed_job.
1468
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1469
+ if (state == NULL) return; // This should not happen, but just in case...
1470
+
1471
+ // on_gvl_running prepares a stack sample so we need to gate it with `during_sample_enter` to avoid a
1472
+ // SIGPROF signal coming in during that function and potentially causing a corrupted final state.
1340
1473
  //
1341
- // But we're not sure if we're on the main Ractor yet. The thread context collector actually can actually help here:
1342
- // it tags threads it's tracking, so if a thread is tagged then by definition we know that thread belongs to the main
1343
- // Ractor. Thus, if we get a ON_GVL_RUNNING_UNKNOWN result we shouldn't touch any state, but otherwise we're good to go.
1474
+ // TODO: Currently, the sample prepared in on_gvl_running can still be clobbered if the signal handler runs
1475
+ // after `during_sample_exit` but before `after_gvl_running_from_postponed_job` gets to run. We'll need to fix
1476
+ // that next.
1477
+ during_sample_enter(state);
1344
1478
 
1345
- #ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS
1346
- target_thread = gvl_profiling_state_maybe_initialize();
1347
- #endif
1479
+ on_gvl_running_result result = thread_context_collector_on_gvl_running(state->thread_context_collector_instance, target_thread, thread_context);
1348
1480
 
1349
- on_gvl_running_result result = thread_context_collector_on_gvl_running(target_thread);
1481
+ during_sample_exit(state);
1482
+
1483
+ if (result.waiting_for_gvl_duration_ns > 0) {
1484
+ state->stats.vm_metrics.gvl_waiting_time_ns_total += (uint64_t) result.waiting_for_gvl_duration_ns;
1485
+ }
1350
1486
 
1351
- if (result == ON_GVL_RUNNING_SAMPLE) {
1487
+ if (result.action == ON_GVL_RUNNING_SAMPLE) {
1352
1488
  #ifndef NO_POSTPONED_TRIGGER
1353
1489
  rb_postponed_job_trigger(after_gvl_running_from_postponed_job_handle);
1354
1490
  #else
1355
1491
  rb_postponed_job_register_one(0, after_gvl_running_from_postponed_job, NULL);
1356
1492
  #endif
1357
- } else if (result == ON_GVL_RUNNING_DONT_SAMPLE) {
1358
- cpu_and_wall_time_worker_state *state = active_sampler_instance_state; // Read from global variable, see "sampler global state safety" note above
1359
-
1360
- if (state == NULL) return; // This should not happen, but just in case...
1361
-
1493
+ } else if (result.action == ON_GVL_RUNNING_DONT_SAMPLE) {
1362
1494
  state->stats.gvl_dont_sample++;
1363
1495
  }
1364
1496
  } else {
1365
- // This is a very delicate time and it's hard for us to raise an exception so let's at least complain to stderr
1366
- fprintf(stderr, "[ddtrace] Unexpected value in on_gvl_event (%d)\n", event_id);
1497
+ rb_bug("[ddtrace] Unexpected value in on_gvl_event (%d)\n", event_id);
1367
1498
  }
1368
1499
  }
1369
1500
 
@@ -1373,12 +1504,17 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
1373
1504
  // This can potentially happen if the CpuAndWallTimeWorker was stopped while the postponed job was waiting to be executed; nothing to do
1374
1505
  if (state == NULL) return;
1375
1506
 
1376
- state->during_sample = true;
1507
+ during_sample_enter(state);
1377
1508
 
1378
1509
  // Rescue against any exceptions that happen during sampling
1379
- safely_call(rescued_after_gvl_running_from_postponed_job, state->self_instance, state->self_instance);
1510
+ safely_call(
1511
+ rescued_after_gvl_running_from_postponed_job,
1512
+ state->self_instance,
1513
+ state->self_instance,
1514
+ handle_sampling_failure_rescued_after_gvl_running_from_postponed_job
1515
+ );
1380
1516
 
1381
- state->during_sample = false;
1517
+ during_sample_exit(state);
1382
1518
  }
1383
1519
 
1384
1520
  static VALUE rescued_after_gvl_running_from_postponed_job(VALUE self_instance) {
@@ -1409,8 +1545,90 @@ static VALUE _native_resume_signals(DDTRACE_UNUSED VALUE self) {
1409
1545
 
1410
1546
  return state->gvl_profiling_hook != NULL ? Qtrue : Qfalse;
1411
1547
  }
1548
+
1549
+ static VALUE handle_sampling_failure_rescued_after_gvl_running_from_postponed_job(VALUE self_instance, VALUE exception) {
1550
+ stop(self_instance, exception, "rescued_after_gvl_running_from_postponed_job");
1551
+ return Qnil;
1552
+ }
1412
1553
  #else
1413
1554
  static VALUE _native_gvl_profiling_hook_active(DDTRACE_UNUSED VALUE self, DDTRACE_UNUSED VALUE instance) {
1414
1555
  return Qfalse;
1415
1556
  }
1416
1557
  #endif
1558
+
1559
+ static VALUE handle_sampling_failure_rescued_sample_from_postponed_job(VALUE self_instance, VALUE exception) {
1560
+ stop(self_instance, exception, "rescued_sample_from_postponed_job");
1561
+ return Qnil;
1562
+ }
1563
+
1564
+ static VALUE handle_sampling_failure_thread_context_collector_sample_after_gc(VALUE self_instance, VALUE exception) {
1565
+ stop(self_instance, exception, "thread_context_collector_sample_after_gc");
1566
+ return Qnil;
1567
+ }
1568
+
1569
+ static VALUE handle_sampling_failure_rescued_sample_allocation(VALUE self_instance, VALUE exception) {
1570
+ stop(self_instance, exception, "rescued_sample_allocation");
1571
+ return Qnil;
1572
+ }
1573
+
1574
+ static VALUE handle_sampling_failure_rescued_after_allocation(VALUE self_instance, VALUE exception) {
1575
+ stop(self_instance, exception, "rescued_after_allocation");
1576
+ return Qnil;
1577
+ }
1578
+
1579
+ static VALUE rescued_after_allocation(VALUE self_instance) {
1580
+ cpu_and_wall_time_worker_state *state;
1581
+ TypedData_Get_Struct(self_instance, cpu_and_wall_time_worker_state, &cpu_and_wall_time_worker_typed_data, state);
1582
+
1583
+ thread_context_collector_after_allocation(state->thread_context_collector_instance);
1584
+
1585
+ // Return a dummy VALUE because we're called from rb_rescue2 which requires it
1586
+ return Qnil;
1587
+ }
1588
+
1589
+ // This postponed job callback is used to finalize heap allocation recordings on Ruby 4+.
1590
+ // During on_newobj_event, calling rb_obj_id() is unsafe because it mutates the object.
1591
+ // So we defer getting the object_id until after the event completes.
1592
+ #pragma GCC diagnostic push
1593
+ #pragma GCC diagnostic ignored "-Wunused-function" // This is only used for some Rubies, but we want to build on all to make it easier to dev
1594
+ static void after_allocation_from_postponed_job(DDTRACE_UNUSED void *_unused) {
1595
+ cpu_and_wall_time_worker_state *state = active_sampler_instance_state;
1596
+
1597
+ if (state == NULL || !ddtrace_rb_ractor_main_p()) return;
1598
+
1599
+ // Protect against nested operations
1600
+ if (state->during_sample) return;
1601
+
1602
+ during_sample_enter(state);
1603
+
1604
+ // NOTE: We're not updating the allocation_sampler here.
1605
+ // This means work done in this function isn't accounted for as profiler overhead.
1606
+ // This is acceptable as the amount of work done here is expected to be small.
1607
+ safely_call(
1608
+ rescued_after_allocation,
1609
+ state->self_instance,
1610
+ state->self_instance,
1611
+ handle_sampling_failure_rescued_after_allocation
1612
+ );
1613
+
1614
+ during_sample_exit(state);
1615
+ }
1616
+ #pragma GCC diagnostic pop
1617
+
1618
+ static inline void during_sample_enter(cpu_and_wall_time_worker_state* state) {
1619
+ // Tell the compiler it's not allowed to reorder the `during_sample` flag with anything that happens after.
1620
+ //
1621
+ // In a few cases, we may be checking this flag from a signal handler, so we need to make sure the compiler didn't
1622
+ // get clever and reordered things in such a way that makes us miss the flag update.
1623
+ //
1624
+ // See https://github.com/ruby/ruby/pull/11036 for a similar change made to the Ruby VM with more context.
1625
+ state->during_sample = true;
1626
+ atomic_signal_fence(memory_order_seq_cst);
1627
+ }
1628
+
1629
+ static inline void during_sample_exit(cpu_and_wall_time_worker_state* state) {
1630
+ // See `during_sample_enter` for more context; in this case we set the fence before to make sure anything that
1631
+ // happens before the fence is not reordered with the flag update.
1632
+ atomic_signal_fence(memory_order_seq_cst);
1633
+ state->during_sample = false;
1634
+ }