datadog 2.4.0 → 2.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1070) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1261 -1
  3. data/README.md +3 -1
  4. data/bin/ddprofrb +4 -4
  5. data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
  6. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
  7. data/ext/datadog_profiling_native_extension/clock_id.h +11 -3
  8. data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +66 -0
  9. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +3 -2
  10. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +653 -212
  11. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +5 -5
  12. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
  13. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
  14. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +33 -20
  15. data/ext/datadog_profiling_native_extension/collectors_stack.c +409 -113
  16. data/ext/datadog_profiling_native_extension/collectors_stack.h +41 -5
  17. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +1208 -623
  18. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +33 -8
  19. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +240 -0
  20. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +85 -16
  21. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +47 -1
  22. data/ext/datadog_profiling_native_extension/encoded_profile.c +80 -0
  23. data/ext/datadog_profiling_native_extension/encoded_profile.h +8 -0
  24. data/ext/datadog_profiling_native_extension/extconf.rb +99 -120
  25. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +6 -43
  26. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -55
  27. data/ext/datadog_profiling_native_extension/heap_recorder.c +762 -608
  28. data/ext/datadog_profiling_native_extension/heap_recorder.h +67 -28
  29. data/ext/datadog_profiling_native_extension/http_transport.c +108 -144
  30. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +18 -15
  31. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -10
  32. data/ext/datadog_profiling_native_extension/macos_sampler_thread.h +55 -0
  33. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -40
  34. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +796 -417
  35. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +48 -12
  36. data/ext/datadog_profiling_native_extension/profiling.c +93 -27
  37. data/ext/datadog_profiling_native_extension/ruby_helpers.c +77 -137
  38. data/ext/datadog_profiling_native_extension/ruby_helpers.h +21 -20
  39. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +34 -10
  40. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +2 -1
  41. data/ext/datadog_profiling_native_extension/stack_recorder.c +458 -302
  42. data/ext/datadog_profiling_native_extension/stack_recorder.h +14 -3
  43. data/ext/datadog_profiling_native_extension/time_helpers.h +18 -11
  44. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +48 -0
  45. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +36 -0
  46. data/ext/libdatadog_api/crashtracker.c +33 -30
  47. data/ext/libdatadog_api/crashtracker.h +5 -0
  48. data/ext/libdatadog_api/crashtracker_report_exception.c +126 -0
  49. data/ext/libdatadog_api/datadog_ruby_common.c +85 -16
  50. data/ext/libdatadog_api/datadog_ruby_common.h +47 -1
  51. data/ext/libdatadog_api/ddsketch.c +102 -0
  52. data/ext/libdatadog_api/di.c +218 -0
  53. data/ext/libdatadog_api/extconf.rb +44 -45
  54. data/ext/libdatadog_api/feature_flags.c +588 -0
  55. data/ext/libdatadog_api/feature_flags.h +5 -0
  56. data/ext/libdatadog_api/helpers.h +27 -0
  57. data/ext/libdatadog_api/init.c +35 -0
  58. data/ext/libdatadog_api/library_config.c +173 -0
  59. data/ext/libdatadog_api/library_config.h +25 -0
  60. data/ext/libdatadog_api/otel_thread_context.c +268 -0
  61. data/ext/libdatadog_api/otel_thread_context.h +5 -0
  62. data/ext/libdatadog_api/process_discovery.c +118 -0
  63. data/ext/libdatadog_api/process_discovery.h +5 -0
  64. data/ext/libdatadog_api/trace_exporter.c +1932 -0
  65. data/ext/libdatadog_api/trace_exporter.h +5 -0
  66. data/ext/libdatadog_extconf_helpers.rb +116 -18
  67. data/lib/datadog/ai_guard/autoload.rb +10 -0
  68. data/lib/datadog/ai_guard/component.rb +53 -0
  69. data/lib/datadog/ai_guard/configuration/ext.rb +19 -0
  70. data/lib/datadog/ai_guard/configuration.rb +128 -0
  71. data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -0
  72. data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
  73. data/lib/datadog/ai_guard/contrib/rack/integration.rb +42 -0
  74. data/lib/datadog/ai_guard/contrib/rack/patcher.rb +26 -0
  75. data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +97 -0
  76. data/lib/datadog/ai_guard/contrib/rails/integration.rb +41 -0
  77. data/lib/datadog/ai_guard/contrib/rails/patcher.rb +97 -0
  78. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +73 -0
  79. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
  80. data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
  81. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +31 -0
  82. data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
  83. data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
  84. data/lib/datadog/ai_guard/evaluation/content_part.rb +48 -0
  85. data/lib/datadog/ai_guard/evaluation/message.rb +43 -0
  86. data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
  87. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +36 -0
  88. data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
  89. data/lib/datadog/ai_guard/evaluation/request.rb +30 -0
  90. data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
  91. data/lib/datadog/ai_guard/evaluation/result.rb +38 -0
  92. data/lib/datadog/ai_guard/evaluation/tool_call.rb +31 -0
  93. data/lib/datadog/ai_guard/evaluation.rb +130 -0
  94. data/lib/datadog/ai_guard/ext.rb +32 -0
  95. data/lib/datadog/ai_guard/http_client.rb +86 -0
  96. data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
  97. data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
  98. data/lib/datadog/ai_guard/redaction/result.rb +29 -0
  99. data/lib/datadog/ai_guard/redaction.rb +80 -0
  100. data/lib/datadog/ai_guard.rb +182 -0
  101. data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +68 -0
  102. data/lib/datadog/appsec/actions_handler.rb +49 -0
  103. data/lib/datadog/appsec/anonymizer.rb +16 -0
  104. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
  105. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +66 -0
  106. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +36 -0
  107. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
  108. data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
  109. data/lib/datadog/appsec/api_security/route_extractor.rb +104 -0
  110. data/lib/datadog/appsec/api_security/sampler.rb +62 -0
  111. data/lib/datadog/appsec/api_security.rb +23 -0
  112. data/lib/datadog/appsec/assets/blocked.html +1 -99
  113. data/lib/datadog/appsec/assets/blocked.json +1 -1
  114. data/lib/datadog/appsec/assets/blocked.text +3 -1
  115. data/lib/datadog/appsec/assets/waf_rules/README.md +44 -5
  116. data/lib/datadog/appsec/assets/waf_rules/recommended.json +954 -229
  117. data/lib/datadog/appsec/assets/waf_rules/strict.json +109 -106
  118. data/lib/datadog/appsec/assets.rb +8 -8
  119. data/lib/datadog/appsec/autoload.rb +5 -5
  120. data/lib/datadog/appsec/component.rb +56 -75
  121. data/lib/datadog/appsec/compressed_json.rb +40 -0
  122. data/lib/datadog/appsec/configuration.rb +436 -1
  123. data/lib/datadog/appsec/context.rb +135 -0
  124. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +94 -0
  125. data/lib/datadog/appsec/contrib/active_record/integration.rb +41 -0
  126. data/lib/datadog/appsec/contrib/active_record/patcher.rb +104 -0
  127. data/lib/datadog/appsec/contrib/auto_instrument.rb +1 -1
  128. data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +75 -0
  129. data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +39 -0
  130. data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +30 -0
  131. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +146 -0
  132. data/lib/datadog/appsec/contrib/devise/configuration.rb +52 -0
  133. data/lib/datadog/appsec/contrib/devise/data_extractor.rb +78 -0
  134. data/lib/datadog/appsec/contrib/devise/ext.rb +22 -0
  135. data/lib/datadog/appsec/contrib/devise/integration.rb +5 -6
  136. data/lib/datadog/appsec/contrib/devise/patcher.rb +34 -26
  137. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +104 -0
  138. data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +70 -0
  139. data/lib/datadog/appsec/contrib/devise/{patcher/rememberable_patch.rb → patches/skip_signin_tracking_patch.rb} +3 -3
  140. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +121 -0
  141. data/lib/datadog/appsec/contrib/excon/integration.rb +41 -0
  142. data/lib/datadog/appsec/contrib/excon/patcher.rb +30 -0
  143. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +181 -0
  144. data/lib/datadog/appsec/contrib/faraday/connection_patch.rb +22 -0
  145. data/lib/datadog/appsec/contrib/faraday/integration.rb +42 -0
  146. data/lib/datadog/appsec/contrib/faraday/patcher.rb +53 -0
  147. data/lib/datadog/appsec/contrib/faraday/rack_builder_patch.rb +22 -0
  148. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +166 -0
  149. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +4 -10
  150. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +76 -26
  151. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +21 -36
  152. data/lib/datadog/appsec/contrib/graphql/integration.rb +10 -9
  153. data/lib/datadog/appsec/contrib/graphql/patcher.rb +3 -6
  154. data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
  155. data/lib/datadog/appsec/contrib/rack/ext.rb +34 -0
  156. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +56 -21
  157. data/lib/datadog/appsec/contrib/rack/gateway/response.rb +4 -4
  158. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +101 -106
  159. data/lib/datadog/appsec/contrib/rack/input_peeker.rb +79 -0
  160. data/lib/datadog/appsec/contrib/rack/integration.rb +7 -7
  161. data/lib/datadog/appsec/contrib/rack/patcher.rb +3 -6
  162. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +17 -15
  163. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +118 -114
  164. data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
  165. data/lib/datadog/appsec/contrib/rails/gateway/request.rb +43 -4
  166. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +58 -35
  167. data/lib/datadog/appsec/contrib/rails/integration.rb +6 -6
  168. data/lib/datadog/appsec/contrib/rails/patcher.rb +82 -69
  169. data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +29 -0
  170. data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +33 -0
  171. data/lib/datadog/appsec/contrib/rails/request.rb +3 -3
  172. data/lib/datadog/appsec/contrib/rails/request_middleware.rb +2 -1
  173. data/lib/datadog/appsec/contrib/rest_client/integration.rb +45 -0
  174. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +30 -0
  175. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +193 -0
  176. data/lib/datadog/appsec/contrib/sinatra/gateway/request.rb +1 -1
  177. data/lib/datadog/appsec/contrib/sinatra/gateway/route_params.rb +1 -1
  178. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +83 -71
  179. data/lib/datadog/appsec/contrib/sinatra/integration.rb +6 -6
  180. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +25 -61
  181. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +31 -0
  182. data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
  183. data/lib/datadog/appsec/counter_sampler.rb +25 -0
  184. data/lib/datadog/appsec/default_header_tags.rb +52 -0
  185. data/lib/datadog/appsec/event.rb +80 -128
  186. data/lib/datadog/appsec/ext.rb +17 -1
  187. data/lib/datadog/appsec/extensions.rb +2 -2
  188. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +23 -2
  189. data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +23 -0
  190. data/lib/datadog/appsec/instrumentation/gateway.rb +6 -24
  191. data/lib/datadog/appsec/metrics/collector.rb +80 -0
  192. data/lib/datadog/appsec/metrics/exporter.rb +48 -0
  193. data/lib/datadog/appsec/metrics/telemetry.rb +40 -0
  194. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +47 -0
  195. data/lib/datadog/appsec/metrics.rb +14 -0
  196. data/lib/datadog/appsec/monitor/gateway/watcher.rb +60 -36
  197. data/lib/datadog/appsec/monitor.rb +1 -1
  198. data/lib/datadog/appsec/processor/rule_loader.rb +54 -60
  199. data/lib/datadog/appsec/rate_limiter.rb +1 -1
  200. data/lib/datadog/appsec/remote.rb +59 -70
  201. data/lib/datadog/appsec/response.rb +42 -94
  202. data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
  203. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
  204. data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
  205. data/lib/datadog/appsec/route_normalizer.rb +80 -0
  206. data/lib/datadog/appsec/security_engine/engine.rb +197 -0
  207. data/lib/datadog/appsec/security_engine/result.rb +103 -0
  208. data/lib/datadog/appsec/security_engine/runner.rb +111 -0
  209. data/lib/datadog/appsec/security_engine.rb +9 -0
  210. data/lib/datadog/appsec/security_event.rb +37 -0
  211. data/lib/datadog/appsec/thread_safe_ref.rb +61 -0
  212. data/lib/datadog/appsec/trace_keeper.rb +36 -0
  213. data/lib/datadog/appsec/utils/hash_coercion.rb +23 -0
  214. data/lib/datadog/appsec/utils/http/body.rb +41 -0
  215. data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
  216. data/lib/datadog/appsec/utils/http/media_range.rb +17 -16
  217. data/lib/datadog/appsec/utils/http/media_type.rb +33 -27
  218. data/lib/datadog/appsec/utils/http/url_encoded.rb +107 -0
  219. data/lib/datadog/appsec.rb +35 -28
  220. data/lib/datadog/auto_instrument.rb +5 -2
  221. data/lib/datadog/auto_instrument_base.rb +2 -1
  222. data/lib/datadog/core/buffer/cruby.rb +1 -1
  223. data/lib/datadog/core/buffer/random.rb +18 -2
  224. data/lib/datadog/core/buffer/thread_safe.rb +1 -1
  225. data/lib/datadog/core/configuration/agent_settings.rb +52 -0
  226. data/lib/datadog/core/configuration/agent_settings_resolver.rb +46 -59
  227. data/lib/datadog/core/configuration/agentless_settings_resolver.rb +176 -0
  228. data/lib/datadog/core/configuration/base.rb +19 -7
  229. data/lib/datadog/core/configuration/components.rb +283 -54
  230. data/lib/datadog/core/configuration/components_state.rb +28 -0
  231. data/lib/datadog/core/configuration/config_helper.rb +109 -0
  232. data/lib/datadog/core/configuration/deprecations.rb +36 -0
  233. data/lib/datadog/core/configuration/ext.rb +15 -12
  234. data/lib/datadog/core/configuration/option.rb +150 -84
  235. data/lib/datadog/core/configuration/option_definition.rb +51 -30
  236. data/lib/datadog/core/configuration/options.rb +58 -19
  237. data/lib/datadog/core/configuration/settings.rb +341 -140
  238. data/lib/datadog/core/configuration/stable_config.rb +32 -0
  239. data/lib/datadog/core/configuration/supported_configurations.rb +426 -0
  240. data/lib/datadog/core/configuration.rb +51 -21
  241. data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
  242. data/lib/datadog/core/contrib/rails/utils.rb +28 -0
  243. data/lib/datadog/core/crashtracking/component.rb +89 -39
  244. data/lib/datadog/core/crashtracking/tag_builder.rb +11 -23
  245. data/lib/datadog/core/ddsketch.rb +19 -0
  246. data/lib/datadog/core/deprecations.rb +3 -3
  247. data/lib/datadog/core/diagnostics/environment_logger.rb +46 -9
  248. data/lib/datadog/core/diagnostics/health.rb +2 -2
  249. data/lib/datadog/core/encoding.rb +21 -5
  250. data/lib/datadog/core/environment/agent_info.rb +142 -0
  251. data/lib/datadog/core/environment/cgroup.rb +53 -28
  252. data/lib/datadog/core/environment/container.rb +143 -51
  253. data/lib/datadog/core/environment/execution.rb +3 -3
  254. data/lib/datadog/core/environment/ext.rb +32 -21
  255. data/lib/datadog/core/environment/gc.rb +1 -1
  256. data/lib/datadog/core/environment/git.rb +5 -4
  257. data/lib/datadog/core/environment/identity.rb +50 -14
  258. data/lib/datadog/core/environment/platform.rb +8 -8
  259. data/lib/datadog/core/environment/process.rb +122 -0
  260. data/lib/datadog/core/environment/socket.rb +15 -2
  261. data/lib/datadog/core/environment/variable_helpers.rb +4 -4
  262. data/lib/datadog/core/environment/vm_cache.rb +1 -1
  263. data/lib/datadog/core/environment/yjit.rb +8 -2
  264. data/lib/datadog/core/error.rb +15 -13
  265. data/lib/datadog/core/evp.rb +11 -0
  266. data/lib/datadog/core/extensions.rb +1 -1
  267. data/lib/datadog/core/feature_flags.rb +61 -0
  268. data/lib/datadog/core/git/ext.rb +4 -4
  269. data/lib/datadog/core/knuth_sampler.rb +59 -0
  270. data/lib/datadog/core/logger.rb +5 -5
  271. data/lib/datadog/core/logging/ext.rb +1 -1
  272. data/lib/datadog/core/metrics/client.rb +49 -49
  273. data/lib/datadog/core/metrics/ext.rb +5 -5
  274. data/lib/datadog/core/metrics/helpers.rb +1 -1
  275. data/lib/datadog/core/metrics/logging.rb +8 -8
  276. data/lib/datadog/core/metrics/options.rb +5 -5
  277. data/lib/datadog/core/pin.rb +9 -9
  278. data/lib/datadog/core/process_discovery/tracer_memfd.rb +13 -0
  279. data/lib/datadog/core/process_discovery.rb +62 -0
  280. data/lib/datadog/core/rate_limiter.rb +16 -4
  281. data/lib/datadog/core/remote/client/capabilities.rb +82 -11
  282. data/lib/datadog/core/remote/client.rb +130 -107
  283. data/lib/datadog/core/remote/component.rb +97 -55
  284. data/lib/datadog/core/remote/configuration/content.rb +17 -4
  285. data/lib/datadog/core/remote/configuration/digest.rb +22 -15
  286. data/lib/datadog/core/remote/configuration/path.rb +9 -9
  287. data/lib/datadog/core/remote/configuration/repository.rb +19 -6
  288. data/lib/datadog/core/remote/configuration/target.rb +15 -8
  289. data/lib/datadog/core/remote/configuration.rb +4 -4
  290. data/lib/datadog/core/remote/ext.rb +3 -3
  291. data/lib/datadog/core/remote/negotiation.rb +10 -10
  292. data/lib/datadog/core/remote/tie/tracing.rb +7 -7
  293. data/lib/datadog/core/remote/transport/config.rb +11 -37
  294. data/lib/datadog/core/remote/transport/http/config.rb +39 -107
  295. data/lib/datadog/core/remote/transport/http/negotiation.rb +19 -92
  296. data/lib/datadog/core/remote/transport/http.rb +33 -106
  297. data/lib/datadog/core/remote/transport/negotiation.rb +9 -21
  298. data/lib/datadog/core/remote/worker.rb +40 -41
  299. data/lib/datadog/core/runtime/ext.rb +22 -22
  300. data/lib/datadog/core/runtime/metrics.rb +34 -14
  301. data/lib/datadog/core/semaphore.rb +32 -0
  302. data/lib/datadog/core/tag_builder.rb +52 -0
  303. data/lib/datadog/core/tag_normalizer.rb +84 -0
  304. data/lib/datadog/core/telemetry/component.rb +143 -60
  305. data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
  306. data/lib/datadog/core/telemetry/emitter.rb +26 -14
  307. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +67 -0
  308. data/lib/datadog/core/telemetry/event/app_closing.rb +18 -0
  309. data/lib/datadog/core/telemetry/event/app_dependencies_loaded.rb +33 -0
  310. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
  311. data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
  312. data/lib/datadog/core/telemetry/event/app_heartbeat.rb +18 -0
  313. data/lib/datadog/core/telemetry/event/app_integrations_change.rb +58 -0
  314. data/lib/datadog/core/telemetry/event/app_started.rb +274 -0
  315. data/lib/datadog/core/telemetry/event/base.rb +40 -0
  316. data/lib/datadog/core/telemetry/event/distributions.rb +18 -0
  317. data/lib/datadog/core/telemetry/event/generate_metrics.rb +43 -0
  318. data/lib/datadog/core/telemetry/event/log.rb +76 -0
  319. data/lib/datadog/core/telemetry/event/message_batch.rb +42 -0
  320. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +66 -0
  321. data/lib/datadog/core/telemetry/event.rb +21 -392
  322. data/lib/datadog/core/telemetry/ext.rb +11 -9
  323. data/lib/datadog/core/telemetry/http/adapters/net.rb +12 -97
  324. data/lib/datadog/core/telemetry/logger.rb +6 -3
  325. data/lib/datadog/core/telemetry/logging.rb +35 -17
  326. data/lib/datadog/core/telemetry/metric.rb +37 -15
  327. data/lib/datadog/core/telemetry/metrics_collection.rb +2 -2
  328. data/lib/datadog/core/telemetry/metrics_manager.rb +10 -1
  329. data/lib/datadog/core/telemetry/request.rb +23 -11
  330. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +66 -0
  331. data/lib/datadog/core/telemetry/transport/http.rb +68 -0
  332. data/lib/datadog/core/telemetry/transport/telemetry.rb +42 -0
  333. data/lib/datadog/core/telemetry/worker.rb +214 -35
  334. data/lib/datadog/core/transport/ext.rb +14 -10
  335. data/lib/datadog/core/transport/http/adapters/net.rb +19 -4
  336. data/lib/datadog/core/transport/http/adapters/test.rb +3 -2
  337. data/lib/datadog/core/transport/http/adapters/unix_socket.rb +5 -5
  338. data/lib/datadog/core/transport/http/api/endpoint.rb +10 -5
  339. data/lib/datadog/{tracing → core}/transport/http/api/instance.rb +5 -5
  340. data/lib/datadog/core/transport/http/api/map.rb +1 -1
  341. data/lib/datadog/{tracing → core}/transport/http/builder.rb +62 -36
  342. data/lib/datadog/core/transport/http/client.rb +80 -0
  343. data/lib/datadog/core/transport/http/env.rb +8 -0
  344. data/lib/datadog/core/transport/http/response.rb +5 -1
  345. data/lib/datadog/core/transport/http.rb +80 -0
  346. data/lib/datadog/core/transport/parcel.rb +61 -9
  347. data/lib/datadog/core/transport/response.rb +19 -1
  348. data/lib/datadog/core/transport/transport.rb +90 -0
  349. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +181 -29
  350. data/lib/datadog/core/utils/base64_codec.rb +23 -0
  351. data/lib/datadog/core/utils/duration.rb +33 -33
  352. data/lib/datadog/core/utils/enumerable_compat.rb +29 -0
  353. data/lib/datadog/core/utils/fnv.rb +26 -0
  354. data/lib/datadog/core/utils/forking.rb +5 -3
  355. data/lib/datadog/core/utils/hash.rb +0 -23
  356. data/lib/datadog/core/utils/lru_cache.rb +45 -0
  357. data/lib/datadog/core/utils/network.rb +34 -13
  358. data/lib/datadog/core/utils/only_once.rb +1 -1
  359. data/lib/datadog/core/utils/only_once_successful.rb +25 -8
  360. data/lib/datadog/core/utils/safe_dup.rb +4 -3
  361. data/lib/datadog/core/utils/sequence.rb +3 -4
  362. data/lib/datadog/core/utils/spawn_monkey_patch.rb +68 -0
  363. data/lib/datadog/core/utils/time.rb +19 -33
  364. data/lib/datadog/core/utils/truncation.rb +21 -0
  365. data/lib/datadog/core/utils/url.rb +1 -1
  366. data/lib/datadog/core/utils.rb +14 -5
  367. data/lib/datadog/core/vendor/multipart-post/multipart/post/composite_read_io.rb +1 -1
  368. data/lib/datadog/core/vendor/multipart-post/multipart/post/multipartable.rb +8 -8
  369. data/lib/datadog/core/vendor/multipart-post/multipart/post/parts.rb +7 -7
  370. data/lib/datadog/core/worker.rb +1 -1
  371. data/lib/datadog/core/workers/async.rb +41 -15
  372. data/lib/datadog/core/workers/interval_loop.rb +63 -4
  373. data/lib/datadog/core/workers/polling.rb +4 -2
  374. data/lib/datadog/core/workers/queue.rb +96 -1
  375. data/lib/datadog/core/workers/runtime_metrics.rb +14 -6
  376. data/lib/datadog/core.rb +20 -3
  377. data/lib/datadog/data_streams/configuration.rb +50 -0
  378. data/lib/datadog/data_streams/ext.rb +11 -0
  379. data/lib/datadog/data_streams/extensions.rb +16 -0
  380. data/lib/datadog/data_streams/pathway_context.rb +167 -0
  381. data/lib/datadog/data_streams/processor.rb +542 -0
  382. data/lib/datadog/data_streams/transport/http/stats.rb +58 -0
  383. data/lib/datadog/data_streams/transport/http.rb +36 -0
  384. data/lib/datadog/data_streams/transport/stats.rb +39 -0
  385. data/lib/datadog/data_streams.rb +100 -0
  386. data/lib/datadog/di/base.rb +123 -0
  387. data/lib/datadog/di/boot.rb +47 -0
  388. data/lib/datadog/di/capture_expression.rb +21 -0
  389. data/lib/datadog/di/capture_expression_evaluator.rb +71 -0
  390. data/lib/datadog/di/capture_limits.rb +41 -0
  391. data/lib/datadog/di/code_tracker.rb +256 -18
  392. data/lib/datadog/di/component.rb +220 -0
  393. data/lib/datadog/di/configuration.rb +241 -2
  394. data/lib/datadog/di/context.rb +82 -0
  395. data/lib/datadog/di/contrib/active_record.rb +41 -0
  396. data/lib/datadog/di/contrib/railtie.rb +15 -0
  397. data/lib/datadog/di/contrib.rb +28 -0
  398. data/lib/datadog/di/el/compiler.rb +244 -0
  399. data/lib/datadog/di/el/evaluator.rb +239 -0
  400. data/lib/datadog/di/el/expression.rb +53 -0
  401. data/lib/datadog/di/el.rb +5 -0
  402. data/lib/datadog/di/error.rb +66 -0
  403. data/lib/datadog/di/fatal_exceptions.rb +26 -0
  404. data/lib/datadog/di/instrumenter.rb +978 -0
  405. data/lib/datadog/di/logger.rb +30 -0
  406. data/lib/datadog/di/preload.rb +18 -0
  407. data/lib/datadog/di/probe.rb +188 -14
  408. data/lib/datadog/di/probe_builder.rb +132 -3
  409. data/lib/datadog/di/probe_file_loader/railtie.rb +15 -0
  410. data/lib/datadog/di/probe_file_loader.rb +87 -0
  411. data/lib/datadog/di/probe_manager.rb +352 -0
  412. data/lib/datadog/di/probe_notification_builder.rb +452 -0
  413. data/lib/datadog/di/probe_notifier_worker.rb +381 -0
  414. data/lib/datadog/di/probe_repository.rb +198 -0
  415. data/lib/datadog/di/proc_responder.rb +36 -0
  416. data/lib/datadog/di/redactor.rb +24 -3
  417. data/lib/datadog/di/remote.rb +310 -0
  418. data/lib/datadog/di/serializer.rb +487 -96
  419. data/lib/datadog/di/transport/diagnostics.rb +32 -0
  420. data/lib/datadog/di/transport/http/diagnostics.rb +35 -0
  421. data/lib/datadog/di/transport/http/input.rb +45 -0
  422. data/lib/datadog/di/transport/http.rb +81 -0
  423. data/lib/datadog/di/transport/input.rb +144 -0
  424. data/lib/datadog/di/utils.rb +170 -0
  425. data/lib/datadog/di.rb +224 -2
  426. data/lib/datadog/error_tracking/collector.rb +88 -0
  427. data/lib/datadog/error_tracking/component.rb +167 -0
  428. data/lib/datadog/error_tracking/configuration.rb +64 -0
  429. data/lib/datadog/error_tracking/ext.rb +18 -0
  430. data/lib/datadog/error_tracking/extensions.rb +16 -0
  431. data/lib/datadog/error_tracking/filters.rb +77 -0
  432. data/lib/datadog/error_tracking.rb +18 -0
  433. data/lib/datadog/kit/appsec/events/v2.rb +253 -0
  434. data/lib/datadog/kit/appsec/events.rb +39 -26
  435. data/lib/datadog/kit/enable_core_dumps.rb +3 -3
  436. data/lib/datadog/kit/identity.rb +34 -24
  437. data/lib/datadog/kit/tracing/method_tracer.rb +135 -0
  438. data/lib/datadog/kit.rb +1 -1
  439. data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
  440. data/lib/datadog/open_feature/agentless/parser.rb +43 -0
  441. data/lib/datadog/open_feature/agentless/response.rb +19 -0
  442. data/lib/datadog/open_feature/agentless/transport.rb +103 -0
  443. data/lib/datadog/open_feature/component.rb +165 -0
  444. data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
  445. data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
  446. data/lib/datadog/open_feature/configuration/source.rb +43 -0
  447. data/lib/datadog/open_feature/configuration.rb +168 -0
  448. data/lib/datadog/open_feature/evaluation_engine.rb +96 -0
  449. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  450. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  451. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  452. data/lib/datadog/open_feature/exposures/event.rb +67 -0
  453. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  454. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  455. data/lib/datadog/open_feature/ext.rb +40 -0
  456. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +442 -0
  457. data/lib/datadog/open_feature/flag_evaluation/writer.rb +544 -0
  458. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +105 -0
  459. data/lib/datadog/open_feature/hooks/flag_eval_metrics_hook.rb +51 -0
  460. data/lib/datadog/open_feature/hooks/span_enrichment_hook/codec.rb +58 -0
  461. data/lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state.rb +96 -0
  462. data/lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state_store.rb +49 -0
  463. data/lib/datadog/open_feature/hooks/span_enrichment_hook.rb +184 -0
  464. data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
  465. data/lib/datadog/open_feature/native_evaluator.rb +74 -0
  466. data/lib/datadog/open_feature/noop_evaluator.rb +31 -0
  467. data/lib/datadog/open_feature/provider.rb +235 -0
  468. data/lib/datadog/open_feature/remote.rb +67 -0
  469. data/lib/datadog/open_feature/resolution_details.rb +37 -0
  470. data/lib/datadog/open_feature/transport.rb +126 -0
  471. data/lib/datadog/open_feature.rb +19 -0
  472. data/lib/datadog/opentelemetry/api/baggage.rb +90 -0
  473. data/lib/datadog/opentelemetry/api/context.rb +25 -10
  474. data/lib/datadog/opentelemetry/configuration/settings.rb +226 -0
  475. data/lib/datadog/opentelemetry/ext.rb +9 -0
  476. data/lib/datadog/opentelemetry/logs.rb +98 -0
  477. data/lib/datadog/opentelemetry/metrics.rb +90 -0
  478. data/lib/datadog/opentelemetry/sdk/configurator.rb +69 -5
  479. data/lib/datadog/opentelemetry/sdk/id_generator.rb +16 -10
  480. data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +30 -0
  481. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +28 -0
  482. data/lib/datadog/opentelemetry/sdk/propagator.rb +21 -15
  483. data/lib/datadog/opentelemetry/sdk/span_processor.rb +24 -21
  484. data/lib/datadog/opentelemetry/sdk/trace/span.rb +43 -39
  485. data/lib/datadog/opentelemetry/sdk.rb +19 -0
  486. data/lib/datadog/opentelemetry/signal_configuration.rb +57 -0
  487. data/lib/datadog/opentelemetry/trace.rb +8 -5
  488. data/lib/datadog/opentelemetry.rb +11 -6
  489. data/lib/datadog/profiling/collectors/code_provenance.rb +84 -16
  490. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +60 -3
  491. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -4
  492. data/lib/datadog/profiling/collectors/info.rb +62 -8
  493. data/lib/datadog/profiling/collectors/thread_context.rb +25 -18
  494. data/lib/datadog/profiling/component.rb +123 -104
  495. data/lib/datadog/profiling/encoded_profile.rb +11 -0
  496. data/lib/datadog/profiling/exporter.rb +51 -17
  497. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +24 -5
  498. data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
  499. data/lib/datadog/profiling/ext.rb +2 -17
  500. data/lib/datadog/profiling/flush.rb +26 -17
  501. data/lib/datadog/profiling/http_transport.rb +30 -96
  502. data/lib/datadog/profiling/load_native_extension.rb +3 -35
  503. data/lib/datadog/profiling/profiler.rb +31 -12
  504. data/lib/datadog/profiling/scheduler.rb +19 -10
  505. data/lib/datadog/profiling/sequence_tracker.rb +44 -0
  506. data/lib/datadog/profiling/stack_recorder.rb +38 -18
  507. data/lib/datadog/profiling/tag_builder.rb +41 -41
  508. data/lib/datadog/profiling/tasks/exec.rb +11 -6
  509. data/lib/datadog/profiling/tasks/help.rb +1 -0
  510. data/lib/datadog/profiling/tasks/setup.rb +4 -2
  511. data/lib/datadog/profiling.rb +42 -37
  512. data/lib/datadog/ruby_version.rb +25 -0
  513. data/lib/datadog/single_step_instrument.rb +11 -2
  514. data/lib/datadog/symbol_database/component.rb +734 -0
  515. data/lib/datadog/symbol_database/configuration.rb +73 -0
  516. data/lib/datadog/symbol_database/extensions.rb +19 -0
  517. data/lib/datadog/symbol_database/extractor.rb +1092 -0
  518. data/lib/datadog/symbol_database/file_hash.rb +48 -0
  519. data/lib/datadog/symbol_database/logger.rb +43 -0
  520. data/lib/datadog/symbol_database/remote.rb +195 -0
  521. data/lib/datadog/symbol_database/scope.rb +102 -0
  522. data/lib/datadog/symbol_database/scope_batcher.rb +292 -0
  523. data/lib/datadog/symbol_database/service_version.rb +66 -0
  524. data/lib/datadog/symbol_database/symbol.rb +69 -0
  525. data/lib/datadog/symbol_database/transport/http/endpoint.rb +28 -0
  526. data/lib/datadog/symbol_database/transport/http.rb +45 -0
  527. data/lib/datadog/symbol_database/transport.rb +54 -0
  528. data/lib/datadog/symbol_database/uploader.rb +230 -0
  529. data/lib/datadog/symbol_database.rb +71 -0
  530. data/lib/datadog/tracing/analytics.rb +2 -2
  531. data/lib/datadog/tracing/buffer.rb +15 -15
  532. data/lib/datadog/tracing/client_ip.rb +4 -4
  533. data/lib/datadog/tracing/component.rb +63 -23
  534. data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
  535. data/lib/datadog/tracing/configuration/dynamic.rb +16 -16
  536. data/lib/datadog/tracing/configuration/ext.rb +62 -33
  537. data/lib/datadog/tracing/configuration/http.rb +2 -2
  538. data/lib/datadog/tracing/configuration/settings.rb +182 -34
  539. data/lib/datadog/tracing/context.rb +27 -5
  540. data/lib/datadog/tracing/context_provider.rb +12 -8
  541. data/lib/datadog/tracing/contrib/action_cable/configuration/settings.rb +2 -2
  542. data/lib/datadog/tracing/contrib/action_cable/event.rb +5 -5
  543. data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +9 -6
  544. data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +8 -5
  545. data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +9 -6
  546. data/lib/datadog/tracing/contrib/action_cable/events.rb +5 -5
  547. data/lib/datadog/tracing/contrib/action_cable/ext.rb +18 -18
  548. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -5
  549. data/lib/datadog/tracing/contrib/action_cable/integration.rb +10 -7
  550. data/lib/datadog/tracing/contrib/action_cable/patcher.rb +4 -4
  551. data/lib/datadog/tracing/contrib/action_mailer/configuration/settings.rb +2 -2
  552. data/lib/datadog/tracing/contrib/action_mailer/event.rb +7 -4
  553. data/lib/datadog/tracing/contrib/action_mailer/events/deliver.rb +8 -8
  554. data/lib/datadog/tracing/contrib/action_mailer/events/process.rb +4 -4
  555. data/lib/datadog/tracing/contrib/action_mailer/events.rb +3 -3
  556. data/lib/datadog/tracing/contrib/action_mailer/ext.rb +18 -18
  557. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +11 -7
  558. data/lib/datadog/tracing/contrib/action_mailer/patcher.rb +3 -3
  559. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +38 -20
  560. data/lib/datadog/tracing/contrib/action_pack/action_controller/patcher.rb +2 -2
  561. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +48 -13
  562. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +5 -3
  563. data/lib/datadog/tracing/contrib/action_pack/configuration/settings.rb +3 -3
  564. data/lib/datadog/tracing/contrib/action_pack/ext.rb +10 -8
  565. data/lib/datadog/tracing/contrib/action_pack/integration.rb +10 -7
  566. data/lib/datadog/tracing/contrib/action_pack/patcher.rb +3 -3
  567. data/lib/datadog/tracing/contrib/action_pack/utils.rb +2 -3
  568. data/lib/datadog/tracing/contrib/action_view/configuration/settings.rb +3 -3
  569. data/lib/datadog/tracing/contrib/action_view/event.rb +1 -1
  570. data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +10 -7
  571. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +12 -9
  572. data/lib/datadog/tracing/contrib/action_view/events.rb +3 -3
  573. data/lib/datadog/tracing/contrib/action_view/ext.rb +10 -10
  574. data/lib/datadog/tracing/contrib/action_view/integration.rb +14 -11
  575. data/lib/datadog/tracing/contrib/action_view/patcher.rb +5 -5
  576. data/lib/datadog/tracing/contrib/action_view/utils.rb +2 -2
  577. data/lib/datadog/tracing/contrib/active_job/configuration/settings.rb +2 -2
  578. data/lib/datadog/tracing/contrib/active_job/data_streams.rb +62 -0
  579. data/lib/datadog/tracing/contrib/active_job/event.rb +11 -11
  580. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +11 -8
  581. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +11 -8
  582. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +11 -8
  583. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +11 -8
  584. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +11 -8
  585. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +11 -8
  586. data/lib/datadog/tracing/contrib/active_job/events.rb +6 -6
  587. data/lib/datadog/tracing/contrib/active_job/ext.rb +23 -23
  588. data/lib/datadog/tracing/contrib/active_job/integration.rb +10 -7
  589. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
  590. data/lib/datadog/tracing/contrib/active_job/patcher.rb +21 -5
  591. data/lib/datadog/tracing/contrib/active_model_serializers/configuration/settings.rb +2 -2
  592. data/lib/datadog/tracing/contrib/active_model_serializers/event.rb +4 -4
  593. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +8 -8
  594. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +9 -10
  595. data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +3 -3
  596. data/lib/datadog/tracing/contrib/active_model_serializers/ext.rb +10 -10
  597. data/lib/datadog/tracing/contrib/active_model_serializers/integration.rb +5 -6
  598. data/lib/datadog/tracing/contrib/active_model_serializers/patcher.rb +3 -3
  599. data/lib/datadog/tracing/contrib/active_record/configuration/makara_resolver.rb +1 -1
  600. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +9 -9
  601. data/lib/datadog/tracing/contrib/active_record/configuration/settings.rb +4 -4
  602. data/lib/datadog/tracing/contrib/active_record/event.rb +1 -1
  603. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +10 -10
  604. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +17 -19
  605. data/lib/datadog/tracing/contrib/active_record/events.rb +3 -3
  606. data/lib/datadog/tracing/contrib/active_record/ext.rb +15 -15
  607. data/lib/datadog/tracing/contrib/active_record/integration.rb +16 -12
  608. data/lib/datadog/tracing/contrib/active_record/patcher.rb +2 -2
  609. data/lib/datadog/tracing/contrib/active_record/utils.rb +19 -19
  610. data/lib/datadog/tracing/contrib/active_support/cache/event.rb +1 -1
  611. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +29 -21
  612. data/lib/datadog/tracing/contrib/active_support/cache/events.rb +1 -1
  613. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +40 -8
  614. data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +8 -4
  615. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +16 -6
  616. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +26 -3
  617. data/lib/datadog/tracing/contrib/active_support/ext.rb +16 -16
  618. data/lib/datadog/tracing/contrib/active_support/integration.rb +11 -8
  619. data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +3 -2
  620. data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +2 -2
  621. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +9 -11
  622. data/lib/datadog/tracing/contrib/active_support/patcher.rb +2 -2
  623. data/lib/datadog/tracing/contrib/analytics.rb +1 -1
  624. data/lib/datadog/tracing/contrib/auto_instrument.rb +5 -5
  625. data/lib/datadog/tracing/contrib/aws/configuration/settings.rb +3 -3
  626. data/lib/datadog/tracing/contrib/aws/ext.rb +27 -27
  627. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +21 -13
  628. data/lib/datadog/tracing/contrib/aws/integration.rb +11 -8
  629. data/lib/datadog/tracing/contrib/aws/parsed_context.rb +9 -3
  630. data/lib/datadog/tracing/contrib/aws/patcher.rb +10 -6
  631. data/lib/datadog/tracing/contrib/aws/service/base.rb +2 -1
  632. data/lib/datadog/tracing/contrib/aws/service/dynamodb.rb +2 -2
  633. data/lib/datadog/tracing/contrib/aws/service/eventbridge.rb +2 -2
  634. data/lib/datadog/tracing/contrib/aws/service/kinesis.rb +4 -4
  635. data/lib/datadog/tracing/contrib/aws/service/s3.rb +2 -2
  636. data/lib/datadog/tracing/contrib/aws/service/sns.rb +3 -3
  637. data/lib/datadog/tracing/contrib/aws/service/sqs.rb +3 -3
  638. data/lib/datadog/tracing/contrib/aws/service/states.rb +5 -5
  639. data/lib/datadog/tracing/contrib/aws/services.rb +14 -14
  640. data/lib/datadog/tracing/contrib/component.rb +3 -3
  641. data/lib/datadog/tracing/contrib/concurrent_ruby/async_patch.rb +2 -2
  642. data/lib/datadog/tracing/contrib/concurrent_ruby/configuration/settings.rb +2 -2
  643. data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +2 -2
  644. data/lib/datadog/tracing/contrib/concurrent_ruby/ext.rb +2 -2
  645. data/lib/datadog/tracing/contrib/concurrent_ruby/future_patch.rb +2 -2
  646. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +8 -5
  647. data/lib/datadog/tracing/contrib/concurrent_ruby/patcher.rb +4 -4
  648. data/lib/datadog/tracing/contrib/concurrent_ruby/promises_future_patch.rb +2 -2
  649. data/lib/datadog/tracing/contrib/configurable.rb +24 -9
  650. data/lib/datadog/tracing/contrib/configuration/resolver.rb +14 -4
  651. data/lib/datadog/tracing/contrib/configuration/resolvers/pattern_resolver.rb +5 -5
  652. data/lib/datadog/tracing/contrib/configuration/settings.rb +3 -3
  653. data/lib/datadog/tracing/contrib/dalli/configuration/settings.rb +2 -2
  654. data/lib/datadog/tracing/contrib/dalli/ext.rb +16 -15
  655. data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +6 -10
  656. data/lib/datadog/tracing/contrib/dalli/integration.rb +10 -7
  657. data/lib/datadog/tracing/contrib/dalli/patcher.rb +3 -3
  658. data/lib/datadog/tracing/contrib/dalli/quantize.rb +3 -3
  659. data/lib/datadog/tracing/contrib/delayed_job/configuration/settings.rb +2 -2
  660. data/lib/datadog/tracing/contrib/delayed_job/ext.rb +14 -14
  661. data/lib/datadog/tracing/contrib/delayed_job/integration.rb +5 -5
  662. data/lib/datadog/tracing/contrib/delayed_job/patcher.rb +3 -3
  663. data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +7 -5
  664. data/lib/datadog/tracing/contrib/delayed_job/server_internal_tracer/worker.rb +1 -0
  665. data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +6 -2
  666. data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +18 -17
  667. data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +8 -8
  668. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +63 -64
  669. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +9 -9
  670. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +7 -3
  671. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +19 -17
  672. data/lib/datadog/tracing/contrib/ethon/ext.rb +16 -14
  673. data/lib/datadog/tracing/contrib/ethon/integration.rb +6 -6
  674. data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +4 -11
  675. data/lib/datadog/tracing/contrib/ethon/patcher.rb +3 -3
  676. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +21 -10
  677. data/lib/datadog/tracing/contrib/excon/ext.rb +14 -12
  678. data/lib/datadog/tracing/contrib/excon/integration.rb +6 -6
  679. data/lib/datadog/tracing/contrib/excon/middleware.rb +18 -17
  680. data/lib/datadog/tracing/contrib/excon/patcher.rb +2 -2
  681. data/lib/datadog/tracing/contrib/ext.rb +23 -20
  682. data/lib/datadog/tracing/contrib/extensions.rb +57 -14
  683. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +20 -13
  684. data/lib/datadog/tracing/contrib/faraday/ext.rb +14 -12
  685. data/lib/datadog/tracing/contrib/faraday/integration.rb +6 -6
  686. data/lib/datadog/tracing/contrib/faraday/middleware.rb +20 -17
  687. data/lib/datadog/tracing/contrib/faraday/patcher.rb +6 -6
  688. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +11 -7
  689. data/lib/datadog/tracing/contrib/grape/endpoint.rb +69 -36
  690. data/lib/datadog/tracing/contrib/grape/ext.rb +15 -15
  691. data/lib/datadog/tracing/contrib/grape/instrumentation.rb +15 -10
  692. data/lib/datadog/tracing/contrib/grape/integration.rb +5 -5
  693. data/lib/datadog/tracing/contrib/grape/patcher.rb +10 -5
  694. data/lib/datadog/tracing/contrib/graphql/configuration/error_extension_env_parser.rb +21 -0
  695. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +20 -2
  696. data/lib/datadog/tracing/contrib/graphql/ext.rb +11 -5
  697. data/lib/datadog/tracing/contrib/graphql/integration.rb +9 -9
  698. data/lib/datadog/tracing/contrib/graphql/patcher.rb +8 -8
  699. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +174 -41
  700. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +2 -2
  701. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +7 -4
  702. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +26 -24
  703. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +14 -18
  704. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor.rb +6 -6
  705. data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +2 -2
  706. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +13 -8
  707. data/lib/datadog/tracing/contrib/grpc/ext.rb +14 -13
  708. data/lib/datadog/tracing/contrib/grpc/formatting.rb +7 -7
  709. data/lib/datadog/tracing/contrib/grpc/integration.rb +5 -5
  710. data/lib/datadog/tracing/contrib/grpc/intercept_with_datadog.rb +1 -1
  711. data/lib/datadog/tracing/contrib/grpc/patcher.rb +3 -3
  712. data/lib/datadog/tracing/contrib/grpc.rb +7 -6
  713. data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +4 -3
  714. data/lib/datadog/tracing/contrib/hanami/configuration/settings.rb +2 -2
  715. data/lib/datadog/tracing/contrib/hanami/ext.rb +8 -8
  716. data/lib/datadog/tracing/contrib/hanami/integration.rb +6 -6
  717. data/lib/datadog/tracing/contrib/hanami/patcher.rb +5 -5
  718. data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +5 -4
  719. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +14 -14
  720. data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +1 -7
  721. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +20 -9
  722. data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +6 -6
  723. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +13 -8
  724. data/lib/datadog/tracing/contrib/http/ext.rb +14 -12
  725. data/lib/datadog/tracing/contrib/http/instrumentation.rb +20 -24
  726. data/lib/datadog/tracing/contrib/http/integration.rb +9 -8
  727. data/lib/datadog/tracing/contrib/http/patcher.rb +3 -3
  728. data/lib/datadog/tracing/contrib/http.rb +7 -6
  729. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +20 -9
  730. data/lib/datadog/tracing/contrib/httpclient/ext.rb +14 -12
  731. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +21 -27
  732. data/lib/datadog/tracing/contrib/httpclient/integration.rb +6 -6
  733. data/lib/datadog/tracing/contrib/httpclient/patcher.rb +3 -3
  734. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +20 -9
  735. data/lib/datadog/tracing/contrib/httprb/ext.rb +14 -12
  736. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +21 -29
  737. data/lib/datadog/tracing/contrib/httprb/integration.rb +9 -6
  738. data/lib/datadog/tracing/contrib/httprb/patcher.rb +3 -3
  739. data/lib/datadog/tracing/contrib/integration.rb +3 -3
  740. data/lib/datadog/tracing/contrib/kafka/configuration/settings.rb +2 -2
  741. data/lib/datadog/tracing/contrib/kafka/event.rb +5 -4
  742. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +8 -4
  743. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +5 -5
  744. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +5 -5
  745. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +6 -6
  746. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/join_group.rb +6 -6
  747. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/leave_group.rb +6 -6
  748. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/sync_group.rb +6 -6
  749. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +11 -6
  750. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +11 -6
  751. data/lib/datadog/tracing/contrib/kafka/events.rb +10 -10
  752. data/lib/datadog/tracing/contrib/kafka/ext.rb +40 -40
  753. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  754. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  755. data/lib/datadog/tracing/contrib/kafka/integration.rb +8 -5
  756. data/lib/datadog/tracing/contrib/kafka/patcher.rb +17 -3
  757. data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +31 -0
  758. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +50 -0
  759. data/lib/datadog/tracing/contrib/karafka/ext.rb +28 -0
  760. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  761. data/lib/datadog/tracing/contrib/karafka/integration.rb +45 -0
  762. data/lib/datadog/tracing/contrib/karafka/monitor.rb +77 -0
  763. data/lib/datadog/tracing/contrib/karafka/patcher.rb +102 -0
  764. data/lib/datadog/tracing/contrib/karafka.rb +38 -0
  765. data/lib/datadog/tracing/contrib/lograge/configuration/settings.rb +2 -2
  766. data/lib/datadog/tracing/contrib/lograge/ext.rb +1 -1
  767. data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +1 -1
  768. data/lib/datadog/tracing/contrib/lograge/integration.rb +5 -5
  769. data/lib/datadog/tracing/contrib/lograge/patcher.rb +13 -11
  770. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +11 -3
  771. data/lib/datadog/tracing/contrib/mongodb/ext.rb +19 -18
  772. data/lib/datadog/tracing/contrib/mongodb/instrumentation.rb +3 -3
  773. data/lib/datadog/tracing/contrib/mongodb/integration.rb +9 -6
  774. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +10 -10
  775. data/lib/datadog/tracing/contrib/mongodb/patcher.rb +3 -3
  776. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +38 -24
  777. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +9 -3
  778. data/lib/datadog/tracing/contrib/mysql2/ext.rb +12 -12
  779. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +25 -18
  780. data/lib/datadog/tracing/contrib/mysql2/integration.rb +5 -5
  781. data/lib/datadog/tracing/contrib/mysql2/patcher.rb +2 -2
  782. data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +19 -2
  783. data/lib/datadog/tracing/contrib/opensearch/ext.rb +31 -21
  784. data/lib/datadog/tracing/contrib/opensearch/integration.rb +8 -6
  785. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +75 -77
  786. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +10 -10
  787. data/lib/datadog/tracing/contrib/patchable.rb +1 -1
  788. data/lib/datadog/tracing/contrib/patcher.rb +15 -14
  789. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +9 -3
  790. data/lib/datadog/tracing/contrib/pg/ext.rb +19 -19
  791. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +21 -24
  792. data/lib/datadog/tracing/contrib/pg/integration.rb +5 -5
  793. data/lib/datadog/tracing/contrib/pg/patcher.rb +2 -2
  794. data/lib/datadog/tracing/contrib/presto/configuration/settings.rb +2 -2
  795. data/lib/datadog/tracing/contrib/presto/ext.rb +22 -22
  796. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +11 -13
  797. data/lib/datadog/tracing/contrib/presto/integration.rb +8 -5
  798. data/lib/datadog/tracing/contrib/presto/patcher.rb +4 -4
  799. data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +2 -2
  800. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +26 -13
  801. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +7 -3
  802. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +29 -5
  803. data/lib/datadog/tracing/contrib/que/configuration/settings.rb +7 -4
  804. data/lib/datadog/tracing/contrib/que/ext.rb +19 -18
  805. data/lib/datadog/tracing/contrib/que/integration.rb +6 -6
  806. data/lib/datadog/tracing/contrib/que/patcher.rb +1 -1
  807. data/lib/datadog/tracing/contrib/que/tracer.rb +3 -2
  808. data/lib/datadog/tracing/contrib/racecar/configuration/settings.rb +3 -3
  809. data/lib/datadog/tracing/contrib/racecar/event.rb +6 -10
  810. data/lib/datadog/tracing/contrib/racecar/events/batch.rb +5 -5
  811. data/lib/datadog/tracing/contrib/racecar/events/consume.rb +4 -4
  812. data/lib/datadog/tracing/contrib/racecar/events/message.rb +5 -5
  813. data/lib/datadog/tracing/contrib/racecar/events.rb +4 -4
  814. data/lib/datadog/tracing/contrib/racecar/ext.rb +18 -18
  815. data/lib/datadog/tracing/contrib/racecar/integration.rb +5 -5
  816. data/lib/datadog/tracing/contrib/racecar/patcher.rb +3 -3
  817. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +14 -4
  818. data/lib/datadog/tracing/contrib/rack/ext.rb +43 -15
  819. data/lib/datadog/tracing/contrib/rack/header_collection.rb +13 -3
  820. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +55 -32
  821. data/lib/datadog/tracing/contrib/rack/integration.rb +7 -7
  822. data/lib/datadog/tracing/contrib/rack/middlewares.rb +102 -63
  823. data/lib/datadog/tracing/contrib/rack/patcher.rb +6 -6
  824. data/lib/datadog/tracing/contrib/rack/request_queue.rb +8 -7
  825. data/lib/datadog/tracing/contrib/rack/route_inference.rb +67 -0
  826. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +131 -7
  827. data/lib/datadog/tracing/contrib/rails/auto_instrument_railtie.rb +1 -1
  828. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +9 -6
  829. data/lib/datadog/tracing/contrib/rails/ext.rb +16 -14
  830. data/lib/datadog/tracing/contrib/rails/framework.rb +12 -12
  831. data/lib/datadog/tracing/contrib/rails/integration.rb +6 -6
  832. data/lib/datadog/tracing/contrib/rails/log_injection.rb +2 -2
  833. data/lib/datadog/tracing/contrib/rails/middlewares.rb +4 -4
  834. data/lib/datadog/tracing/contrib/rails/patcher.rb +11 -9
  835. data/lib/datadog/tracing/contrib/rails/railtie.rb +4 -4
  836. data/lib/datadog/tracing/contrib/rails/runner.rb +64 -40
  837. data/lib/datadog/tracing/contrib/rake/configuration/settings.rb +3 -3
  838. data/lib/datadog/tracing/contrib/rake/ext.rb +12 -12
  839. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +13 -11
  840. data/lib/datadog/tracing/contrib/rake/integration.rb +5 -5
  841. data/lib/datadog/tracing/contrib/rake/patcher.rb +3 -3
  842. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +5 -5
  843. data/lib/datadog/tracing/contrib/redis/configuration/settings.rb +2 -2
  844. data/lib/datadog/tracing/contrib/redis/ext.rb +19 -18
  845. data/lib/datadog/tracing/contrib/redis/instrumentation.rb +6 -6
  846. data/lib/datadog/tracing/contrib/redis/integration.rb +7 -7
  847. data/lib/datadog/tracing/contrib/redis/patcher.rb +12 -12
  848. data/lib/datadog/tracing/contrib/redis/quantize.rb +41 -7
  849. data/lib/datadog/tracing/contrib/redis/tags.rb +7 -12
  850. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +20 -12
  851. data/lib/datadog/tracing/contrib/registerable.rb +11 -0
  852. data/lib/datadog/tracing/contrib/registry.rb +1 -1
  853. data/lib/datadog/tracing/contrib/resque/configuration/settings.rb +2 -2
  854. data/lib/datadog/tracing/contrib/resque/ext.rb +7 -7
  855. data/lib/datadog/tracing/contrib/resque/integration.rb +5 -5
  856. data/lib/datadog/tracing/contrib/resque/patcher.rb +3 -3
  857. data/lib/datadog/tracing/contrib/resque/resque_job.rb +7 -6
  858. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +7 -4
  859. data/lib/datadog/tracing/contrib/rest_client/ext.rb +13 -11
  860. data/lib/datadog/tracing/contrib/rest_client/integration.rb +8 -5
  861. data/lib/datadog/tracing/contrib/rest_client/patcher.rb +2 -2
  862. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +19 -15
  863. data/lib/datadog/tracing/contrib/roda/configuration/settings.rb +2 -2
  864. data/lib/datadog/tracing/contrib/roda/ext.rb +6 -5
  865. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +12 -7
  866. data/lib/datadog/tracing/contrib/roda/integration.rb +6 -6
  867. data/lib/datadog/tracing/contrib/roda/patcher.rb +3 -3
  868. data/lib/datadog/tracing/contrib/semantic_logger/configuration/settings.rb +2 -2
  869. data/lib/datadog/tracing/contrib/semantic_logger/ext.rb +1 -1
  870. data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +1 -1
  871. data/lib/datadog/tracing/contrib/semantic_logger/integration.rb +5 -5
  872. data/lib/datadog/tracing/contrib/semantic_logger/patcher.rb +2 -2
  873. data/lib/datadog/tracing/contrib/sequel/configuration/settings.rb +2 -2
  874. data/lib/datadog/tracing/contrib/sequel/database.rb +8 -11
  875. data/lib/datadog/tracing/contrib/sequel/dataset.rb +7 -6
  876. data/lib/datadog/tracing/contrib/sequel/ext.rb +12 -8
  877. data/lib/datadog/tracing/contrib/sequel/integration.rb +5 -5
  878. data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
  879. data/lib/datadog/tracing/contrib/sequel/patcher.rb +3 -3
  880. data/lib/datadog/tracing/contrib/sequel/utils.rb +103 -16
  881. data/lib/datadog/tracing/contrib/shoryuken/configuration/settings.rb +2 -2
  882. data/lib/datadog/tracing/contrib/shoryuken/ext.rb +12 -12
  883. data/lib/datadog/tracing/contrib/shoryuken/integration.rb +6 -6
  884. data/lib/datadog/tracing/contrib/shoryuken/patcher.rb +1 -1
  885. data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +3 -2
  886. data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +15 -9
  887. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +7 -3
  888. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +14 -9
  889. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +31 -29
  890. data/lib/datadog/tracing/contrib/sidekiq/integration.rb +7 -7
  891. data/lib/datadog/tracing/contrib/sidekiq/patcher.rb +8 -8
  892. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/heartbeat.rb +2 -0
  893. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/job_fetch.rb +1 -0
  894. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/redis_info.rb +1 -0
  895. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/scheduled_poller.rb +2 -0
  896. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/stop.rb +1 -0
  897. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +19 -15
  898. data/lib/datadog/tracing/contrib/sidekiq/utils.rb +10 -10
  899. data/lib/datadog/tracing/contrib/sidekiq.rb +7 -6
  900. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +8 -4
  901. data/lib/datadog/tracing/contrib/sinatra/env.rb +6 -6
  902. data/lib/datadog/tracing/contrib/sinatra/ext.rb +17 -16
  903. data/lib/datadog/tracing/contrib/sinatra/framework.rb +2 -2
  904. data/lib/datadog/tracing/contrib/sinatra/integration.rb +5 -5
  905. data/lib/datadog/tracing/contrib/sinatra/patcher.rb +5 -5
  906. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +11 -10
  907. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +46 -45
  908. data/lib/datadog/tracing/contrib/sneakers/configuration/settings.rb +1 -1
  909. data/lib/datadog/tracing/contrib/sneakers/ext.rb +12 -12
  910. data/lib/datadog/tracing/contrib/sneakers/integration.rb +19 -8
  911. data/lib/datadog/tracing/contrib/sneakers/patcher.rb +2 -2
  912. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +5 -4
  913. data/lib/datadog/tracing/contrib/span_attribute_schema.rb +9 -4
  914. data/lib/datadog/tracing/contrib/status_range_env_parser.rb +2 -2
  915. data/lib/datadog/tracing/contrib/status_range_matcher.rb +13 -1
  916. data/lib/datadog/tracing/contrib/stripe/configuration/settings.rb +2 -2
  917. data/lib/datadog/tracing/contrib/stripe/ext.rb +12 -12
  918. data/lib/datadog/tracing/contrib/stripe/integration.rb +5 -5
  919. data/lib/datadog/tracing/contrib/stripe/patcher.rb +2 -2
  920. data/lib/datadog/tracing/contrib/stripe/request.rb +5 -5
  921. data/lib/datadog/tracing/contrib/sucker_punch/configuration/settings.rb +2 -2
  922. data/lib/datadog/tracing/contrib/sucker_punch/exception_handler.rb +1 -1
  923. data/lib/datadog/tracing/contrib/sucker_punch/ext.rb +13 -13
  924. data/lib/datadog/tracing/contrib/sucker_punch/instrumentation.rb +5 -4
  925. data/lib/datadog/tracing/contrib/sucker_punch/integration.rb +5 -5
  926. data/lib/datadog/tracing/contrib/sucker_punch/patcher.rb +4 -4
  927. data/lib/datadog/tracing/contrib/support.rb +28 -0
  928. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +9 -3
  929. data/lib/datadog/tracing/contrib/trilogy/ext.rb +12 -12
  930. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +10 -12
  931. data/lib/datadog/tracing/contrib/trilogy/integration.rb +5 -5
  932. data/lib/datadog/tracing/contrib/trilogy/patcher.rb +2 -2
  933. data/lib/datadog/tracing/contrib/utils/database.rb +5 -5
  934. data/lib/datadog/tracing/contrib/utils/quantization/{hash.rb → hash_formatter.rb} +17 -15
  935. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +16 -16
  936. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +31 -0
  937. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +50 -0
  938. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +18 -0
  939. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  940. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  941. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +49 -0
  942. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  943. data/lib/datadog/tracing/contrib/waterdrop.rb +42 -0
  944. data/lib/datadog/tracing/contrib.rb +62 -52
  945. data/lib/datadog/tracing/correlation.rb +21 -14
  946. data/lib/datadog/tracing/diagnostics/environment_logger.rb +23 -15
  947. data/lib/datadog/tracing/diagnostics/ext.rb +19 -19
  948. data/lib/datadog/tracing/diagnostics/health.rb +1 -1
  949. data/lib/datadog/tracing/distributed/b3_multi.rb +9 -9
  950. data/lib/datadog/tracing/distributed/b3_single.rb +6 -6
  951. data/lib/datadog/tracing/distributed/baggage.rb +250 -0
  952. data/lib/datadog/tracing/distributed/datadog.rb +41 -38
  953. data/lib/datadog/tracing/distributed/fetcher.rb +1 -1
  954. data/lib/datadog/tracing/distributed/helpers.rb +4 -4
  955. data/lib/datadog/tracing/distributed/none.rb +4 -2
  956. data/lib/datadog/tracing/distributed/propagation.rb +73 -14
  957. data/lib/datadog/tracing/distributed/propagation_policy.rb +47 -0
  958. data/lib/datadog/tracing/distributed/trace_context.rb +31 -259
  959. data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
  960. data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
  961. data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
  962. data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
  963. data/lib/datadog/tracing/event.rb +6 -8
  964. data/lib/datadog/tracing/ext.rb +9 -0
  965. data/lib/datadog/tracing/flush.rb +1 -1
  966. data/lib/datadog/tracing/metadata/analytics.rb +3 -3
  967. data/lib/datadog/tracing/metadata/errors.rb +8 -8
  968. data/lib/datadog/tracing/metadata/ext.rb +89 -59
  969. data/lib/datadog/tracing/metadata/metastruct.rb +36 -0
  970. data/lib/datadog/tracing/metadata/metastruct_tagging.rb +42 -0
  971. data/lib/datadog/tracing/metadata/tagging.rb +9 -9
  972. data/lib/datadog/tracing/metadata.rb +5 -3
  973. data/lib/datadog/tracing/otel_thread_context.rb +99 -0
  974. data/lib/datadog/tracing/pipeline/span_filter.rb +5 -3
  975. data/lib/datadog/tracing/pipeline/span_processor.rb +3 -1
  976. data/lib/datadog/tracing/pipeline.rb +4 -4
  977. data/lib/datadog/tracing/remote.rb +228 -30
  978. data/lib/datadog/tracing/sampling/all_sampler.rb +1 -1
  979. data/lib/datadog/tracing/sampling/ext.rb +9 -9
  980. data/lib/datadog/tracing/sampling/matcher.rb +4 -4
  981. data/lib/datadog/tracing/sampling/priority_sampler.rb +20 -5
  982. data/lib/datadog/tracing/sampling/rate_by_key_sampler.rb +3 -3
  983. data/lib/datadog/tracing/sampling/rate_by_service_sampler.rb +2 -2
  984. data/lib/datadog/tracing/sampling/rate_sampler.rb +9 -19
  985. data/lib/datadog/tracing/sampling/rule.rb +5 -5
  986. data/lib/datadog/tracing/sampling/rule_sampler.rb +91 -58
  987. data/lib/datadog/tracing/sampling/sampler.rb +2 -2
  988. data/lib/datadog/tracing/sampling/span/ext.rb +3 -3
  989. data/lib/datadog/tracing/sampling/span/matcher.rb +2 -2
  990. data/lib/datadog/tracing/sampling/span/rule.rb +1 -2
  991. data/lib/datadog/tracing/sampling/span/rule_parser.rb +11 -11
  992. data/lib/datadog/tracing/sampling/span/sampler.rb +0 -7
  993. data/lib/datadog/tracing/span.rb +29 -12
  994. data/lib/datadog/tracing/span_event.rb +125 -5
  995. data/lib/datadog/tracing/span_link.rb +12 -12
  996. data/lib/datadog/tracing/span_operation.rb +120 -40
  997. data/lib/datadog/tracing/sync_writer.rb +22 -14
  998. data/lib/datadog/tracing/trace_digest.rb +58 -28
  999. data/lib/datadog/tracing/trace_operation.rb +263 -120
  1000. data/lib/datadog/tracing/trace_segment.rb +18 -16
  1001. data/lib/datadog/tracing/tracer.rb +208 -73
  1002. data/lib/datadog/tracing/transport/http/client.rb +17 -30
  1003. data/lib/datadog/tracing/transport/http/statistics.rb +2 -2
  1004. data/lib/datadog/tracing/transport/http/traces.rb +11 -90
  1005. data/lib/datadog/tracing/transport/http.rb +26 -73
  1006. data/lib/datadog/tracing/transport/io/client.rb +13 -16
  1007. data/lib/datadog/tracing/transport/io/response.rb +1 -1
  1008. data/lib/datadog/tracing/transport/io/traces.rb +33 -39
  1009. data/lib/datadog/tracing/transport/io.rb +3 -3
  1010. data/lib/datadog/tracing/transport/native/response.rb +71 -0
  1011. data/lib/datadog/tracing/transport/native.rb +379 -0
  1012. data/lib/datadog/tracing/transport/serializable_trace.rb +47 -23
  1013. data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
  1014. data/lib/datadog/tracing/transport/statistics.rb +3 -3
  1015. data/lib/datadog/tracing/transport/trace_formatter.rb +49 -10
  1016. data/lib/datadog/tracing/transport/traces.rb +45 -99
  1017. data/lib/datadog/tracing/utils.rb +3 -3
  1018. data/lib/datadog/tracing/workers.rb +12 -10
  1019. data/lib/datadog/tracing/writer.rb +54 -39
  1020. data/lib/datadog/tracing.rb +19 -6
  1021. data/lib/datadog/version.rb +4 -4
  1022. data/lib/datadog.rb +21 -5
  1023. metadata +372 -70
  1024. data/ext/datadog_profiling_loader/datadog_profiling_loader.c +0 -142
  1025. data/ext/datadog_profiling_loader/extconf.rb +0 -60
  1026. data/ext/datadog_profiling_native_extension/clock_id_noop.c +0 -21
  1027. data/ext/libdatadog_api/macos_development.md +0 -26
  1028. data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -92
  1029. data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -114
  1030. data/lib/datadog/appsec/configuration/settings.rb +0 -207
  1031. data/lib/datadog/appsec/contrib/devise/event.rb +0 -57
  1032. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +0 -77
  1033. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +0 -54
  1034. data/lib/datadog/appsec/contrib/devise/resource.rb +0 -35
  1035. data/lib/datadog/appsec/contrib/devise/tracking.rb +0 -57
  1036. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +0 -59
  1037. data/lib/datadog/appsec/contrib/patcher.rb +0 -12
  1038. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +0 -81
  1039. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +0 -60
  1040. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +0 -66
  1041. data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
  1042. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +0 -66
  1043. data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
  1044. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +0 -61
  1045. data/lib/datadog/appsec/monitor/reactive/set_user.rb +0 -58
  1046. data/lib/datadog/appsec/processor/actions.rb +0 -49
  1047. data/lib/datadog/appsec/processor/rule_merger.rb +0 -170
  1048. data/lib/datadog/appsec/processor.rb +0 -170
  1049. data/lib/datadog/appsec/reactive/address_hash.rb +0 -22
  1050. data/lib/datadog/appsec/reactive/engine.rb +0 -47
  1051. data/lib/datadog/appsec/reactive/operation.rb +0 -68
  1052. data/lib/datadog/appsec/reactive/subscriber.rb +0 -19
  1053. data/lib/datadog/appsec/scope.rb +0 -61
  1054. data/lib/datadog/core/crashtracking/agent_base_url.rb +0 -21
  1055. data/lib/datadog/core/remote/transport/http/api/instance.rb +0 -39
  1056. data/lib/datadog/core/remote/transport/http/api/spec.rb +0 -21
  1057. data/lib/datadog/core/remote/transport/http/api.rb +0 -58
  1058. data/lib/datadog/core/remote/transport/http/builder.rb +0 -219
  1059. data/lib/datadog/core/remote/transport/http/client.rb +0 -48
  1060. data/lib/datadog/core/telemetry/http/env.rb +0 -20
  1061. data/lib/datadog/core/telemetry/http/ext.rb +0 -28
  1062. data/lib/datadog/core/telemetry/http/response.rb +0 -70
  1063. data/lib/datadog/core/telemetry/http/transport.rb +0 -90
  1064. data/lib/datadog/core/utils/base64.rb +0 -22
  1065. data/lib/datadog/di/configuration/settings.rb +0 -163
  1066. data/lib/datadog/tracing/contrib/rails/utils.rb +0 -26
  1067. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -84
  1068. data/lib/datadog/tracing/transport/http/api/spec.rb +0 -19
  1069. data/lib/datadog/tracing/transport/http/api.rb +0 -43
  1070. data/lib/datadog/tracing/workers/trace_writer.rb +0 -196
