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