@@ -1,14 +1,22 @@
1
1
  #include "heap_recorder.h"
2
- #include <pthread.h>
3
2
  #include "ruby/st.h"
4
3
  #include "ruby_helpers.h"
5
- #include <errno.h>
6
4
  #include "collectors_stack.h"
7
5
  #include "libdatadog_helpers.h"
8
-
9
- #if (defined(HAVE_WORKING_RB_GC_FORCE_RECYCLE) && ! defined(NO_SEEN_OBJ_ID_FLAG))
10
- #define CAN_APPLY_GC_FORCE_RECYCLE_BUG_WORKAROUND
11
- #endif
6
+ #include "private_vm_api_access.h"
7
+ #include "time_helpers.h"
8
+
9
+ // note on calloc vs ruby_xcalloc use:
10
+ // * Whenever we're allocating memory after being called by the Ruby VM in a "regular" situation (e.g. initializer)
11
+ // we should use `ruby_xcalloc` to give the VM visibility into what we're doing + give it a chance to manage GC
12
+ // * BUT, when we're being called during a sample, being in the middle of an object allocation is a very special
13
+ // situation for the VM to be in, and we've found the hard way (e.g. https://bugs.ruby-lang.org/issues/20629 and
14
+ // https://github.com/DataDog/dd-trace-rb/pull/4240 ) that it can be easy to do things the VM didn't expect.
15
+ // * Thus, out of caution and to avoid future potential issues such as the ones above, whenever we allocate memory
16
+ // during **sampling** we use `calloc` instead of `ruby_xcalloc`. Note that we've never seen issues from using
17
+ // `ruby_xcalloc` at any time, so this is a **precaution** not a "we've seen it break". But it seems a harmless
18
+ // one to use.
19
+ // This applies to both heap_recorder.c and collectors_thread_context.c
12
20
 
13
21
  // Minimum age (in GC generations) of heap objects we want to include in heap
14
22
  // recorder iterations. Object with age 0 represent objects that have yet to undergo
@@ -16,106 +24,71 @@
16
24
  // relevant for heap profiles as the great majority should be trivially reclaimed
17
25
  // during the next GC.
18
26
  #define ITERATION_MIN_AGE 1
27
+ // Copied from https://github.com/ruby/ruby/blob/15135030e5808d527325feaaaf04caeb1b44f8b5/gc/default.c#L725C1-L725C27
28
+ // to align with Ruby's GC definition of what constitutes an old object which are only
29
+ // supposed to be reclaimed in major GCs.
30
+ #define OLD_AGE 3
31
+ // Wait at least 2 seconds before asking heap recorder to explicitly update itself. Heap recorder
32
+ // data will only materialize at profile serialization time but updating often helps keep our
33
+ // heap tracking data small since every GC should get rid of a bunch of temporary objects. The
34
+ // more we clean up before profile flush, the less work we'll have to do all-at-once when preparing
35
+ // to flush heap data and holding the GVL which should hopefully help with reducing latency impact.
36
+ #define MIN_TIME_BETWEEN_HEAP_RECORDER_UPDATES_NS SECONDS_AS_NS(2)
19
37
 
20
38
  // A compact representation of a stacktrace frame for a heap allocation.
21
39
  typedef struct {
22
- char *name;
23
- char *filename;
40
+ ddog_prof_ManagedStringId name;
41
+ ddog_prof_ManagedStringId filename;
24
42
  int32_t line;
25
43
  } heap_frame;
26
- static st_index_t heap_frame_hash(heap_frame*, st_index_t seed);
44
+
45
+ // We use memcmp/st_hash below to compare/hash an entire array of heap_frames, so want to make sure no padding is added
46
+ // We could define the structure to be packed, but that seems even weirder across compilers, and this seems more portable?
47
+ _Static_assert(
48
+ sizeof(heap_frame) == sizeof(ddog_prof_ManagedStringId) * 2 + sizeof(int32_t),
49
+ "Size of heap_frame does not match the sum of its members. Padding detected."
50
+ );
27
51
 
28
52
  // A compact representation of a stacktrace for a heap allocation.
29
- //
30
- // We could use a ddog_prof_Slice_Location instead but it has a lot of
31
- // unused fields. Because we have to keep these stacks around for at
32
- // least the lifetime of the objects allocated therein, we would be
33
- // incurring a non-negligible memory overhead for little purpose.
53
+ // Used to dedup heap allocation stacktraces across multiple objects sharing the same allocation location.
34
54
  typedef struct {
55
+ // How many objects are currently tracked in object_records recorder for this heap record.
56
+ uint32_t num_tracked_objects;
57
+
35
58
  uint16_t frames_len;
36
59
  heap_frame frames[];
37
- } heap_stack;
38
- static heap_stack* heap_stack_new(ddog_prof_Slice_Location);
39
- static void heap_stack_free(heap_stack*);
40
- static st_index_t heap_stack_hash(heap_stack*, st_index_t);
60
+ } heap_record;
61
+ static heap_record* heap_record_new(heap_recorder*, ddog_prof_Slice_Location);
62
+ static void heap_record_free(heap_recorder*, heap_record*, bool should_unintern);
41
63
 
42
64
  #if MAX_FRAMES_LIMIT > UINT16_MAX
43
65
  #error Frames len type not compatible with MAX_FRAMES_LIMIT
44
66
  #endif
45
67
 
46
- enum heap_record_key_type {
47
- HEAP_STACK,
48
- LOCATION_SLICE
49
- };
50
- // This struct allows us to use two different types of stacks when
51
- // interacting with a heap_record hash.
52
- //
53
- // The idea is that we'll always want to use heap_stack-keys when
54
- // adding new entries to the hash since that's the compact stack
55
- // representation we rely on internally.
56
- //
57
- // However, when querying for an existing heap record, we'd save a
58
- // lot of allocations if we could query with the
59
- // ddog_prof_Slice_Location we receive in our external API.
60
- //
61
- // To allow this interchange, we need a union and need to ensure
62
- // that whatever shape of the union, the heap_record_key_cmp_st
63
- // and heap_record_hash_st functions return the same results for
64
- // equivalent stacktraces.
65
- typedef struct {
66
- enum heap_record_key_type type;
67
- union {
68
- // key never owns this if set
69
- heap_stack *heap_stack;
70
- // key never owns this if set
71
- ddog_prof_Slice_Location *location_slice;
72
- };
73
- } heap_record_key;
74
- static heap_record_key* heap_record_key_new(heap_stack*);
75
- static void heap_record_key_free(heap_record_key*);
76
- static int heap_record_key_cmp_st(st_data_t, st_data_t);
77
- static st_index_t heap_record_key_hash_st(st_data_t);
78
- static const struct st_hash_type st_hash_type_heap_record_key = {
79
- heap_record_key_cmp_st,
80
- heap_record_key_hash_st,
81
- };
82
-
83
- // Need to implement these functions to support the location-slice based keys
84
- static st_index_t ddog_location_hash(ddog_prof_Location, st_index_t seed);
85
- static st_index_t ddog_location_slice_hash(ddog_prof_Slice_Location, st_index_t seed);
68
+ static int heap_record_cmp_st(st_data_t, st_data_t);
69
+ static st_index_t heap_record_hash_st(st_data_t);
70
+ static const struct st_hash_type st_hash_type_heap_record = { .compare = heap_record_cmp_st, .hash = heap_record_hash_st };
86
71
 
87
- // A heap record is used for deduping heap allocation stacktraces across multiple
88
- // objects sharing the same allocation location.
72
+ // A pending recording is used to defer adding the object to the `weak_objects` map: doing so is a Ruby method
73
+ // call that allocates, and neither of those is safe to do during on_newobj_event
89
74
  typedef struct {
90
- // How many objects are currently tracked by the heap recorder for this heap record.
91
- uint32_t num_tracked_objects;
92
- // stack is owned by the associated record and gets cleaned up alongside it
93
- heap_stack *stack;
94
- } heap_record;
95
- static heap_record* heap_record_new(heap_stack*);
96
- static void heap_record_free(heap_record*);
75
+ VALUE object_ref;
76
+ long record_id; // Always in Fixnum range
77
+ heap_record *heap_record;
78
+ live_object_data object_data;
79
+ } pending_recording;
97
80
 
98
81
  // An object record is used for storing data about currently tracked live objects
99
82
  typedef struct {
100
- long obj_id;
83
+ long record_id;
101
84
  heap_record *heap_record;
102
85
  live_object_data object_data;
103
86
  } object_record;
104
- static object_record* object_record_new(long, heap_record*, live_object_data);
105
- static void object_record_free(object_record*);
106
- static VALUE object_record_inspect(object_record*);
107
- static object_record SKIPPED_RECORD = {0};
87
+ static object_record* object_record_new(pending_recording pending);
88
+ static void object_record_free(heap_recorder*, object_record*, bool should_unintern);
89
+ static VALUE object_record_inspect(heap_recorder*, object_record*);
108
90
 
109
- // A wrapper around an object record that is in the process of being recorded and was not
110
- // yet committed.
111
- typedef struct {
112
- // Pointer to the (potentially partial) object_record containing metadata about an ongoing recording.
113
- // When NULL, this symbolizes an unstarted/invalid recording.
114
- object_record *object_record;
115
- // A flag to track whether we had to force set the RUBY_FL_SEEN_OBJ_ID flag on this object
116
- // as part of our workaround around rb_gc_force_recycle issues.
117
- bool did_recycle_workaround;
118
- } recording;
91
+ #define MAX_PENDING_RECORDINGS 256
119
92
 
120
93
  struct heap_recorder {
121
94
  // Config
@@ -123,66 +96,234 @@ struct heap_recorder {
123
96
  bool size_enabled;
124
97
  uint sample_rate;
125
98
 
126
- // Map[key: heap_record_key*, record: heap_record*]
127
- // NOTE: We always use heap_record_key.type == HEAP_STACK for storage but support lookups
128
- // via heap_record_key.type == LOCATION_SLICE to allow for allocation-free fast-paths.
99
+ // Map[key: heap_record*, record: nothing] (This is a set, basically)
129
100
  // NOTE: This table is currently only protected by the GVL since we never interact with it
130
101
  // outside the GVL.
131
- // NOTE: This table has ownership of both its heap_record_keys and heap_records.
102
+ // NOTE: This table has ownership of its heap_records.
103
+ //
104
+ // This is a cpu/memory trade-off: Maintaining the "heap_records" map means we spend extra CPU when sampling as we need
105
+ // to do de-duplication, but we reduce the memory footprint of the heap profiler.
106
+ // In the future, it may be worth revisiting if we can move this inside libdatadog: if libdatadog was able to track
107
+ // entire stacks for us, then we wouldn't need to do it on the Ruby side.
132
108
  st_table *heap_records;
133
109
 
134
- // Map[obj_id: long, record: object_record*]
110
+ // Map[record_id: long, record: object_record*]
135
111
  // NOTE: This table is currently only protected by the GVL since we never interact with it
136
112
  // outside the GVL.
137
113
  // NOTE: This table has ownership of its object_records. The keys are longs and so are
138
114
  // passed as values.
115
+ //
116
+ // TODO: @ivoanjo We've evolved to actually never need to look up on object_records (we only insert and iterate),
117
+ // so right now this seems to be just a really really fancy self-resizing list/set.
118
+ // Tests do use it for lookup currently though.
139
119
  st_table *object_records;
140
120
 
141
- // Map[obj_id: long, record: object_record*]
121
+ // Map[record_id: long, record: object_record*]
142
122
  // NOTE: This is a snapshot of object_records built ahead of a iteration. Outside of an
143
123
  // iteration context, this table will be NULL. During an iteration, there will be no
144
124
  // mutation of the data so iteration can occur without acquiring a lock.
145
125
  // NOTE: Contrary to object_records, this table has no ownership of its data.
146
126
  st_table *object_records_snapshot;
147
- // The GC gen/epoch/count in which we prepared the current iteration.
127
+ // "Lock" protecting the heap recorder bookkeeping; see "note on locking" below
128
+ uint8_t lock;
129
+ // The GC gen/epoch/count in which we are updating (or last updated if not currently updating).
148
130
  //
149
- // This enables us to calculate the age of iterated objects in the above snapshot by
150
- // comparing it against an object's alloc_gen.
151
- size_t iteration_gen;
152
-
153
- // Data for a heap recording that was started but not yet ended
154
- recording active_recording;
155
-
156
- // Reusable location array, implementing a flyweight pattern for things like iteration.
131
+ // This enables us to calculate the age of objects considered in the update by comparing it
132
+ // against an object's alloc_gen.
133
+ size_t update_gen;
134
+ // Whether the current update (or last update if not currently updating) is including old
135
+ // objects or not.
136
+ bool update_include_old;
137
+ // When did we do the last update of heap recorder?
138
+ long last_update_ns;
139
+
140
+ // ObjectSpace::WeakMap[record_id: Integer (Fixnum) => object]
141
+ // Weak references to every object we're tracking.
142
+ // Looking a record_id up returns the object while it's alive, and nil once it's been garbage collected, which
143
+ // is how we determine liveness. See `ruby_weak_map_new` for details on why this gives us a weak reference.
144
+ VALUE weak_objects;
145
+ // Source for the ids used as keys in `object_records` and `weak_objects`. Ids are never reused.
146
+ long next_record_id;
147
+
148
+ // Recordings that are waiting to be committed after on_newobj_event completes.
149
+ // We can't add the object to `weak_objects` during the newobj event, so we store the
150
+ // VALUE reference here and commit it via a postponed job.
151
+ pending_recording pending_recordings[MAX_PENDING_RECORDINGS];
152
+ uint16_t pending_recordings_count;
153
+
154
+ // Reusable arrays, implementing a flyweight pattern for things like iteration
155
+ #define REUSABLE_LOCATIONS_SIZE MAX_FRAMES_LIMIT
157
156
  ddog_prof_Location *reusable_locations;
158
157
 
158
+ #define REUSABLE_FRAME_DETAILS_SIZE (2 * MAX_FRAMES_LIMIT) // because it'll be used for both function names AND file names)
159
+ ddog_prof_ManagedStringId *reusable_ids;
160
+ ddog_CharSlice *reusable_char_slices;
161
+
159
162
  // Sampling state
160
163
  uint num_recordings_skipped;
161
164
 
165
+ ddog_prof_ManagedStringStorage string_storage;
166
+
162
167
  struct stats_last_update {
163
168
  size_t objects_alive;
164
169
  size_t objects_dead;
165
170
  size_t objects_skipped;
166
171
  size_t objects_frozen;
167
172
  } stats_last_update;
168
- };
169
173
 
170
- struct end_heap_allocation_args {
171
- struct heap_recorder *heap_recorder;
172
- ddog_prof_Slice_Location locations;
174
+ struct stats_lifetime {
175
+ unsigned long updates_successful;
176
+ unsigned long updates_skipped_concurrent;
177
+ unsigned long updates_skipped_gcgen;
178
+ unsigned long updates_skipped_time;
179
+
180
+ double ewma_young_objects_alive;
181
+ double ewma_young_objects_dead;
182
+ double ewma_young_objects_skipped; // Note: Here "young" refers to the young update; objects skipped includes non-young objects
183
+
184
+ double ewma_objects_alive;
185
+ double ewma_objects_dead;
186
+ double ewma_objects_skipped;
187
+
188
+ unsigned long deferred_recordings_skipped_buffer_full;
189
+ unsigned long deferred_recordings_committed;
190
+ } stats_lifetime;
173
191
  };
174
192
 
193
+ // note on locking:
194
+ //
195
+ // The state of the heap profiler (`pending_recordings`, `object_records`, `heap_records`, `weak_objects`) gets mutated from a
196
+ // few different places and, unlike most of the profiler, some of those operations lose the GVL while they work.
197
+ // Thus, unlike most of the profiler, relying only on knowing "we get called with the GVL" is not enough to keep them
198
+ // from stepping on each other, hence this extra "uint8_t lock".
199
+ //
200
+ // Yet, because we only ever touch while holding the GVL, and never lose the GVL between checking it and setting it, a
201
+ // plain field is enough -- no atomics needed. (Hence the "lock" and not a full actual lock)
202
+ //
203
+ // Operations that can be skipped use `heap_recorder_try_lock` and walk away when it's taken (which is almost all of them).
204
+ // The one operation that can't be skipped -- the full update that runs before serialization -- uses
205
+ // `heap_recorder_lock`, which waits.
206
+ //
207
+ // `heap_recorder_lock` is expected to be bounded because once `LOCK_WANTED` gets set, no `try_lock` will succeed, so
208
+ // the `heap_recorder_lock` will only need to wait for the previous user of the lock to finish it's work + Ruby to
209
+ // switch back to is thread.
210
+ #define HEAP_RECORDER_LOCK_HELD 0x1
211
+ #define HEAP_RECORDER_LOCK_WANTED 0x2
212
+
213
+ // Is someone in the middle of a locked operation? (Which, because they may lose the GVL, may be true even though we're
214
+ // the ones currently holding the GVL.)
215
+ static inline bool heap_recorder_is_locked(heap_recorder *heap_recorder) {
216
+ return (heap_recorder->lock & HEAP_RECORDER_LOCK_HELD) != 0;
217
+ }
218
+
219
+ // Takes the lock, unless it's held or someone is waiting for it. Returns whether it was taken.
220
+ __attribute__((warn_unused_result))
221
+ static inline bool heap_recorder_try_lock(heap_recorder *heap_recorder) {
222
+ if (heap_recorder->lock != 0) return false;
223
+
224
+ heap_recorder->lock = HEAP_RECORDER_LOCK_HELD;
225
+ return true;
226
+ }
227
+
228
+ // Takes the lock, waiting for the current holder to finish if needed. See "note on locking" above for why this wait is
229
+ // expected to be bounded.
230
+ //
231
+ // WARN: Do not call this while already holding the lock -- it would wait forever.
232
+ static void heap_recorder_lock(heap_recorder *heap_recorder) {
233
+ while (heap_recorder_is_locked(heap_recorder)) {
234
+ heap_recorder->lock |= HEAP_RECORDER_LOCK_WANTED;
235
+ rb_thread_schedule();
236
+ }
237
+
238
+ // Note that we go from observing the lock as free to taking it without ever losing the GVL, and thus without giving
239
+ // anyone else a chance to take it from under us
240
+ heap_recorder->lock = HEAP_RECORDER_LOCK_HELD;
241
+ }
242
+
243
+ static inline void heap_recorder_unlock(heap_recorder *heap_recorder) {
244
+ // Deliberately preserves `HEAP_RECORDER_LOCK_WANTED` (rather than just zero-ing the lock)
245
+ heap_recorder->lock &= ~HEAP_RECORDER_LOCK_HELD;
246
+ }
247
+
248
+ // Same as the above, in the shape `rb_ensure` wants
249
+ static VALUE heap_recorder_unlock_ensure(VALUE heap_recorder_as_value) {
250
+ heap_recorder_unlock((heap_recorder *) heap_recorder_as_value);
251
+ return Qnil;
252
+ }
253
+
254
+ typedef struct {
255
+ heap_recorder *heap_recorder;
256
+ bool full_update;
257
+ } heap_recorder_update_locked_args;
258
+
175
259
  static heap_record* get_or_create_heap_record(heap_recorder*, ddog_prof_Slice_Location);
176
260
  static void cleanup_heap_record_if_unused(heap_recorder*, heap_record*);
177
261
  static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, object_record *record);
178
- static int st_heap_record_entry_free(st_data_t, st_data_t, st_data_t);
179
- static int st_object_record_entry_free(st_data_t, st_data_t, st_data_t);
262
+ static int st_heap_record_entry_free_no_unintern(st_data_t, st_data_t, st_data_t);
263
+ static int st_object_record_entry_free_no_unintern(st_data_t, st_data_t, st_data_t);
180
264
  static int st_object_record_update(st_data_t, st_data_t, st_data_t);
181
265
  static int st_object_records_iterate(st_data_t, st_data_t, st_data_t);
182
266
  static int st_object_records_debug(st_data_t key, st_data_t value, st_data_t extra);
183
- static int update_object_record_entry(st_data_t*, st_data_t*, st_data_t, int);
184
- static void commit_recording(heap_recorder*, heap_record*, recording);
185
- static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args);
267
+ static void inc_tracked_objects_or_fail(heap_record *heap_record);
268
+ static void commit_recording(heap_recorder *, pending_recording);
269
+ static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update);
270
+ static VALUE heap_recorder_update_locked(VALUE heap_recorder_update_locked_args_as_value);
271
+ static VALUE heap_recorder_commit_recordings_may_lose_gvl_locked(VALUE heap_recorder_as_value);
272
+ static inline double ewma_stat(double previous, double current);
273
+ static void unintern_or_raise(heap_recorder *, ddog_prof_ManagedStringId);
274
+ static void unintern_all_or_raise(heap_recorder *recorder, ddog_prof_Slice_ManagedStringId ids);
275
+ static VALUE get_ruby_string_or_raise(heap_recorder*, ddog_prof_ManagedStringId);
276
+
277
+ // The following global variables are initialized at startup to save expensive lookups later.
278
+ // They are not expected to be mutated outside of init.
279
+ static VALUE class_weak_map = Qnil;
280
+ static ID aref_id = Qnil;
281
+ static ID aset_id = Qnil;
282
+
283
+ void collectors_heap_recorder_init(void) {
284
+ rb_global_variable(&class_weak_map);
285
+
286
+ VALUE module_object_space = rb_const_get(rb_cObject, rb_intern("ObjectSpace"));
287
+ class_weak_map = rb_const_get(module_object_space, rb_intern("WeakMap"));
288
+ aref_id = rb_intern("[]");
289
+ aset_id = rb_intern("[]=");
290
+ }
291
+
292
+ // Native wrapper to create a new `ObjectSpace::WeakMap`.
293
+ //
294
+ // Because an `ObjectSpace::WeakMap` entry is dropped as soon as *either* its key or its value is garbage
295
+ // collected, pairing a key that can never be collected (such as a fixnum) with the object of interest as the
296
+ // value gives us a weak reference: reading the key back returns the object while it's alive, and nothing once
297
+ // it's been collected.
298
+ static VALUE ruby_weak_map_new(void) {
299
+ return rb_class_new_instance(0, NULL, class_weak_map);
300
+ }
301
+
302
+ // Native wrapper to get an object from an `ObjectSpace::WeakMap`.
303
+ // Returns the object on success and nil if the entry is gone, meaning
304
+ // the object has been garbage collected.
305
+ // We never store nil as a value, see ruby_weak_map_set_may_lose_gvl_and_allocate_objects(), so nil unambiguously
306
+ // means "the value was garbage collected".
307
+ //
308
+ // Note: GVL can be released and other threads may get to run before this method returns
309
+ static VALUE ruby_weak_map_get_may_lose_gvl(VALUE weak_map, VALUE key) {
310
+ return rb_funcall(weak_map, aref_id, 1, key);
311
+ }
312
+
313
+ // Native wrapper to add an entry to an `ObjectSpace::WeakMap`.
314
+ // Raises RuntimeError if passed nil as a value.
315
+ //
316
+ // Note: GVL can be released and other threads may get to run before this method returns.
317
+ //
318
+ // On Ruby < 3.3 this allocates Ruby objects (because underneath the weak map registers
319
+ // finalizers for the objects, which requires allocations). Later Rubies improved WeakMap to not need this.
320
+ // This we need to be careful not to recurse on the profiler (e.g. NEWOBJ tracepoint).
321
+ static void ruby_weak_map_set_may_lose_gvl_and_allocate_objects(VALUE weak_map, VALUE key, VALUE value) {
322
+ if (value == Qnil) {
323
+ raise_error(rb_eRuntimeError, "Can't use nil as the value in the WeakMap, otherwise #[] can't differentiate alive vs nil value");
324
+ }
325
+ rb_funcall(weak_map, aset_id, 2, key, value);
326
+ }
186
327
 
187
328
  // ==========================
188
329
  // Heap Recorder External API
@@ -193,16 +334,21 @@ static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args);
193
334
  // happens under the GVL.
194
335
  //
195
336
  // ==========================
196
- heap_recorder* heap_recorder_new(void) {
337
+ heap_recorder* heap_recorder_new(ddog_prof_ManagedStringStorage string_storage) {
197
338
  heap_recorder *recorder = ruby_xcalloc(1, sizeof(heap_recorder));
198
339
 
199
- recorder->heap_records = st_init_table(&st_hash_type_heap_record_key);
340
+ recorder->heap_records = st_init_table(&st_hash_type_heap_record);
200
341
  recorder->object_records = st_init_numtable();
201
342
  recorder->object_records_snapshot = NULL;
202
- recorder->reusable_locations = ruby_xcalloc(MAX_FRAMES_LIMIT, sizeof(ddog_prof_Location));
203
- recorder->active_recording = (recording) {0};
343
+ recorder->reusable_locations = ruby_xcalloc(REUSABLE_LOCATIONS_SIZE, sizeof(ddog_prof_Location));
344
+ recorder->reusable_ids = ruby_xcalloc(REUSABLE_FRAME_DETAILS_SIZE, sizeof(ddog_prof_ManagedStringId));
345
+ recorder->reusable_char_slices = ruby_xcalloc(REUSABLE_FRAME_DETAILS_SIZE, sizeof(ddog_CharSlice));
204
346
  recorder->size_enabled = true;
205
347
  recorder->sample_rate = 1; // By default do no sampling on top of what allocation profiling already does
348
+ recorder->string_storage = string_storage;
349
+ // Note: This allocates, and thus can trigger a GC. That's fine: our caller only publishes the heap recorder on the
350
+ // stack recorder state after we return, so `heap_recorder_mark` will not observe a half-initialized recorder.
351
+ recorder->weak_objects = ruby_weak_map_new();
206
352
 
207
353
  return recorder;
208
354
  }
@@ -218,20 +364,24 @@ void heap_recorder_free(heap_recorder *heap_recorder) {
218
364
  heap_recorder_finish_iteration(heap_recorder);
219
365
  }
220
366
 
367
+ // NOTE: We don't unintern the strings referenced by the records we're about to free, thus we use
368
+ // `..._no_unintern` and `should_unintern: false`. This is intentional: `heap_recorder_free` is only ever called as part of
369
+ // tearing down the entire stack recorder, and the caller drops the whole managed string storage right after
370
+ // (see `stack_recorder_typed_data_free`), so there's no need to spend effort updating the managed string table.
371
+ // Crucially, this also keeps us from crashing: this code runs from the stack recorder's GC free callback, and
372
+ // because uninterning can fail, we can't raise exceptions in the middle of a dfree.
373
+
221
374
  // Clean-up all object records
222
- st_foreach(heap_recorder->object_records, st_object_record_entry_free, 0);
375
+ st_foreach(heap_recorder->object_records, st_object_record_entry_free_no_unintern, (st_data_t) heap_recorder);
223
376
  st_free_table(heap_recorder->object_records);
224
377
 
225
378
  // Clean-up all heap records (this includes those only referred to by queued_samples)
226
- st_foreach(heap_recorder->heap_records, st_heap_record_entry_free, 0);
379
+ st_foreach(heap_recorder->heap_records, st_heap_record_entry_free_no_unintern, (st_data_t) heap_recorder);
227
380
  st_free_table(heap_recorder->heap_records);
228
381
 
229
- if (heap_recorder->active_recording.object_record != NULL && heap_recorder->active_recording.object_record != &SKIPPED_RECORD) {
230
- // If there's a partial object record, clean it up as well
231
- object_record_free(heap_recorder->active_recording.object_record);
232
- }
233
-
234
382
  ruby_xfree(heap_recorder->reusable_locations);
383
+ ruby_xfree(heap_recorder->reusable_ids);
384
+ ruby_xfree(heap_recorder->reusable_char_slices);
235
385
 
236
386
  ruby_xfree(heap_recorder);
237
387
  }
@@ -250,7 +400,7 @@ void heap_recorder_set_sample_rate(heap_recorder *heap_recorder, int sample_rate
250
400
  }
251
401
 
252
402
  if (sample_rate <= 0) {
253
- rb_raise(rb_eArgError, "Heap sample rate must be a positive integer value but was %d", sample_rate);
403
+ raise_error(rb_eArgError, "Heap sample rate must be a positive integer value but was %d", sample_rate);
254
404
  }
255
405
 
256
406
  heap_recorder->sample_rate = sample_rate;
@@ -272,124 +422,253 @@ void heap_recorder_after_fork(heap_recorder *heap_recorder) {
272
422
  // simply be noticed on next heap_recorder_prepare_iteration.
273
423
  //
274
424
  // There is one small caveat though: fork only preserves one thread and in a Ruby app, that
275
- // will be the thread holding on to the GVL. Since we support iteration on the heap recorder
276
- // outside of the GVL, any state specific to that interaction may be incosistent after fork
425
+ // will be the thread holding the GVL. Since we support iteration on the heap recorder
426
+ // outside of the GVL, any state specific to that interaction may be inconsistent after fork
277
427
  // (e.g. an acquired lock for thread safety). Iteration operates on object_records_snapshot
278
428
  // though and that one will be updated on next heap_recorder_prepare_iteration so we really
279
429
  // only need to finish any iteration that might have been left unfinished.
280
430
  if (heap_recorder->object_records_snapshot != NULL) {
281
431
  heap_recorder_finish_iteration(heap_recorder);
282
432
  }
433
+
434
+ // This could also be left over if fork happens in the middle of a locked operation
435
+ heap_recorder->lock = 0;
436
+
437
+ // Clear lifetime stats since this is essentially a new heap recorder
438
+ heap_recorder->stats_lifetime = (struct stats_lifetime) {0};
283
439
  }
284
440
 
285
- void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight, ddog_CharSlice *alloc_class) {
441
+ // This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
442
+ // Ruby heap nor release the GVL (https://github.com/DataDog/dd-trace-rb/pull/4240).
443
+ //
444
+ // See the header for details on the arguments and on `needs_commit`.
445
+ void heap_recorder_record_allocation(
446
+ heap_recorder *heap_recorder,
447
+ VALUE new_object,
448
+ unsigned int weight,
449
+ ddog_CharSlice alloc_class,
450
+ ddog_prof_Slice_Location locations,
451
+ bool *needs_commit
452
+ ) {
286
453
  if (heap_recorder == NULL) {
454
+ *needs_commit = false;
287
455
  return;
288
456
  }
289
457
 
290
- if (heap_recorder->active_recording.object_record != NULL) {
291
- rb_raise(rb_eRuntimeError, "Detected consecutive heap allocation recording starts without end.");
458
+ // We always report `needs_commit` when there's anything pending, even if it's from a previous allocation.
459
+ //
460
+ // This can mean we "spam" a bit the postponed jobs mechanism (e.g. perhaps our postponed job hasn't run
461
+ // because there's a native extension doing Ruby object allocations in a row without giving Ruby the chance to
462
+ // run interrupts.
463
+ // That shouldn't be a problem because the allocation profiler has the dynamic sampling rate mechanism +
464
+ // triggering postponed jobs is cheap on current Rubies.
465
+ // (Note that asking for the same job multiple times de-duplicates -- Ruby will only run it once when it gets the chance)
466
+ *needs_commit = heap_recorder->pending_recordings_count > 0;
467
+
468
+ if (++heap_recorder->num_recordings_skipped < heap_recorder->sample_rate) return;
469
+
470
+ if (heap_recorder_is_locked(heap_recorder)) {
471
+ // If we got unlucky and an allocation showed up in the middle of a locked operation (because it triggered
472
+ // an allocation directly OR because that operation lost the GVL), let's skip this sample as well.
473
+ // (Note we don't take the lock ourselves: the current function never loses the GVL, and is thus already atomic.)
474
+ return;
292
475
  }
293
476
 
294
- if (heap_recorder->num_recordings_skipped + 1 < heap_recorder->sample_rate) {
295
- heap_recorder->active_recording.object_record = &SKIPPED_RECORD;
296
- heap_recorder->num_recordings_skipped++;
297
- return;
477
+ // Do not sample internal objects in the heap profiler because they are added to a ObjectSpace::WeakMap, which would not be safe.
478
+ // This means we ignore internal VM objects samples for the heap profiler,
479
+ // which is a trade-off discussed in https://github.com/DataDog/dd-trace-rb/pull/6176#discussion_r3819776251
480
+ //
481
+ // Note this is checked after the sample rate above, as the sample rate is in number of allocation profiler samples,
482
+ // not in number of non-internal samples. Like the skips above, num_recordings_skipped is not reset here, so the
483
+ // next allocation sample still gets a chance to be tracked.
484
+ if (ddtrace_is_internal_object_p(new_object)) return;
485
+
486
+ // Skip if we've hit the pending recordings limit
487
+ if (heap_recorder->pending_recordings_count >= MAX_PENDING_RECORDINGS) {
488
+ heap_recorder->stats_lifetime.deferred_recordings_skipped_buffer_full++;
489
+ return; // `needs_commit` is already true: a full buffer definitely has something to commit
298
490
  }
299
491
 
300
492
  heap_recorder->num_recordings_skipped = 0;
301
493
 
302
- VALUE ruby_obj_id = rb_obj_id(new_obj);
303
- if (!FIXNUM_P(ruby_obj_id)) {
304
- rb_raise(rb_eRuntimeError, "Detected a bignum object id. These are not supported by heap profiling.");
494
+ // Note: Everything that can raise happens before we add the recording to `pending_recordings` below, so that a
495
+ // failure never leaves a half-built entry behind
496
+ live_object_data object_data = {
497
+ .weight = weight * heap_recorder->sample_rate,
498
+ .class = intern_or_raise(heap_recorder->string_storage, alloc_class),
499
+ .alloc_gen = rb_gc_count(),
500
+ };
501
+
502
+ long record_id = ++heap_recorder->next_record_id;
503
+ if (record_id > RUBY_FIXNUM_MAX) {
504
+ // We rely on record_id always fitting in a FIXNUM to avoid extra overhead and
505
+ // allocation here. If we exhaust the record_id, we stop profiling.
506
+ // We don't expect this to happen in practice, as we don't heap sample every object, and RUBY_FIXNUM_MAX is (2**62 - 1)
507
+ raise_error(rb_eRuntimeError, "Heap profiling: Exhausted usable record_ids");
305
508
  }
306
509
 
307
- bool did_recycle_workaround = false;
308
-
309
- #ifdef CAN_APPLY_GC_FORCE_RECYCLE_BUG_WORKAROUND
310
- // If we are in a ruby version that has a working rb_gc_force_recycle implementation,
311
- // its usage may lead to an object being re-used outside of the typical GC cycle.
312
- //
313
- // This re-use is in theory invisible to us unless we're lucky enough to sample both
314
- // the original object and the replacement that uses the recycled slot.
315
- //
316
- // In practice, we've observed (https://github.com/DataDog/dd-trace-rb/pull/3366)
317
- // that non-noop implementations of rb_gc_force_recycle have an implementation bug
318
- // which results in the object that re-used the recycled slot inheriting the same
319
- // object id without setting the FL_SEEN_OBJ_ID flag. We rely on this knowledge to
320
- // "observe" implicit frees when an object we are tracking is force-recycled.
321
- //
322
- // However, it may happen that we start tracking a new object and that object was
323
- // allocated on a recycled slot. Due to the bug, this object would be missing the
324
- // FL_SEEN_OBJ_ID flag even though it was not recycled itself. If we left it be,
325
- // when we're doing our liveness check, the absence of the flag would trigger our
326
- // implicit free workaround and the object would be inferred as recycled even though
327
- // it might still be alive.
328
- //
329
- // Thus, if we detect that this new allocation is already missing the flag at the start
330
- // of the heap allocation recording, we force-set it. This should be safe since we
331
- // just called rb_obj_id on it above and the expectation is that any flaggable object
332
- // that goes through it ends up with the flag set (as evidenced by the GC_ASSERT
333
- // lines in https://github.com/ruby/ruby/blob/4a8d7246d15b2054eacb20f8ab3d29d39a3e7856/gc.c#L4050C14-L4050C14).
334
- if (RB_FL_ABLE(new_obj) && !RB_FL_TEST(new_obj, RUBY_FL_SEEN_OBJ_ID)) {
335
- RB_FL_SET(new_obj, RUBY_FL_SEEN_OBJ_ID);
336
- did_recycle_workaround = true;
337
- }
338
- #endif
339
-
340
- heap_recorder->active_recording = (recording) {
341
- .object_record = object_record_new(FIX2LONG(ruby_obj_id), NULL, (live_object_data) {
342
- .weight = weight * heap_recorder->sample_rate,
343
- .class = alloc_class != NULL ? string_from_char_slice(*alloc_class) : NULL,
344
- .alloc_gen = rb_gc_count(),
345
- }),
346
- .did_recycle_workaround = did_recycle_workaround,
510
+ heap_record *heap_record = get_or_create_heap_record(heap_recorder, locations);
511
+ inc_tracked_objects_or_fail(heap_record);
512
+
513
+ // We can't add the object to `weak_objects` from inside the NEWOBJ tracepoint, so the recording stays pending until
514
+ // `heap_recorder_commit_recordings_may_lose_gvl` gets to it
515
+ heap_recorder->pending_recordings[heap_recorder->pending_recordings_count++] = (pending_recording) {
516
+ .object_ref = new_object,
517
+ .record_id = record_id,
518
+ .heap_record = heap_record,
519
+ .object_data = object_data,
347
520
  };
521
+
522
+ *needs_commit = true; // We just added a recording
348
523
  }
349
524
 
350
- // end_heap_allocation_recording_with_rb_protect gets called while the stack_recorder is holding one of the profile
351
- // locks. To enable us to correctly unlock the profile on exception, we wrap the call to end_heap_allocation_recording
352
- // with an rb_protect.
353
- __attribute__((warn_unused_result))
354
- int end_heap_allocation_recording_with_rb_protect(struct heap_recorder *heap_recorder, ddog_prof_Slice_Location locations) {
355
- int exception_state;
356
- struct end_heap_allocation_args end_heap_allocation_args = {
357
- .heap_recorder = heap_recorder,
358
- .locations = locations,
359
- };
360
- rb_protect(end_heap_allocation_recording, (VALUE) &end_heap_allocation_args, &exception_state);
361
- return exception_state;
525
+ void heap_recorder_update_young_objects(heap_recorder *heap_recorder) {
526
+ if (heap_recorder == NULL) {
527
+ return;
528
+ }
529
+
530
+ heap_recorder_update(heap_recorder, /* full_update: */ false);
362
531
  }
363
532
 
364
- static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args) {
365
- struct end_heap_allocation_args *args = (struct end_heap_allocation_args *) end_heap_allocation_args;
533
+ void heap_recorder_commit_recordings_may_lose_gvl(heap_recorder *heap_recorder) {
534
+ if (heap_recorder == NULL) {
535
+ return; // Nothing to do
536
+ }
537
+
538
+ if (!heap_recorder_try_lock(heap_recorder)) {
539
+ // Something else is busy; `heap_recorder_record_allocation` will ask for us to be called again later
540
+ return;
541
+ }
542
+
543
+ rb_ensure(
544
+ heap_recorder_commit_recordings_may_lose_gvl_locked,
545
+ (VALUE) heap_recorder,
546
+ heap_recorder_unlock_ensure,
547
+ (VALUE) heap_recorder
548
+ );
549
+ }
366
550
 
367
- struct heap_recorder *heap_recorder = args->heap_recorder;
368
- ddog_prof_Slice_Location locations = args->locations;
551
+ static VALUE heap_recorder_commit_recordings_may_lose_gvl_locked(VALUE heap_recorder_as_value) {
552
+ heap_recorder *heap_recorder = (struct heap_recorder *) heap_recorder_as_value;
369
553
 
554
+ // Note: We consume the buffer back-to-front, and only drop each entry once it's fully committed. That makes the
555
+ // "can lose the GVL" bit below safe to retry: if anything raises, or another thread unwinds us, the entry stays
556
+ // pending and a later commit picks it up -- and setting the same entry on the weak map again is harmless. It also
557
+ // means the entry we're working on stays marked (see `heap_recorder_mark`) across any GC that happens while we don't
558
+ // have the GVL.
559
+ //
560
+ // Note as well that `ruby_weak_map_set_may_lose_gvl_and_allocate_objects` below can lose the GVL (and, on older
561
+ // Rubies, allocate). This function gets called while holding the heap recorder lock so that no other heap recorder
562
+ // operation can concurrently execute with us in that window (including recording more allocations, which on older Rubies
563
+ // would otherwise add entries to the very buffer we're draining).
564
+ while (heap_recorder->pending_recordings_count > 0) {
565
+ pending_recording pending = heap_recorder->pending_recordings[heap_recorder->pending_recordings_count - 1];
566
+
567
+ ruby_weak_map_set_may_lose_gvl_and_allocate_objects(heap_recorder->weak_objects, LONG2FIX(pending.record_id), pending.object_ref);
568
+
569
+ // After here, we can no longer lose the GVL until we loop around again, so these steps are "atomic"
570
+
571
+ commit_recording(heap_recorder, pending);
572
+ heap_recorder->pending_recordings_count--;
573
+ heap_recorder->stats_lifetime.deferred_recordings_committed++;
574
+ }
575
+
576
+ return Qnil;
577
+ }
578
+
579
+ // Mark the Ruby objects the heap recorder holds on to.
580
+ //
581
+ // Note that the objects we track are deliberately **not** marked here: `weak_objects` holds only weak references
582
+ // to them, which is exactly what lets us detect that they've been garbage collected.
583
+ void heap_recorder_mark(heap_recorder *heap_recorder) {
370
584
  if (heap_recorder == NULL) {
371
- return Qnil;
585
+ return;
586
+ }
587
+
588
+ // Mark pending recordings while they're waiting to be committed, otherwise it won't be safe to read them later.
589
+ // We would not mind if they are GC'd, but we would need to know that reliably and we can't.
590
+ for (uint i = 0; i < heap_recorder->pending_recordings_count; i++) {
591
+ rb_gc_mark(heap_recorder->pending_recordings[i].object_ref);
372
592
  }
373
593
 
374
- recording active_recording = heap_recorder->active_recording;
594
+ rb_gc_mark(heap_recorder->weak_objects);
595
+ }
375
596
 
376
- if (active_recording.object_record == NULL) {
377
- // Recording ended without having been started?
378
- rb_raise(rb_eRuntimeError, "Ended a heap recording that was not started");
597
+ // NOTE: This function needs and assumes it gets called with the GVL being held.
598
+ // But importantly **some of the operations inside `st_object_record_update` may cause a thread switch**,
599
+ // so we can't assume a single update happens in a single "atomic" step -- other threads may get some running time
600
+ // in the meanwhile.
601
+ static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update) {
602
+ if (full_update) {
603
+ // A full update runs as part of serialization and can't be skipped, so we wait for our turn if needed
604
+ heap_recorder_lock(heap_recorder);
605
+ } else if (!heap_recorder_try_lock(heap_recorder)) {
606
+ // Non-full updates are optional, so let's walk away
607
+ heap_recorder->stats_lifetime.updates_skipped_concurrent++;
608
+ return;
379
609
  }
380
- // From now on, mark the global active recording as invalid so we can short-circuit at any point
381
- // and not end up with a still active recording. the local active_recording still holds the
382
- // data required for committing though.
383
- heap_recorder->active_recording = (recording) {0};
384
610
 
385
- if (active_recording.object_record == &SKIPPED_RECORD) { // special marker when we decided to skip due to sampling
611
+ heap_recorder_update_locked_args args = {.heap_recorder = heap_recorder, .full_update = full_update};
612
+ rb_ensure(heap_recorder_update_locked, (VALUE) &args, heap_recorder_unlock_ensure, (VALUE) heap_recorder);
613
+ }
614
+
615
+ static VALUE heap_recorder_update_locked(VALUE heap_recorder_update_locked_args_as_value) {
616
+ heap_recorder_update_locked_args *args = (heap_recorder_update_locked_args *) heap_recorder_update_locked_args_as_value;
617
+ heap_recorder *heap_recorder = args->heap_recorder;
618
+ bool full_update = args->full_update;
619
+
620
+ if (heap_recorder->object_records_snapshot != NULL) {
621
+ // While serialization is happening, it runs without the GVL and uses the object_records_snapshot.
622
+ // Although we iterate on a snapshot of object_records, these records point to other data that has not been
623
+ // snapshotted for efficiency reasons (e.g. heap_records). Since updating may invalidate
624
+ // some of that non-snapshotted data, let's refrain from doing updates during iteration. This also enforces the
625
+ // semantic that iteration will operate as a point-in-time snapshot.
386
626
  return Qnil;
387
627
  }
388
628
 
389
- heap_record *heap_record = get_or_create_heap_record(heap_recorder, locations);
629
+ size_t current_gc_gen = rb_gc_count();
630
+ long now_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
631
+
632
+ if (!full_update) {
633
+ if (current_gc_gen == heap_recorder->update_gen) {
634
+ // Are we still in the same GC gen as last update? If so, skip updating since things should not have
635
+ // changed significantly since last time.
636
+ // NOTE: This is mostly a performance decision. I suppose some objects may be cleaned up in intermediate
637
+ // GC steps and sizes may change. But because we have to iterate through all our tracked
638
+ // object records to do an update, let's wait until all steps for a particular GC generation
639
+ // have finished to do so. We may revisit this once we have a better liveness checking mechanism.
640
+ heap_recorder->stats_lifetime.updates_skipped_gcgen++;
641
+ return Qnil;
642
+ }
643
+
644
+ if (now_ns > 0 && (now_ns - heap_recorder->last_update_ns) < MIN_TIME_BETWEEN_HEAP_RECORDER_UPDATES_NS) {
645
+ // We did an update not too long ago. Let's skip this one to avoid over-taxing the system.
646
+ heap_recorder->stats_lifetime.updates_skipped_time++;
647
+ return Qnil;
648
+ }
649
+ }
390
650
 
391
- // And then commit the new allocation.
392
- commit_recording(heap_recorder, heap_record, active_recording);
651
+ // Reset last update stats, we'll be building them from scratch during the st_foreach call below
652
+ heap_recorder->stats_last_update = (struct stats_last_update) {0};
653
+
654
+ heap_recorder->update_gen = current_gc_gen;
655
+ heap_recorder->update_include_old = full_update;
656
+
657
+ st_foreach(heap_recorder->object_records, st_object_record_update, (st_data_t) heap_recorder);
658
+
659
+ heap_recorder->last_update_ns = now_ns;
660
+ heap_recorder->stats_lifetime.updates_successful++;
661
+
662
+ // Lifetime stats updating
663
+ if (!full_update) {
664
+ heap_recorder->stats_lifetime.ewma_young_objects_alive = ewma_stat(heap_recorder->stats_lifetime.ewma_young_objects_alive, heap_recorder->stats_last_update.objects_alive);
665
+ heap_recorder->stats_lifetime.ewma_young_objects_dead = ewma_stat(heap_recorder->stats_lifetime.ewma_young_objects_dead, heap_recorder->stats_last_update.objects_dead);
666
+ heap_recorder->stats_lifetime.ewma_young_objects_skipped = ewma_stat(heap_recorder->stats_lifetime.ewma_young_objects_skipped, heap_recorder->stats_last_update.objects_skipped);
667
+ } else {
668
+ heap_recorder->stats_lifetime.ewma_objects_alive = ewma_stat(heap_recorder->stats_lifetime.ewma_objects_alive, heap_recorder->stats_last_update.objects_alive);
669
+ heap_recorder->stats_lifetime.ewma_objects_dead = ewma_stat(heap_recorder->stats_lifetime.ewma_objects_dead, heap_recorder->stats_last_update.objects_dead);
670
+ heap_recorder->stats_lifetime.ewma_objects_skipped = ewma_stat(heap_recorder->stats_lifetime.ewma_objects_skipped, heap_recorder->stats_last_update.objects_skipped);
671
+ }
393
672
 
394
673
  return Qnil;
395
674
  }
@@ -399,21 +678,16 @@ void heap_recorder_prepare_iteration(heap_recorder *heap_recorder) {
399
678
  return;
400
679
  }
401
680
 
402
- heap_recorder->iteration_gen = rb_gc_count();
403
-
404
681
  if (heap_recorder->object_records_snapshot != NULL) {
405
682
  // we could trivially handle this but we raise to highlight and catch unexpected usages.
406
- rb_raise(rb_eRuntimeError, "New heap recorder iteration prepared without the previous one having been finished.");
683
+ raise_error(rb_eRuntimeError, "New heap recorder iteration prepared without the previous one having been finished.");
407
684
  }
408
685
 
409
- // Reset last update stats, we'll be building them from scratch during the st_foreach call below
410
- heap_recorder->stats_last_update = (struct stats_last_update) {};
411
-
412
- st_foreach(heap_recorder->object_records, st_object_record_update, (st_data_t) heap_recorder);
686
+ heap_recorder_update(heap_recorder, /* full_update: */ true);
413
687
 
414
688
  heap_recorder->object_records_snapshot = st_copy(heap_recorder->object_records);
415
689
  if (heap_recorder->object_records_snapshot == NULL) {
416
- rb_raise(rb_eRuntimeError, "Failed to create heap snapshot.");
690
+ raise_error(rb_eRuntimeError, "Failed to create heap snapshot.");
417
691
  }
418
692
  }
419
693
 
@@ -424,7 +698,7 @@ void heap_recorder_finish_iteration(heap_recorder *heap_recorder) {
424
698
 
425
699
  if (heap_recorder->object_records_snapshot == NULL) {
426
700
  // we could trivially handle this but we raise to highlight and catch unexpected usages.
427
- rb_raise(rb_eRuntimeError, "Heap recorder iteration finished without having been prepared.");
701
+ raise_error(rb_eRuntimeError, "Heap recorder iteration finished without having been prepared.");
428
702
  }
429
703
 
430
704
  st_free_table(heap_recorder->object_records_snapshot);
@@ -466,96 +740,103 @@ bool heap_recorder_for_each_live_object(
466
740
 
467
741
  VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder) {
468
742
  VALUE arguments[] = {
469
- ID2SYM(rb_intern("num_object_records")), /* => */ LONG2NUM(heap_recorder->object_records->num_entries),
470
- ID2SYM(rb_intern("num_heap_records")), /* => */ LONG2NUM(heap_recorder->heap_records->num_entries),
743
+ ID2SYM(rb_intern("num_object_records")), /* => */ ULONG2NUM(heap_recorder->object_records->num_entries),
744
+ ID2SYM(rb_intern("num_heap_records")), /* => */ ULONG2NUM(heap_recorder->heap_records->num_entries),
745
+ ID2SYM(rb_intern("pending_recordings_count")), /* => */ ULONG2NUM(heap_recorder->pending_recordings_count),
471
746
 
472
747
  // Stats as of last update
473
- ID2SYM(rb_intern("last_update_objects_alive")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_alive),
474
- ID2SYM(rb_intern("last_update_objects_dead")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_dead),
475
- ID2SYM(rb_intern("last_update_objects_skipped")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_skipped),
476
- ID2SYM(rb_intern("last_update_objects_frozen")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_frozen),
748
+ ID2SYM(rb_intern("last_update_objects_alive")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_alive),
749
+ ID2SYM(rb_intern("last_update_objects_dead")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_dead),
750
+ ID2SYM(rb_intern("last_update_objects_skipped")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_skipped),
751
+ ID2SYM(rb_intern("last_update_objects_frozen")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_frozen),
752
+
753
+ // Lifetime stats
754
+ ID2SYM(rb_intern("lifetime_updates_successful")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_successful),
755
+ ID2SYM(rb_intern("lifetime_updates_skipped_concurrent")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_skipped_concurrent),
756
+ ID2SYM(rb_intern("lifetime_updates_skipped_gcgen")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_skipped_gcgen),
757
+ ID2SYM(rb_intern("lifetime_updates_skipped_time")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_skipped_time),
758
+ ID2SYM(rb_intern("lifetime_ewma_young_objects_alive")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_alive),
759
+ ID2SYM(rb_intern("lifetime_ewma_young_objects_dead")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_dead),
760
+ // Note: Here "young" refers to the young update; objects skipped includes non-young objects
761
+ ID2SYM(rb_intern("lifetime_ewma_young_objects_skipped")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_skipped),
762
+ ID2SYM(rb_intern("lifetime_ewma_objects_alive")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_alive),
763
+ ID2SYM(rb_intern("lifetime_ewma_objects_dead")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_dead),
764
+ ID2SYM(rb_intern("lifetime_ewma_objects_skipped")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_skipped),
765
+
766
+ ID2SYM(rb_intern("lifetime_deferred_recordings_skipped_buffer_full")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.deferred_recordings_skipped_buffer_full),
767
+ ID2SYM(rb_intern("lifetime_deferred_recordings_committed")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.deferred_recordings_committed),
477
768
  };
478
769
  VALUE hash = rb_hash_new();
479
770
  for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
771
+
480
772
  return hash;
481
773
  }
482
774
 
483
- void heap_recorder_testonly_assert_hash_matches(ddog_prof_Slice_Location locations) {
484
- heap_stack *stack = heap_stack_new(locations);
485
- heap_record_key stack_based_key = (heap_record_key) {
486
- .type = HEAP_STACK,
487
- .heap_stack = stack,
488
- };
489
- heap_record_key location_based_key = (heap_record_key) {
490
- .type = LOCATION_SLICE,
491
- .location_slice = &locations,
492
- };
493
-
494
- st_index_t stack_hash = heap_record_key_hash_st((st_data_t) &stack_based_key);
495
- st_index_t location_hash = heap_record_key_hash_st((st_data_t) &location_based_key);
496
-
497
- heap_stack_free(stack);
498
-
499
- if (stack_hash != location_hash) {
500
- rb_raise(rb_eRuntimeError, "Heap record key hashes built from the same locations differ. stack_based_hash=%"PRI_VALUE_PREFIX"u location_based_hash=%"PRI_VALUE_PREFIX"u", stack_hash, location_hash);
501
- }
502
- }
775
+ typedef struct {
776
+ heap_recorder *recorder;
777
+ VALUE debug_ary;
778
+ } debug_context;
503
779
 
504
780
  VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder) {
505
781
  if (heap_recorder == NULL) {
506
- return rb_str_new2("NULL heap_recorder");
782
+ raise_error(rb_eArgError, "heap_recorder is NULL");
507
783
  }
508
784
 
509
- VALUE debug_str = rb_str_new2("object records:\n");
510
- st_foreach(heap_recorder->object_records, st_object_records_debug, (st_data_t) debug_str);
511
- return debug_str;
785
+ VALUE debug_ary = rb_ary_new();
786
+ debug_context context = (debug_context) {.recorder = heap_recorder, .debug_ary = debug_ary};
787
+ st_foreach(heap_recorder->object_records, st_object_records_debug, (st_data_t) &context);
788
+
789
+ return rb_ary_new_from_args(2,
790
+ rb_ary_new_from_args(2, ID2SYM(rb_intern("records")), debug_ary),
791
+ rb_ary_new_from_args(2, ID2SYM(rb_intern("state")), heap_recorder_state_snapshot(heap_recorder))
792
+ );
512
793
  }
513
794
 
514
795
  // ==========================
515
796
  // Heap Recorder Internal API
516
797
  // ==========================
517
- static int st_heap_record_entry_free(st_data_t key, st_data_t value, DDTRACE_UNUSED st_data_t extra_arg) {
518
- heap_record_key *record_key = (heap_record_key*) key;
519
- heap_record_key_free(record_key);
520
- heap_record_free((heap_record *) value);
798
+ // NOTE: Only expected to be used from heap_recorder_free, which will separately destroy the string table
799
+ static int st_heap_record_entry_free_no_unintern(st_data_t key, DDTRACE_UNUSED st_data_t value, st_data_t extra_arg) {
800
+ heap_recorder *recorder = (heap_recorder *) extra_arg;
801
+ heap_record_free(recorder, (heap_record *) key, false);
521
802
  return ST_DELETE;
522
803
  }
523
804
 
524
- static int st_object_record_entry_free(DDTRACE_UNUSED st_data_t key, st_data_t value, DDTRACE_UNUSED st_data_t extra_arg) {
525
- object_record_free((object_record *) value);
805
+ // NOTE: Only expected to be used from heap_recorder_free, which will separately destroy the string table
806
+ static int st_object_record_entry_free_no_unintern(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra_arg) {
807
+ heap_recorder *recorder = (heap_recorder *) extra_arg;
808
+ object_record_free(recorder, (object_record *) value, false);
526
809
  return ST_DELETE;
527
810
  }
528
811
 
529
- // Check to see if an object should not be included in a heap recorder iteration.
530
- // This centralizes the checking logic to ensure it's equally applied between
531
- // preparation and iteration codepaths.
532
- static inline bool should_exclude_from_iteration(object_record *obj_record) {
533
- return obj_record->object_data.gen_age < ITERATION_MIN_AGE;
534
- }
535
-
812
+ // NOTE: Some operations inside this function can cause the GVL to be released! Plan accordingly.
536
813
  static int st_object_record_update(st_data_t key, st_data_t value, st_data_t extra_arg) {
537
- long obj_id = (long) key;
814
+ long record_id = (long) key;
538
815
  object_record *record = (object_record*) value;
539
816
  heap_recorder *recorder = (heap_recorder*) extra_arg;
540
817
 
541
- VALUE ref;
542
-
543
- size_t iteration_gen = recorder->iteration_gen;
818
+ size_t update_gen = recorder->update_gen;
544
819
  size_t alloc_gen = record->object_data.alloc_gen;
545
820
  // Guard against potential overflows given unsigned types here.
546
- record->object_data.gen_age = alloc_gen < iteration_gen ? iteration_gen - alloc_gen : 0;
821
+ record->object_data.gen_age = alloc_gen < update_gen ? update_gen - alloc_gen : 0;
822
+
823
+ if (record->object_data.gen_age == 0) {
824
+ // Objects that belong to the current GC gen have not had a chance to be cleaned up yet
825
+ // and won't show up in the iteration anyway so no point in checking their liveness/sizes.
826
+ recorder->stats_last_update.objects_skipped++;
827
+ return ST_CONTINUE;
828
+ }
547
829
 
548
- if (should_exclude_from_iteration(record)) {
549
- // If an object won't be included in the current iteration, there's
550
- // no point checking for liveness or updating its size, so exit early.
551
- // NOTE: This means that there should be an equivalent check during actual
552
- // iteration otherwise we'd iterate/expose stale object data.
830
+ if (!recorder->update_include_old && record->object_data.gen_age >= OLD_AGE) {
831
+ // The current update is not including old objects but this record is for an old object, skip its update.
553
832
  recorder->stats_last_update.objects_skipped++;
554
833
  return ST_CONTINUE;
555
834
  }
556
835
 
557
- if (!ruby_ref_from_id(LONG2NUM(obj_id), &ref)) {
558
- // Id no longer associated with a valid ref. Need to delete this object record!
836
+ // Note: This function call can cause the GVL to be released
837
+ VALUE ref = ruby_weak_map_get_may_lose_gvl(recorder->weak_objects, LONG2FIX(record_id));
838
+ if (ref == Qnil) {
839
+ // The weak reference is gone, meaning the object was garbage collected. Need to delete this object record!
559
840
  on_committed_object_record_cleanup(recorder, record);
560
841
  recorder->stats_last_update.objects_dead++;
561
842
  return ST_DELETE;
@@ -563,49 +844,23 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
563
844
 
564
845
  // If we got this far, then we found a valid live object for the tracked id.
565
846
 
566
- #ifdef CAN_APPLY_GC_FORCE_RECYCLE_BUG_WORKAROUND
567
- // If we are in a ruby version that has a working rb_gc_force_recycle implementation,
568
- // its usage may lead to an object being re-used outside of the typical GC cycle.
569
- //
570
- // This re-use is in theory invisible to us and would mean that the ref from which we
571
- // collected the object_record metadata may not be the same as the current ref and
572
- // thus any further reporting would be innacurately attributed to stale metadata.
573
- //
574
- // In practice, there is a way for us to notice that this happened because of a bug
575
- // in the implementation of rb_gc_force_recycle. Our heap profiler relies on object
576
- // ids and id2ref to detect whether objects are still alive. Turns out that when an
577
- // object with an id is re-used via rb_gc_force_recycle, it will "inherit" the ID
578
- // of the old object but it will NOT have the FL_SEEN_OBJ_ID as per the experiment
579
- // in https://github.com/DataDog/dd-trace-rb/pull/3360#discussion_r1442823517
580
- //
581
- // Thus, if we detect that the ref we just resolved above is missing this flag, we can
582
- // safely say re-use happened and thus treat it as an implicit free of the object
583
- // we were tracking (the original one which got recycled).
584
- if (RB_FL_ABLE(ref) && !RB_FL_TEST(ref, RUBY_FL_SEEN_OBJ_ID)) {
585
-
586
- // NOTE: We don't really need to set this flag for heap recorder to work correctly
587
- // but doing so partially mitigates a bug in runtimes with working rb_gc_force_recycle
588
- // which leads to broken invariants and leaking of entries in obj_to_id and id_to_obj
589
- // tables in objspace. We already do the same thing when we sample a recycled object,
590
- // here we apply it as well to objects that replace recycled objects that were being
591
- // tracked. More details in https://github.com/DataDog/dd-trace-rb/pull/3366
592
- RB_FL_SET(ref, RUBY_FL_SEEN_OBJ_ID);
593
-
594
- on_committed_object_record_cleanup(recorder, record);
595
- recorder->stats_last_update.objects_dead++;
596
- return ST_DELETE;
597
- }
598
-
599
- #endif
600
-
601
- if (recorder->size_enabled && !record->object_data.is_frozen) {
847
+ if (
848
+ recorder->size_enabled &&
849
+ recorder->update_include_old && // We only update sizes when doing a full update
850
+ !record->object_data.is_frozen
851
+ ) {
602
852
  // if we were asked to update sizes and this object was not already seen as being frozen,
603
853
  // update size again.
604
- record->object_data.size = ruby_obj_memsize_of(ref);
854
+ record->object_data.size = ruby_obj_memsize_of(ref); // Note: This function call can cause the GVL to be released... maybe?
855
+ // (With T_DATA for instance, since it can be a custom method supplied by extensions)
605
856
  // Check if it's now frozen so we skip a size update next time
606
857
  record->object_data.is_frozen = RB_OBJ_FROZEN(ref);
607
858
  }
608
859
 
860
+ // Ensure that ref is kept on the stack so the Ruby garbage collector does not try to clean up the object before this
861
+ // point.
862
+ RB_GC_GUARD(ref);
863
+
609
864
  recorder->stats_last_update.objects_alive++;
610
865
  if (record->object_data.is_frozen) {
611
866
  recorder->stats_last_update.objects_frozen++;
@@ -617,15 +872,13 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
617
872
  // WARN: This can get called outside the GVL. NO HEAP ALLOCATIONS OR EXCEPTIONS ARE ALLOWED.
618
873
  static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra) {
619
874
  object_record *record = (object_record*) value;
620
- const heap_stack *stack = record->heap_record->stack;
875
+ const heap_record *stack = record->heap_record;
621
876
  iteration_context *context = (iteration_context*) extra;
622
877
 
623
878
  const heap_recorder *recorder = context->heap_recorder;
624
879
 
625
- if (should_exclude_from_iteration(record)) {
880
+ if (record->object_data.gen_age < ITERATION_MIN_AGE) {
626
881
  // Skip objects that should not be included in iteration
627
- // NOTE: This matches the short-circuiting condition in st_object_record_update
628
- // and prevents iteration over stale objects.
629
882
  return ST_CONTINUE;
630
883
  }
631
884
 
@@ -633,10 +886,12 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
633
886
  for (uint16_t i = 0; i < stack->frames_len; i++) {
634
887
  const heap_frame *frame = &stack->frames[i];
635
888
  locations[i] = (ddog_prof_Location) {
636
- .mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C("")},
889
+ .mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
637
890
  .function = {
638
- .name = {.ptr = frame->name, .len = strlen(frame->name)},
639
- .filename = {.ptr = frame->filename, .len = strlen(frame->filename)},
891
+ .name = DDOG_CHARSLICE_C(""),
892
+ .name_id = frame->name,
893
+ .filename = DDOG_CHARSLICE_C(""),
894
+ .filename_id = frame->filename,
640
895
  },
641
896
  .line = frame->line,
642
897
  };
@@ -646,6 +901,7 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
646
901
  iteration_data.object_data = record->object_data;
647
902
  iteration_data.locations = (ddog_prof_Slice_Location) {.ptr = locations, .len = stack->frames_len};
648
903
 
904
+ // This is expected to be StackRecorder's add_heap_sample_to_active_profile_without_gvl
649
905
  if (!context->for_each_callback(iteration_data, context->for_each_callback_extra_arg)) {
650
906
  return ST_STOP;
651
907
  }
@@ -654,113 +910,53 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
654
910
  }
655
911
 
656
912
  static int st_object_records_debug(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra) {
657
- VALUE debug_str = (VALUE) extra;
913
+ debug_context *context = (debug_context*) extra;
658
914
 
659
915
  object_record *record = (object_record*) value;
660
916
 
661
- rb_str_catf(debug_str, "%"PRIsVALUE"\n", object_record_inspect(record));
917
+ rb_ary_push(context->debug_ary, object_record_inspect(context->recorder, record));
662
918
 
663
919
  return ST_CONTINUE;
664
920
  }
665
921
 
666
- // Struct holding data required for an update operation on heap_records
667
- typedef struct {
668
- // [in] The recording containing the new object record we want to add.
669
- // NOTE: Transfer of ownership of the contained object record is assumed, do not re-use it after call to ::update_object_record_entry
670
- recording recording;
671
-
672
- // [in] The heap recorder where the update is happening.
673
- heap_recorder *heap_recorder;
674
- } object_record_update_data;
675
-
676
- static int update_object_record_entry(DDTRACE_UNUSED st_data_t *key, st_data_t *value, st_data_t data, int existing) {
677
- object_record_update_data *update_data = (object_record_update_data*) data;
678
- recording recording = update_data->recording;
679
- object_record *new_object_record = recording.object_record;
680
- if (existing) {
681
- object_record *existing_record = (object_record*) (*value);
682
- if (recording.did_recycle_workaround) {
683
- // In this case, it's possible for an object id to be re-used and we were lucky enough to have
684
- // sampled both the original object and the replacement so cleanup the old one and replace it with
685
- // the new object_record (i.e. treat this as a combined free+allocation).
686
- on_committed_object_record_cleanup(update_data->heap_recorder, existing_record);
687
- } else {
688
- // This is not supposed to happen, raising...
689
- VALUE existing_inspect = object_record_inspect(existing_record);
690
- VALUE new_inspect = object_record_inspect(new_object_record);
691
- rb_raise(rb_eRuntimeError, "Object ids are supposed to be unique. We got 2 allocation recordings with "
692
- "the same id. previous=%"PRIsVALUE" new=%"PRIsVALUE, existing_inspect, new_inspect);
693
- }
694
- }
695
- // Always carry on with the update, we want the new record to be there at the end
696
- (*value) = (st_data_t) new_object_record;
697
- return ST_CONTINUE;
698
- }
699
-
700
- static void commit_recording(heap_recorder *heap_recorder, heap_record *heap_record, recording recording) {
701
- // Link the object record with the corresponding heap record. This was the last remaining thing we
702
- // needed to fully build the object_record.
703
- recording.object_record->heap_record = heap_record;
922
+ static void inc_tracked_objects_or_fail(heap_record *heap_record) {
704
923
  if (heap_record->num_tracked_objects == UINT32_MAX) {
705
- rb_raise(rb_eRuntimeError, "Reached maximum number of tracked objects for heap record");
924
+ raise_error(rb_eRuntimeError, "Reached maximum number of tracked objects for heap record");
706
925
  }
707
926
  heap_record->num_tracked_objects++;
927
+ }
708
928
 
709
- // Update object_records with the data for this new recording
710
- object_record_update_data update_data = (object_record_update_data) {
711
- .heap_recorder = heap_recorder,
712
- .recording = recording,
713
- };
714
- st_update(heap_recorder->object_records, recording.object_record->obj_id, update_object_record_entry, (st_data_t) &update_data);
929
+ static void commit_recording(heap_recorder *heap_recorder, pending_recording pending) {
930
+ object_record *new_record = object_record_new(pending);
931
+
932
+ int existing = st_insert(heap_recorder->object_records, new_record->record_id, (st_data_t) new_record);
933
+ if (existing) {
934
+ raise_error(rb_eRuntimeError, "Unexpected record with the same record_id in heap_recorder->object_records");
935
+ }
715
936
  }
716
937
 
717
- // Struct holding data required for an update operation on heap_records
718
- typedef struct {
719
- // [in] The locations we did this update with
720
- ddog_prof_Slice_Location locations;
721
- // [out] Pointer that will be updated to the updated heap record to prevent having to do
722
- // another lookup to access the updated heap record.
723
- heap_record **record;
724
- } heap_record_update_data;
725
-
726
- // This function assumes ownership of stack_data is passed on to it so it'll either transfer ownership or clean-up.
727
938
  static int update_heap_record_entry_with_new_allocation(st_data_t *key, st_data_t *value, st_data_t data, int existing) {
728
- heap_record_update_data *update_data = (heap_record_update_data*) data;
939
+ heap_record **new_or_existing_record = (heap_record **) data;
940
+ (*new_or_existing_record) = (heap_record *) (*key);
729
941
 
730
942
  if (!existing) {
731
- // there was no matching heap record so lets create a new one...
732
- // we need to initialize a heap_record_key with a new stack and use that for the key storage. We can't use the
733
- // locations-based key we used for the update call because we don't own its lifecycle. So we create a new
734
- // heap stack and will pass ownership of it to the heap_record.
735
- heap_stack *stack = heap_stack_new(update_data->locations);
736
- (*key) = (st_data_t) heap_record_key_new(stack);
737
- (*value) = (st_data_t) heap_record_new(stack);
943
+ (*value) = (st_data_t) true; // We're only using this hash as a set
738
944
  }
739
945
 
740
- heap_record *record = (heap_record*) (*value);
741
- (*update_data->record) = record;
742
-
743
946
  return ST_CONTINUE;
744
947
  }
745
948
 
746
949
  static heap_record* get_or_create_heap_record(heap_recorder *heap_recorder, ddog_prof_Slice_Location locations) {
747
- // For performance reasons we use a stack-allocated location-slice based key. This allows us
748
- // to do allocation-free lookups and reuse of a matching existing heap record.
749
- // NOTE: If we end up creating a new record, we'll create a heap-allocated key we own and use that for storage
750
- // instead of this one.
751
- heap_record_key lookup_key = (heap_record_key) {
752
- .type = LOCATION_SLICE,
753
- .location_slice = &locations,
754
- };
950
+ // See note on "heap_records" definition for why we keep this map.
951
+ heap_record *stack = heap_record_new(heap_recorder, locations);
755
952
 
756
- heap_record *heap_record = NULL;
757
- heap_record_update_data update_data = (heap_record_update_data) {
758
- .locations = locations,
759
- .record = &heap_record,
760
- };
761
- st_update(heap_recorder->heap_records, (st_data_t) &lookup_key, update_heap_record_entry_with_new_allocation, (st_data_t) &update_data);
953
+ heap_record *new_or_existing_record = NULL; // Will be set inside update_heap_record_entry_with_new_allocation
954
+ int existing = st_update(heap_recorder->heap_records, (st_data_t) stack, update_heap_record_entry_with_new_allocation, (st_data_t) &new_or_existing_record);
955
+ if (existing) {
956
+ heap_record_free(heap_recorder, stack, true);
957
+ }
762
958
 
763
- return heap_record;
959
+ return new_or_existing_record;
764
960
  }
765
961
 
766
962
  static void cleanup_heap_record_if_unused(heap_recorder *heap_recorder, heap_record *heap_record) {
@@ -769,18 +965,10 @@ static void cleanup_heap_record_if_unused(heap_recorder *heap_recorder, heap_rec
769
965
  return;
770
966
  }
771
967
 
772
- heap_record_key heap_key = (heap_record_key) {
773
- .type = HEAP_STACK,
774
- .heap_stack = heap_record->stack,
968
+ if (!st_delete(heap_recorder->heap_records, (st_data_t*) &heap_record, NULL)) {
969
+ raise_error(rb_eRuntimeError, "Attempted to cleanup an untracked heap_record");
775
970
  };
776
- // We need to access the deleted key to free it since we gave ownership of the keys to the hash.
777
- // st_delete will change this pointer to point to the removed key if one is found.
778
- heap_record_key *deleted_key = &heap_key;
779
- if (!st_delete(heap_recorder->heap_records, (st_data_t*) &deleted_key, NULL)) {
780
- rb_raise(rb_eRuntimeError, "Attempted to cleanup an untracked heap_record");
781
- };
782
- heap_record_key_free(deleted_key);
783
- heap_record_free(heap_record);
971
+ heap_record_free(heap_recorder, heap_record, true);
784
972
  }
785
973
 
786
974
  static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, object_record *record) {
@@ -788,72 +976,61 @@ static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, obj
788
976
  // (See PROF-10656 Datadog-internal for details). Just in case, I've sprinkled a bunch of NULL tests in this function for now.
789
977
  // Once we figure out the issue we can get rid of them again.
790
978
 
791
- if (heap_recorder == NULL) rb_raise(rb_eRuntimeError, "heap_recorder was NULL in on_committed_object_record_cleanup");
792
- if (heap_recorder->heap_records == NULL) rb_raise(rb_eRuntimeError, "heap_recorder->heap_records was NULL in on_committed_object_record_cleanup");
793
- if (record == NULL) rb_raise(rb_eRuntimeError, "record was NULL in on_committed_object_record_cleanup");
979
+ if (heap_recorder == NULL) raise_error(rb_eRuntimeError, "heap_recorder was NULL in on_committed_object_record_cleanup");
980
+ if (heap_recorder->heap_records == NULL) raise_error(rb_eRuntimeError, "heap_recorder->heap_records was NULL in on_committed_object_record_cleanup");
981
+ if (record == NULL) raise_error(rb_eRuntimeError, "record was NULL in on_committed_object_record_cleanup");
794
982
 
795
983
  // Starting with the associated heap record. There will now be one less tracked object pointing to it
796
984
  heap_record *heap_record = record->heap_record;
797
985
 
798
- if (heap_record == NULL) rb_raise(rb_eRuntimeError, "heap_record was NULL in on_committed_object_record_cleanup");
799
- if (heap_record->stack == NULL) rb_raise(rb_eRuntimeError, "heap_record->stack was NULL in on_committed_object_record_cleanup");
986
+ if (heap_record == NULL) raise_error(rb_eRuntimeError, "heap_record was NULL in on_committed_object_record_cleanup");
800
987
 
801
988
  heap_record->num_tracked_objects--;
802
989
 
803
990
  // One less object using this heap record, it may have become unused...
804
991
  cleanup_heap_record_if_unused(heap_recorder, heap_record);
805
992
 
806
- object_record_free(record);
807
- }
808
-
809
- // ===============
810
- // Heap Record API
811
- // ===============
812
- heap_record* heap_record_new(heap_stack *stack) {
813
- heap_record *record = ruby_xcalloc(1, sizeof(heap_record));
814
- record->num_tracked_objects = 0;
815
- record->stack = stack;
816
- return record;
817
- }
818
-
819
- void heap_record_free(heap_record *record) {
820
- heap_stack_free(record->stack);
821
- ruby_xfree(record);
993
+ object_record_free(heap_recorder, record, true);
822
994
  }
823
995
 
824
-
825
996
  // =================
826
997
  // Object Record API
827
998
  // =================
828
- object_record* object_record_new(long obj_id, heap_record *heap_record, live_object_data object_data) {
829
- object_record *record = ruby_xcalloc(1, sizeof(object_record));
830
- record->obj_id = obj_id;
831
- record->heap_record = heap_record;
832
- record->object_data = object_data;
999
+ static object_record* object_record_new(pending_recording pending) {
1000
+ object_record *record = calloc(1, sizeof(object_record)); // See "note on calloc vs ruby_xcalloc use" above
1001
+ record->record_id = pending.record_id;
1002
+ // Link the object record with the corresponding heap record
1003
+ record->heap_record = pending.heap_record;
1004
+ record->object_data = pending.object_data;
833
1005
  return record;
834
1006
  }
835
1007
 
836
- void object_record_free(object_record *record) {
837
- if (record->object_data.class != NULL) {
838
- ruby_xfree(record->object_data.class);
839
- }
840
- ruby_xfree(record);
1008
+ static void object_record_free(heap_recorder *recorder, object_record *record, bool should_unintern) {
1009
+ // When tearing down the whole recorder state, we skip uninterning as it's not needed (the managed
1010
+ // string table is going to be destroyed anyway) and if there's any failures we can't raise
1011
+ // in the middle of a dfree callback.
1012
+ if (should_unintern) unintern_or_raise(recorder, record->object_data.class);
1013
+
1014
+ free(record); // See "note on calloc vs ruby_xcalloc use" above
841
1015
  }
842
1016
 
843
- VALUE object_record_inspect(object_record *record) {
844
- heap_frame top_frame = record->heap_record->stack->frames[0];
1017
+ static VALUE object_record_inspect(heap_recorder *recorder, object_record *record) {
1018
+ heap_frame top_frame = record->heap_record->frames[0];
1019
+ VALUE filename = get_ruby_string_or_raise(recorder, top_frame.filename);
845
1020
  live_object_data object_data = record->object_data;
846
- VALUE inspect = rb_sprintf("obj_id=%ld weight=%d size=%zu location=%s:%d alloc_gen=%zu gen_age=%zu frozen=%d ",
847
- record->obj_id, object_data.weight, object_data.size, top_frame.filename,
1021
+
1022
+ VALUE inspect = rb_sprintf("record_id=%ld weight=%d size=%zu location=%"PRIsVALUE":%d alloc_gen=%zu gen_age=%zu frozen=%d ",
1023
+ record->record_id, object_data.weight, object_data.size, filename,
848
1024
  (int) top_frame.line, object_data.alloc_gen, object_data.gen_age, object_data.is_frozen);
849
1025
 
850
- const char *class = record->object_data.class;
851
- if (class != NULL) {
852
- rb_str_catf(inspect, "class=%s ", class);
1026
+ if (record->object_data.class.value > 0) {
1027
+ VALUE class = get_ruby_string_or_raise(recorder, record->object_data.class);
1028
+
1029
+ rb_str_catf(inspect, "class=%"PRIsVALUE" ", class);
853
1030
  }
854
- VALUE ref;
855
1031
 
856
- if (!ruby_ref_from_id(LONG2NUM(record->obj_id), &ref)) {
1032
+ VALUE ref = ruby_weak_map_get_may_lose_gvl(recorder->weak_objects, LONG2FIX(record->record_id));
1033
+ if (ref == Qnil) {
857
1034
  rb_str_catf(inspect, "object=<invalid>");
858
1035
  } else {
859
1036
  rb_str_catf(inspect, "value=%p ", (void *) ref);
@@ -869,221 +1046,198 @@ VALUE object_record_inspect(object_record *record) {
869
1046
  }
870
1047
 
871
1048
  // ==============
872
- // Heap Frame API
873
- // ==============
874
- int heap_frame_cmp(heap_frame *f1, heap_frame *f2) {
875
- int line_diff = (int) (f1->line - f2->line);
876
- if (line_diff != 0) {
877
- return line_diff;
878
- }
879
- int cmp = strcmp(f1->name, f2->name);
880
- if (cmp != 0) {
881
- return cmp;
882
- }
883
- return strcmp(f1->filename, f2->filename);
884
- }
885
-
886
- // TODO: Research potential performance improvements around hashing stuff here
887
- // once we have a benchmarking suite.
888
- // Example: Each call to st_hash is calling murmur_finish and we may want
889
- // to only finish once per structure, not per field?
890
- // Example: There may be a more efficient hashing for line that is not the
891
- // generic st_hash algorithm?
892
-
893
- // WARN: Must be kept in-sync with ::char_slice_hash
894
- st_index_t string_hash(char *str, st_index_t seed) {
895
- return st_hash(str, strlen(str), seed);
896
- }
897
-
898
- // WARN: Must be kept in-sync with ::string_hash
899
- st_index_t char_slice_hash(ddog_CharSlice char_slice, st_index_t seed) {
900
- return st_hash(char_slice.ptr, char_slice.len, seed);
901
- }
902
-
903
- // WARN: Must be kept in-sync with ::ddog_location_hash
904
- st_index_t heap_frame_hash(heap_frame *frame, st_index_t seed) {
905
- st_index_t hash = string_hash(frame->name, seed);
906
- hash = string_hash(frame->filename, hash);
907
- hash = st_hash(&frame->line, sizeof(frame->line), hash);
908
- return hash;
909
- }
910
-
911
- // WARN: Must be kept in-sync with ::heap_frame_hash
912
- st_index_t ddog_location_hash(ddog_prof_Location location, st_index_t seed) {
913
- st_index_t hash = char_slice_hash(location.function.name, seed);
914
- hash = char_slice_hash(location.function.filename, hash);
915
- // Convert ddog_prof line type to the same type we use for our heap_frames to
916
- // ensure we have compatible hashes
917
- int32_t line_as_int32 = (int32_t) location.line;
918
- hash = st_hash(&line_as_int32, sizeof(line_as_int32), hash);
919
- return hash;
920
- }
921
-
922
- // ==============
923
- // Heap Stack API
1049
+ // Heap Record API
924
1050
  // ==============
925
- heap_stack* heap_stack_new(ddog_prof_Slice_Location locations) {
1051
+ static heap_record* heap_record_new(heap_recorder *recorder, ddog_prof_Slice_Location locations) {
926
1052
  uint16_t frames_len = locations.len;
927
1053
  if (frames_len > MAX_FRAMES_LIMIT) {
928
- // This should not be happening anyway since MAX_FRAMES_LIMIT should be shared with
929
- // the stacktrace construction mechanism. If it happens, lets just raise. This should
930
- // be safe since only allocate with the GVL anyway.
931
- rb_raise(rb_eRuntimeError, "Found stack with more than %d frames (%d)", MAX_FRAMES_LIMIT, frames_len);
1054
+ // This is not expected as MAX_FRAMES_LIMIT is shared with the stacktrace construction mechanism
1055
+ raise_error(rb_eRuntimeError, "Found stack with more than %d frames (%d)", MAX_FRAMES_LIMIT, frames_len);
932
1056
  }
933
- heap_stack *stack = ruby_xcalloc(1, sizeof(heap_stack) + frames_len * sizeof(heap_frame));
1057
+ heap_record *stack = calloc(1, sizeof(heap_record) + frames_len * sizeof(heap_frame)); // See "note on calloc vs ruby_xcalloc use" above
1058
+ stack->num_tracked_objects = 0;
934
1059
  stack->frames_len = frames_len;
1060
+
1061
+ // Intern all these strings...
1062
+ ddog_CharSlice *strings = recorder->reusable_char_slices;
1063
+ // Put all the char slices in the same array; we'll pull them out in the same order from the ids array
935
1064
  for (uint16_t i = 0; i < stack->frames_len; i++) {
936
1065
  const ddog_prof_Location *location = &locations.ptr[i];
1066
+ strings[i] = location->function.filename;
1067
+ strings[i + stack->frames_len] = location->function.name;
1068
+ }
1069
+ intern_all_or_raise(recorder->string_storage, (ddog_prof_Slice_CharSlice) { .ptr = strings, .len = stack->frames_len * 2 }, recorder->reusable_ids, stack->frames_len * 2);
1070
+
1071
+ // ...and record them for later use
1072
+ for (uint16_t i = 0; i < stack->frames_len; i++) {
937
1073
  stack->frames[i] = (heap_frame) {
938
- .name = string_from_char_slice(location->function.name),
939
- .filename = string_from_char_slice(location->function.filename),
1074
+ .filename = recorder->reusable_ids[i],
1075
+ .name = recorder->reusable_ids[i + stack->frames_len],
940
1076
  // ddog_prof_Location is a int64_t. We don't expect to have to profile files with more than
941
1077
  // 2M lines so this cast should be fairly safe?
942
- .line = (int32_t) location->line,
1078
+ .line = (int32_t) locations.ptr[i].line,
943
1079
  };
944
1080
  }
1081
+
945
1082
  return stack;
946
1083
  }
947
1084
 
948
- void heap_stack_free(heap_stack *stack) {
949
- for (uint64_t i = 0; i < stack->frames_len; i++) {
950
- heap_frame *frame = &stack->frames[i];
951
- ruby_xfree(frame->name);
952
- ruby_xfree(frame->filename);
1085
+ static void heap_record_free(heap_recorder *recorder, heap_record *stack, bool should_unintern) {
1086
+ // When tearing down the whole recorder state, we skip uninterning as it's not needed (the managed
1087
+ // string table is going to be destroyed anyway) and if there's any failures we can't raise
1088
+ // in the middle of a dfree callback.
1089
+ if (should_unintern) {
1090
+ ddog_prof_ManagedStringId *ids = recorder->reusable_ids;
1091
+
1092
+ // Put all the ids in the same array; doesn't really matter the order
1093
+ for (u_int16_t i = 0; i < stack->frames_len; i++) {
1094
+ ids[i] = stack->frames[i].filename;
1095
+ ids[i + stack->frames_len] = stack->frames[i].name;
1096
+ }
1097
+ unintern_all_or_raise(recorder, (ddog_prof_Slice_ManagedStringId) { .ptr = ids, .len = stack->frames_len * 2 });
953
1098
  }
954
- ruby_xfree(stack);
955
- }
956
1099
 
957
- // WARN: Must be kept in-sync with ::ddog_location_slice_hash
958
- st_index_t heap_stack_hash(heap_stack *stack, st_index_t seed) {
959
- st_index_t hash = seed;
960
- for (uint64_t i = 0; i < stack->frames_len; i++) {
961
- hash = heap_frame_hash(&stack->frames[i], hash);
962
- }
963
- return hash;
1100
+ free(stack); // See "note on calloc vs ruby_xcalloc use" above
964
1101
  }
965
1102
 
966
- // WARN: Must be kept in-sync with ::heap_stack_hash
967
- st_index_t ddog_location_slice_hash(ddog_prof_Slice_Location locations, st_index_t seed) {
968
- st_index_t hash = seed;
969
- for (uint64_t i = 0; i < locations.len; i++) {
970
- hash = ddog_location_hash(locations.ptr[i], hash);
1103
+ // The entire stack is represented by ids (name, filename) and lines (integers) so we can treat is as just
1104
+ // a big string of bytes and compare it all in one go.
1105
+ static int heap_record_cmp_st(st_data_t key1, st_data_t key2) {
1106
+ heap_record *stack1 = (heap_record*) key1;
1107
+ heap_record *stack2 = (heap_record*) key2;
1108
+
1109
+ if (stack1->frames_len != stack2->frames_len) {
1110
+ return ((int) stack1->frames_len) - ((int) stack2->frames_len);
1111
+ } else {
1112
+ return memcmp(stack1->frames, stack2->frames, stack1->frames_len * sizeof(heap_frame));
971
1113
  }
972
- return hash;
973
1114
  }
974
1115
 
975
- // ===================
976
- // Heap Record Key API
977
- // ===================
978
- heap_record_key* heap_record_key_new(heap_stack *stack) {
979
- heap_record_key *key = ruby_xmalloc(sizeof(heap_record_key));
980
- key->type = HEAP_STACK;
981
- key->heap_stack = stack;
982
- return key;
1116
+ // Initial seed for hash function, same as Ruby uses
1117
+ #define FNV1_32A_INIT 0x811c9dc5
1118
+
1119
+ // The entire stack is represented by ids (name, filename) and lines (integers) so we can treat is as just
1120
+ // a big string of bytes and hash it all in one go.
1121
+ static st_index_t heap_record_hash_st(st_data_t key) {
1122
+ heap_record *stack = (heap_record*) key;
1123
+ return st_hash(stack->frames, stack->frames_len * sizeof(heap_frame), FNV1_32A_INIT);
983
1124
  }
984
1125
 
985
- void heap_record_key_free(heap_record_key *key) {
986
- ruby_xfree(key);
1126
+ static void unintern_or_raise(heap_recorder *recorder, ddog_prof_ManagedStringId id) {
1127
+ if (id.value == 0) return; // Empty string, nothing to do
1128
+
1129
+ ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern(recorder->string_storage, id);
1130
+ if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
1131
+ raise_error(rb_eRuntimeError, "Failed to unintern id: %"PRIsVALUE, get_error_details_and_drop(&result.some));
1132
+ }
987
1133
  }
988
1134
 
989
- static inline size_t heap_record_key_len(heap_record_key *key) {
990
- if (key->type == HEAP_STACK) {
991
- return key->heap_stack->frames_len;
992
- } else {
993
- return key->location_slice->len;
1135
+ static void unintern_all_or_raise(heap_recorder *recorder, ddog_prof_Slice_ManagedStringId ids) {
1136
+ ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern_all(recorder->string_storage, ids);
1137
+ if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
1138
+ raise_error(rb_eRuntimeError, "Failed to unintern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some));
994
1139
  }
995
1140
  }
996
1141
 
997
- static inline int64_t heap_record_key_entry_line(heap_record_key *key, size_t entry_i) {
998
- if (key->type == HEAP_STACK) {
999
- return key->heap_stack->frames[entry_i].line;
1000
- } else {
1001
- return key->location_slice->ptr[entry_i].line;
1142
+ static VALUE get_ruby_string_or_raise(heap_recorder *recorder, ddog_prof_ManagedStringId id) {
1143
+ ddog_StringWrapperResult get_string_result = ddog_prof_ManagedStringStorage_get_string(recorder->string_storage, id);
1144
+ if (get_string_result.tag == DDOG_STRING_WRAPPER_RESULT_ERR) {
1145
+ raise_error(rb_eRuntimeError, "Failed to get string: %"PRIsVALUE, get_error_details_and_drop(&get_string_result.err));
1002
1146
  }
1147
+ VALUE ruby_string = ruby_string_from_vec_u8(get_string_result.ok.message);
1148
+ ddog_StringWrapper_drop((ddog_StringWrapper *) &get_string_result.ok);
1149
+
1150
+ return ruby_string;
1003
1151
  }
1004
1152
 
1005
- static inline size_t heap_record_key_entry_name(heap_record_key *key, size_t entry_i, const char **name_ptr) {
1006
- if (key->type == HEAP_STACK) {
1007
- char *name = key->heap_stack->frames[entry_i].name;
1008
- (*name_ptr) = name;
1009
- return strlen(name);
1010
- } else {
1011
- ddog_CharSlice name = key->location_slice->ptr[entry_i].function.name;
1012
- (*name_ptr) = name.ptr;
1013
- return name.len;
1153
+ static inline double ewma_stat(double previous, double current) {
1154
+ double alpha = 0.3;
1155
+ return (1 - alpha) * previous + alpha * current;
1156
+ }
1157
+
1158
+ VALUE heap_recorder_testonly_is_object_recorded(heap_recorder *heap_recorder, long record_id) {
1159
+ if (heap_recorder == NULL) {
1160
+ raise_error(rb_eArgError, "heap_recorder is NULL");
1014
1161
  }
1162
+
1163
+ // Check if object records contains an object with this record_id
1164
+ return st_is_member(heap_recorder->object_records, record_id) ? Qtrue : Qfalse;
1015
1165
  }
1016
1166
 
1017
- static inline size_t heap_record_key_entry_filename(heap_record_key *key, size_t entry_i, const char **filename_ptr) {
1018
- if (key->type == HEAP_STACK) {
1019
- char *filename = key->heap_stack->frames[entry_i].filename;
1020
- (*filename_ptr) = filename;
1021
- return strlen(filename);
1022
- } else {
1023
- ddog_CharSlice filename = key->location_slice->ptr[entry_i].function.filename;
1024
- (*filename_ptr) = filename.ptr;
1025
- return filename.len;
1167
+ typedef struct {
1168
+ heap_recorder *recorder;
1169
+ VALUE target;
1170
+ VALUE result;
1171
+ } record_id_for_context;
1172
+
1173
+ static int st_object_record_id_for(st_data_t key, DDTRACE_UNUSED st_data_t value, st_data_t extra) {
1174
+ record_id_for_context *context = (record_id_for_context*) extra;
1175
+ long record_id = (long) key;
1176
+
1177
+ VALUE ref = ruby_weak_map_get_may_lose_gvl(context->recorder->weak_objects, LONG2FIX(record_id));
1178
+ if (ref != Qnil && ref == context->target) {
1179
+ context->result = LONG2FIX(record_id);
1180
+ return ST_STOP;
1026
1181
  }
1182
+
1183
+ return ST_CONTINUE;
1027
1184
  }
1028
1185
 
1029
- int heap_record_key_cmp_st(st_data_t key1, st_data_t key2) {
1030
- heap_record_key *key_record1 = (heap_record_key*) key1;
1031
- heap_record_key *key_record2 = (heap_record_key*) key2;
1186
+ VALUE heap_recorder_testonly_record_id_for(heap_recorder *heap_recorder, VALUE obj) {
1187
+ // Heap profiling is disabled, so nothing is being tracked
1188
+ if (heap_recorder == NULL) return Qnil;
1032
1189
 
1033
- // Fast path, check if lengths differ
1034
- size_t key_record1_len = heap_record_key_len(key_record1);
1035
- size_t key_record2_len = heap_record_key_len(key_record2);
1190
+ record_id_for_context context = (record_id_for_context) {.recorder = heap_recorder, .target = obj, .result = Qnil};
1191
+ st_foreach(heap_recorder->object_records, st_object_record_id_for, (st_data_t) &context);
1036
1192
 
1037
- if (key_record1_len != key_record2_len) {
1038
- return ((int) key_record1_len) - ((int) key_record2_len);
1193
+ return context.result;
1194
+ }
1195
+
1196
+ void heap_recorder_testonly_set_object_alloc_gen(heap_recorder *heap_recorder, long record_id, size_t alloc_gen) {
1197
+ if (heap_recorder == NULL) {
1198
+ raise_error(rb_eArgError, "heap_recorder is NULL");
1039
1199
  }
1040
1200
 
1041
- // If we got this far, we have same lengths so need to check item-by-item
1042
- for (size_t i = 0; i < key_record1_len; i++) {
1043
- // Lines are faster to compare, lets do that first
1044
- size_t line1 = heap_record_key_entry_line(key_record1, i);
1045
- size_t line2 = heap_record_key_entry_line(key_record2, i);
1046
- if (line1 != line2) {
1047
- return ((int) line1) - ((int)line2);
1048
- }
1201
+ object_record *record = NULL;
1202
+ if (!st_lookup(heap_recorder->object_records, (st_data_t) record_id, (st_data_t *) &record)) {
1203
+ raise_error(rb_eArgError, "record_id %ld is not being tracked", record_id);
1204
+ }
1049
1205
 
1050
- // Then come names, they are usually smaller than filenames
1051
- const char *name1, *name2;
1052
- size_t name1_len = heap_record_key_entry_name(key_record1, i, &name1);
1053
- size_t name2_len = heap_record_key_entry_name(key_record2, i, &name2);
1054
- if (name1_len != name2_len) {
1055
- return ((int) name1_len) - ((int) name2_len);
1056
- }
1057
- int name_cmp_result = strncmp(name1, name2, name1_len);
1058
- if (name_cmp_result != 0) {
1059
- return name_cmp_result;
1060
- }
1206
+ record->object_data.alloc_gen = alloc_gen;
1207
+ }
1061
1208
 
1062
- // Then come filenames
1063
- const char *filename1, *filename2;
1064
- int64_t filename1_len = heap_record_key_entry_filename(key_record1, i, &filename1);
1065
- int64_t filename2_len = heap_record_key_entry_filename(key_record2, i, &filename2);
1066
- if (filename1_len != filename2_len) {
1067
- return ((int) filename1_len) - ((int) filename2_len);
1068
- }
1069
- int filename_cmp_result = strncmp(filename1, filename2, filename1_len);
1070
- if (filename_cmp_result != 0) {
1071
- return filename_cmp_result;
1072
- }
1209
+ void heap_recorder_testonly_exhaust_record_ids(heap_recorder *heap_recorder) {
1210
+ if (heap_recorder == NULL) {
1211
+ raise_error(rb_eArgError, "heap_recorder is NULL");
1073
1212
  }
1074
1213
 
1075
- // If we survived the above for, then everything matched
1076
- return 0;
1214
+ heap_recorder->next_record_id = RUBY_FIXNUM_MAX;
1077
1215
  }
1078
1216
 
1079
- // Initial seed for hash functions
1080
- #define FNV1_32A_INIT 0x811c9dc5
1217
+ void heap_recorder_testonly_reset_last_update(heap_recorder *heap_recorder) {
1218
+ if (heap_recorder == NULL) {
1219
+ raise_error(rb_eArgError, "heap_recorder is NULL");
1220
+ }
1221
+
1222
+ heap_recorder->last_update_ns = 0;
1223
+ }
1224
+
1225
+ void heap_recorder_testonly_benchmark_intern(heap_recorder *heap_recorder, ddog_CharSlice string, int times, bool use_all) {
1226
+ if (heap_recorder == NULL) raise_error(rb_eArgError, "heap profiling must be enabled");
1227
+ if (times > REUSABLE_FRAME_DETAILS_SIZE) raise_error(rb_eArgError, "times cannot be > than REUSABLE_FRAME_DETAILS_SIZE");
1228
+
1229
+ if (use_all) {
1230
+ ddog_CharSlice *strings = heap_recorder->reusable_char_slices;
1231
+
1232
+ for (int i = 0; i < times; i++) strings[i] = string;
1081
1233
 
1082
- st_index_t heap_record_key_hash_st(st_data_t key) {
1083
- heap_record_key *record_key = (heap_record_key*) key;
1084
- if (record_key->type == HEAP_STACK) {
1085
- return heap_stack_hash(record_key->heap_stack, FNV1_32A_INIT);
1234
+ intern_all_or_raise(
1235
+ heap_recorder->string_storage,
1236
+ (ddog_prof_Slice_CharSlice) { .ptr = strings, .len = times },
1237
+ heap_recorder->reusable_ids,
1238
+ times
1239
+ );
1086
1240
  } else {
1087
- return ddog_location_slice_hash(*record_key->location_slice, FNV1_32A_INIT);
1241
+ for (int i = 0; i < times; i++) intern_or_raise(heap_recorder->string_storage, string);
1088
1242
  }
1089
1243
  }