datadog 2.17.0 → 2.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (765) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +686 -1
  3. data/README.md +0 -1
  4. data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
  5. data/ext/datadog_profiling_native_extension/clock_id.h +9 -1
  6. data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +66 -0
  7. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +3 -2
  8. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +344 -126
  9. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -4
  10. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
  11. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +18 -4
  12. data/ext/datadog_profiling_native_extension/collectors_stack.c +314 -91
  13. data/ext/datadog_profiling_native_extension/collectors_stack.h +26 -3
  14. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +680 -352
  15. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +18 -5
  16. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
  17. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +65 -1
  18. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +40 -1
  19. data/ext/datadog_profiling_native_extension/encoded_profile.c +2 -1
  20. data/ext/datadog_profiling_native_extension/extconf.rb +48 -34
  21. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +4 -43
  22. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -47
  23. data/ext/datadog_profiling_native_extension/heap_recorder.c +455 -430
  24. data/ext/datadog_profiling_native_extension/heap_recorder.h +16 -7
  25. data/ext/datadog_profiling_native_extension/http_transport.c +66 -68
  26. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +18 -15
  27. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -10
  28. data/ext/datadog_profiling_native_extension/macos_sampler_thread.h +55 -0
  29. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -13
  30. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +73 -69
  31. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +10 -4
  32. data/ext/datadog_profiling_native_extension/profiling.c +65 -19
  33. data/ext/datadog_profiling_native_extension/ruby_helpers.c +64 -59
  34. data/ext/datadog_profiling_native_extension/ruby_helpers.h +20 -16
  35. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +32 -10
  36. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +2 -1
  37. data/ext/datadog_profiling_native_extension/stack_recorder.c +253 -131
  38. data/ext/datadog_profiling_native_extension/stack_recorder.h +3 -1
  39. data/ext/datadog_profiling_native_extension/time_helpers.h +1 -0
  40. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +2 -1
  41. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +9 -4
  42. data/ext/libdatadog_api/crashtracker.c +16 -15
  43. data/ext/libdatadog_api/crashtracker_report_exception.c +126 -0
  44. data/ext/libdatadog_api/datadog_ruby_common.c +65 -1
  45. data/ext/libdatadog_api/datadog_ruby_common.h +40 -1
  46. data/ext/libdatadog_api/ddsketch.c +102 -0
  47. data/ext/libdatadog_api/di.c +203 -0
  48. data/ext/libdatadog_api/extconf.rb +21 -25
  49. data/ext/libdatadog_api/feature_flags.c +554 -0
  50. data/ext/libdatadog_api/feature_flags.h +5 -0
  51. data/ext/libdatadog_api/helpers.h +27 -0
  52. data/ext/libdatadog_api/init.c +13 -1
  53. data/ext/libdatadog_api/library_config.c +88 -37
  54. data/ext/libdatadog_api/library_config.h +6 -0
  55. data/ext/libdatadog_api/process_discovery.c +25 -24
  56. data/ext/libdatadog_extconf_helpers.rb +107 -14
  57. data/lib/datadog/ai_guard/api_client.rb +84 -0
  58. data/lib/datadog/ai_guard/autoload.rb +10 -0
  59. data/lib/datadog/ai_guard/component.rb +44 -0
  60. data/lib/datadog/ai_guard/configuration/ext.rb +17 -0
  61. data/lib/datadog/ai_guard/configuration.rb +115 -0
  62. data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -0
  63. data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
  64. data/lib/datadog/ai_guard/contrib/rack/integration.rb +42 -0
  65. data/lib/datadog/ai_guard/contrib/rack/patcher.rb +26 -0
  66. data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +97 -0
  67. data/lib/datadog/ai_guard/contrib/rails/integration.rb +41 -0
  68. data/lib/datadog/ai_guard/contrib/rails/patcher.rb +97 -0
  69. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +80 -0
  70. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
  71. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +30 -0
  72. data/lib/datadog/ai_guard/evaluation/content_builder.rb +31 -0
  73. data/lib/datadog/ai_guard/evaluation/content_part.rb +36 -0
  74. data/lib/datadog/ai_guard/evaluation/message.rb +25 -0
  75. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +36 -0
  76. data/lib/datadog/ai_guard/evaluation/request.rb +86 -0
  77. data/lib/datadog/ai_guard/evaluation/result.rb +45 -0
  78. data/lib/datadog/ai_guard/evaluation/tool_call.rb +18 -0
  79. data/lib/datadog/ai_guard/evaluation.rb +108 -0
  80. data/lib/datadog/ai_guard/ext.rb +29 -0
  81. data/lib/datadog/ai_guard.rb +181 -0
  82. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
  83. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +66 -0
  84. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +36 -0
  85. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
  86. data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
  87. data/lib/datadog/appsec/api_security/route_extractor.rb +104 -0
  88. data/lib/datadog/appsec/api_security/sampler.rb +62 -0
  89. data/lib/datadog/appsec/api_security.rb +14 -0
  90. data/lib/datadog/appsec/assets/blocked.html +10 -1
  91. data/lib/datadog/appsec/assets/blocked.json +1 -1
  92. data/lib/datadog/appsec/assets/blocked.text +3 -1
  93. data/lib/datadog/appsec/assets/waf_rules/README.md +30 -36
  94. data/lib/datadog/appsec/assets/waf_rules/recommended.json +601 -74
  95. data/lib/datadog/appsec/assets/waf_rules/strict.json +48 -75
  96. data/lib/datadog/appsec/assets.rb +1 -1
  97. data/lib/datadog/appsec/autoload.rb +2 -2
  98. data/lib/datadog/appsec/component.rb +37 -64
  99. data/lib/datadog/appsec/compressed_json.rb +3 -3
  100. data/lib/datadog/appsec/configuration.rb +427 -1
  101. data/lib/datadog/appsec/context.rb +77 -16
  102. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
  103. data/lib/datadog/appsec/contrib/active_record/integration.rb +1 -1
  104. data/lib/datadog/appsec/contrib/active_record/patcher.rb +4 -1
  105. data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +75 -0
  106. data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +39 -0
  107. data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +30 -0
  108. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +144 -0
  109. data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
  110. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +4 -2
  111. data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +2 -1
  112. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +1 -1
  113. data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
  114. data/lib/datadog/appsec/contrib/excon/patcher.rb +3 -1
  115. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +152 -13
  116. data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
  117. data/lib/datadog/appsec/contrib/faraday/patcher.rb +1 -1
  118. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +138 -13
  119. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +74 -24
  120. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +4 -2
  121. data/lib/datadog/appsec/contrib/graphql/integration.rb +2 -1
  122. data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
  123. data/lib/datadog/appsec/contrib/rack/ext.rb +1 -1
  124. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +47 -13
  125. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +40 -19
  126. data/lib/datadog/appsec/contrib/rack/input_peeker.rb +79 -0
  127. data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
  128. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +80 -62
  129. data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
  130. data/lib/datadog/appsec/contrib/rails/gateway/request.rb +35 -1
  131. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +47 -5
  132. data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
  133. data/lib/datadog/appsec/contrib/rails/patcher.rb +52 -26
  134. data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +29 -0
  135. data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +33 -0
  136. data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
  137. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +3 -1
  138. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +167 -12
  139. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +63 -10
  140. data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
  141. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +11 -21
  142. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +31 -0
  143. data/lib/datadog/appsec/counter_sampler.rb +25 -0
  144. data/lib/datadog/appsec/default_header_tags.rb +52 -0
  145. data/lib/datadog/appsec/event.rb +14 -47
  146. data/lib/datadog/appsec/ext.rb +3 -0
  147. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +17 -1
  148. data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +2 -3
  149. data/lib/datadog/appsec/instrumentation/gateway.rb +2 -15
  150. data/lib/datadog/appsec/metrics/collector.rb +47 -5
  151. data/lib/datadog/appsec/metrics/exporter.rb +16 -3
  152. data/lib/datadog/appsec/metrics/telemetry.rb +21 -4
  153. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +47 -0
  154. data/lib/datadog/appsec/metrics.rb +5 -4
  155. data/lib/datadog/appsec/monitor/gateway/watcher.rb +8 -6
  156. data/lib/datadog/appsec/processor/rule_loader.rb +5 -6
  157. data/lib/datadog/appsec/remote.rb +51 -76
  158. data/lib/datadog/appsec/response.rb +18 -4
  159. data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
  160. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
  161. data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
  162. data/lib/datadog/appsec/route_normalizer.rb +80 -0
  163. data/lib/datadog/appsec/security_engine/engine.rb +197 -0
  164. data/lib/datadog/appsec/security_engine/result.rb +45 -9
  165. data/lib/datadog/appsec/security_engine/runner.rb +44 -21
  166. data/lib/datadog/appsec/security_event.rb +5 -7
  167. data/lib/datadog/appsec/thread_safe_ref.rb +61 -0
  168. data/lib/datadog/appsec/trace_keeper.rb +36 -0
  169. data/lib/datadog/appsec/utils/hash_coercion.rb +23 -0
  170. data/lib/datadog/appsec/utils/http/body.rb +38 -0
  171. data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
  172. data/lib/datadog/appsec/utils/http/media_range.rb +2 -1
  173. data/lib/datadog/appsec/utils/http/media_type.rb +32 -26
  174. data/lib/datadog/appsec/utils/http/url_encoded.rb +52 -0
  175. data/lib/datadog/appsec.rb +10 -17
  176. data/lib/datadog/auto_instrument_base.rb +2 -1
  177. data/lib/datadog/core/configuration/agent_settings.rb +52 -0
  178. data/lib/datadog/core/configuration/agent_settings_resolver.rb +5 -47
  179. data/lib/datadog/core/configuration/base.rb +17 -5
  180. data/lib/datadog/core/configuration/components.rb +192 -20
  181. data/lib/datadog/core/configuration/components_state.rb +6 -1
  182. data/lib/datadog/core/configuration/config_helper.rb +109 -0
  183. data/lib/datadog/core/configuration/deprecations.rb +36 -0
  184. data/lib/datadog/core/configuration/ext.rb +0 -1
  185. data/lib/datadog/core/configuration/option.rb +95 -65
  186. data/lib/datadog/core/configuration/option_definition.rb +42 -23
  187. data/lib/datadog/core/configuration/options.rb +57 -18
  188. data/lib/datadog/core/configuration/settings.rb +160 -25
  189. data/lib/datadog/core/configuration/stable_config.rb +11 -2
  190. data/lib/datadog/core/configuration/supported_configurations.rb +413 -0
  191. data/lib/datadog/core/configuration.rb +4 -4
  192. data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
  193. data/lib/datadog/core/contrib/rails/utils.rb +7 -3
  194. data/lib/datadog/core/crashtracking/component.rb +73 -21
  195. data/lib/datadog/core/crashtracking/tag_builder.rb +9 -21
  196. data/lib/datadog/core/ddsketch.rb +19 -0
  197. data/lib/datadog/core/deprecations.rb +2 -2
  198. data/lib/datadog/core/diagnostics/environment_logger.rb +3 -1
  199. data/lib/datadog/core/environment/agent_info.rb +65 -1
  200. data/lib/datadog/core/environment/cgroup.rb +52 -25
  201. data/lib/datadog/core/environment/container.rb +140 -46
  202. data/lib/datadog/core/environment/ext.rb +13 -2
  203. data/lib/datadog/core/environment/git.rb +2 -2
  204. data/lib/datadog/core/environment/identity.rb +40 -4
  205. data/lib/datadog/core/environment/process.rb +122 -0
  206. data/lib/datadog/core/environment/socket.rb +13 -0
  207. data/lib/datadog/core/environment/variable_helpers.rb +3 -3
  208. data/lib/datadog/core/environment/yjit.rb +2 -1
  209. data/lib/datadog/core/error.rb +6 -6
  210. data/lib/datadog/core/evp.rb +11 -0
  211. data/lib/datadog/core/feature_flags.rb +61 -0
  212. data/lib/datadog/core/knuth_sampler.rb +57 -0
  213. data/lib/datadog/core/logger.rb +1 -1
  214. data/lib/datadog/core/metrics/client.rb +7 -7
  215. data/lib/datadog/core/metrics/logging.rb +1 -1
  216. data/lib/datadog/core/pin.rb +8 -8
  217. data/lib/datadog/core/process_discovery/tracer_memfd.rb +13 -0
  218. data/lib/datadog/core/process_discovery.rb +51 -21
  219. data/lib/datadog/core/rate_limiter.rb +11 -1
  220. data/lib/datadog/core/remote/client/capabilities.rb +49 -6
  221. data/lib/datadog/core/remote/client.rb +15 -7
  222. data/lib/datadog/core/remote/component.rb +64 -36
  223. data/lib/datadog/core/remote/configuration/content.rb +15 -2
  224. data/lib/datadog/core/remote/configuration/digest.rb +14 -7
  225. data/lib/datadog/core/remote/configuration/repository.rb +13 -1
  226. data/lib/datadog/core/remote/configuration/target.rb +13 -6
  227. data/lib/datadog/core/remote/transport/config.rb +9 -36
  228. data/lib/datadog/core/remote/transport/http/config.rb +15 -55
  229. data/lib/datadog/core/remote/transport/http/negotiation.rb +14 -44
  230. data/lib/datadog/core/remote/transport/http.rb +15 -24
  231. data/lib/datadog/core/remote/transport/negotiation.rb +8 -33
  232. data/lib/datadog/core/remote/worker.rb +25 -37
  233. data/lib/datadog/core/runtime/ext.rb +0 -1
  234. data/lib/datadog/core/runtime/metrics.rb +11 -2
  235. data/lib/datadog/core/semaphore.rb +1 -4
  236. data/lib/datadog/core/tag_builder.rb +52 -0
  237. data/lib/datadog/core/tag_normalizer.rb +84 -0
  238. data/lib/datadog/core/telemetry/component.rb +65 -17
  239. data/lib/datadog/core/telemetry/emitter.rb +6 -6
  240. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -3
  241. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
  242. data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
  243. data/lib/datadog/core/telemetry/event/app_started.rb +203 -89
  244. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  245. data/lib/datadog/core/telemetry/event.rb +2 -7
  246. data/lib/datadog/core/telemetry/ext.rb +1 -0
  247. data/lib/datadog/core/telemetry/logger.rb +5 -2
  248. data/lib/datadog/core/telemetry/logging.rb +22 -4
  249. data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
  250. data/lib/datadog/core/telemetry/request.rb +17 -5
  251. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +8 -34
  252. data/lib/datadog/core/telemetry/transport/http.rb +21 -16
  253. data/lib/datadog/core/telemetry/transport/telemetry.rb +8 -17
  254. data/lib/datadog/core/telemetry/worker.rb +108 -32
  255. data/lib/datadog/core/transport/ext.rb +3 -0
  256. data/lib/datadog/core/transport/http/adapters/net.rb +17 -2
  257. data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
  258. data/lib/datadog/core/transport/http/api/instance.rb +4 -21
  259. data/lib/datadog/core/transport/http/builder.rb +11 -7
  260. data/lib/datadog/core/transport/http/client.rb +80 -0
  261. data/lib/datadog/core/transport/http/env.rb +8 -0
  262. data/lib/datadog/core/transport/http/response.rb +4 -0
  263. data/lib/datadog/core/transport/http.rb +24 -19
  264. data/lib/datadog/core/transport/parcel.rb +61 -9
  265. data/lib/datadog/core/transport/response.rb +16 -2
  266. data/lib/datadog/core/transport/transport.rb +90 -0
  267. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
  268. data/lib/datadog/core/utils/{base64.rb → base64_codec.rb} +4 -3
  269. data/lib/datadog/core/utils/enumerable_compat.rb +29 -0
  270. data/lib/datadog/core/utils/fnv.rb +26 -0
  271. data/lib/datadog/core/utils/forking.rb +3 -1
  272. data/lib/datadog/core/utils/hash.rb +0 -23
  273. data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -14
  274. data/lib/datadog/core/utils/network.rb +22 -1
  275. data/lib/datadog/core/utils/only_once.rb +1 -1
  276. data/lib/datadog/core/utils/only_once_successful.rb +8 -2
  277. data/lib/datadog/core/utils/safe_dup.rb +2 -2
  278. data/lib/datadog/core/utils/sequence.rb +2 -0
  279. data/lib/datadog/core/utils/spawn_monkey_patch.rb +68 -0
  280. data/lib/datadog/core/utils/time.rb +1 -1
  281. data/lib/datadog/core/utils.rb +10 -1
  282. data/lib/datadog/core/workers/async.rb +11 -2
  283. data/lib/datadog/core/workers/interval_loop.rb +51 -3
  284. data/lib/datadog/core/workers/polling.rb +2 -0
  285. data/lib/datadog/core/workers/queue.rb +96 -1
  286. data/lib/datadog/core/workers/runtime_metrics.rb +9 -1
  287. data/lib/datadog/core.rb +11 -2
  288. data/lib/datadog/data_streams/configuration.rb +50 -0
  289. data/lib/datadog/data_streams/ext.rb +11 -0
  290. data/lib/datadog/data_streams/extensions.rb +16 -0
  291. data/lib/datadog/data_streams/pathway_context.rb +169 -0
  292. data/lib/datadog/data_streams/processor.rb +511 -0
  293. data/lib/datadog/data_streams/transport/http/stats.rb +58 -0
  294. data/lib/datadog/data_streams/transport/http.rb +36 -0
  295. data/lib/datadog/data_streams/transport/stats.rb +39 -0
  296. data/lib/datadog/data_streams.rb +100 -0
  297. data/lib/datadog/di/base.rb +15 -7
  298. data/lib/datadog/di/boot.rb +11 -1
  299. data/lib/datadog/di/code_tracker.rb +217 -15
  300. data/lib/datadog/di/component.rb +143 -48
  301. data/lib/datadog/di/configuration.rb +235 -2
  302. data/lib/datadog/di/context.rb +76 -0
  303. data/lib/datadog/di/contrib/active_record.rb +34 -5
  304. data/lib/datadog/di/el/compiler.rb +168 -0
  305. data/lib/datadog/di/el/evaluator.rb +159 -0
  306. data/lib/datadog/di/el/expression.rb +42 -0
  307. data/lib/datadog/di/el.rb +5 -0
  308. data/lib/datadog/di/error.rb +44 -0
  309. data/lib/datadog/di/fatal_exceptions.rb +26 -0
  310. data/lib/datadog/di/instrumenter.rb +620 -132
  311. data/lib/datadog/di/logger.rb +2 -2
  312. data/lib/datadog/di/probe.rb +78 -15
  313. data/lib/datadog/di/probe_builder.rb +42 -3
  314. data/lib/datadog/di/probe_file_loader/railtie.rb +15 -0
  315. data/lib/datadog/di/probe_file_loader.rb +87 -0
  316. data/lib/datadog/di/probe_manager.rb +195 -100
  317. data/lib/datadog/di/probe_notification_builder.rb +285 -100
  318. data/lib/datadog/di/probe_notifier_worker.rb +120 -36
  319. data/lib/datadog/di/probe_repository.rb +198 -0
  320. data/lib/datadog/di/proc_responder.rb +36 -0
  321. data/lib/datadog/di/redactor.rb +24 -2
  322. data/lib/datadog/di/remote.rb +258 -95
  323. data/lib/datadog/di/serializer.rb +297 -20
  324. data/lib/datadog/di/transport/diagnostics.rb +12 -42
  325. data/lib/datadog/di/transport/http/diagnostics.rb +4 -34
  326. data/lib/datadog/di/transport/http/input.rb +12 -34
  327. data/lib/datadog/di/transport/http.rb +43 -19
  328. data/lib/datadog/di/transport/input.rb +121 -39
  329. data/lib/datadog/di/utils.rb +42 -14
  330. data/lib/datadog/di.rb +203 -10
  331. data/lib/datadog/error_tracking/collector.rb +2 -1
  332. data/lib/datadog/error_tracking/component.rb +2 -2
  333. data/lib/datadog/error_tracking/configuration.rb +55 -2
  334. data/lib/datadog/error_tracking/filters.rb +2 -2
  335. data/lib/datadog/kit/appsec/events/v2.rb +253 -0
  336. data/lib/datadog/kit/appsec/events.rb +11 -10
  337. data/lib/datadog/kit/enable_core_dumps.rb +1 -1
  338. data/lib/datadog/kit/identity.rb +17 -11
  339. data/lib/datadog/kit/tracing/method_tracer.rb +135 -0
  340. data/lib/datadog/open_feature/component.rb +100 -0
  341. data/lib/datadog/open_feature/configuration.rb +37 -0
  342. data/lib/datadog/open_feature/evaluation_engine.rb +70 -0
  343. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  344. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  345. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  346. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  347. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  348. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  349. data/lib/datadog/open_feature/ext.rb +16 -0
  350. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
  351. data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
  352. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
  353. data/lib/datadog/open_feature/hooks/flag_eval_metrics_hook.rb +49 -0
  354. data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
  355. data/lib/datadog/open_feature/native_evaluator.rb +60 -0
  356. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  357. data/lib/datadog/open_feature/provider.rb +191 -0
  358. data/lib/datadog/open_feature/remote.rb +67 -0
  359. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  360. data/lib/datadog/open_feature/transport.rb +121 -0
  361. data/lib/datadog/open_feature.rb +19 -0
  362. data/lib/datadog/opentelemetry/api/baggage.rb +3 -3
  363. data/lib/datadog/opentelemetry/api/context.rb +10 -9
  364. data/lib/datadog/opentelemetry/configuration/settings.rb +226 -0
  365. data/lib/datadog/opentelemetry/ext.rb +9 -0
  366. data/lib/datadog/opentelemetry/logs.rb +98 -0
  367. data/lib/datadog/opentelemetry/metrics.rb +90 -0
  368. data/lib/datadog/opentelemetry/sdk/configurator.rb +66 -2
  369. data/lib/datadog/opentelemetry/sdk/id_generator.rb +16 -10
  370. data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +30 -0
  371. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +28 -0
  372. data/lib/datadog/opentelemetry/sdk/propagator.rb +13 -7
  373. data/lib/datadog/opentelemetry/sdk/span_processor.rb +12 -9
  374. data/lib/datadog/opentelemetry/sdk/trace/span.rb +14 -10
  375. data/lib/datadog/opentelemetry/sdk.rb +19 -0
  376. data/lib/datadog/opentelemetry/signal_configuration.rb +53 -0
  377. data/lib/datadog/opentelemetry/trace.rb +4 -4
  378. data/lib/datadog/opentelemetry.rb +4 -0
  379. data/lib/datadog/profiling/collectors/code_provenance.rb +83 -15
  380. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +52 -3
  381. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +13 -4
  382. data/lib/datadog/profiling/collectors/info.rb +59 -4
  383. data/lib/datadog/profiling/collectors/thread_context.rb +17 -5
  384. data/lib/datadog/profiling/component.rb +73 -49
  385. data/lib/datadog/profiling/exporter.rb +48 -13
  386. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +24 -5
  387. data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
  388. data/lib/datadog/profiling/ext.rb +2 -15
  389. data/lib/datadog/profiling/flush.rb +23 -11
  390. data/lib/datadog/profiling/http_transport.rb +24 -11
  391. data/lib/datadog/profiling/load_native_extension.rb +2 -2
  392. data/lib/datadog/profiling/profiler.rb +31 -12
  393. data/lib/datadog/profiling/scheduler.rb +12 -10
  394. data/lib/datadog/profiling/sequence_tracker.rb +44 -0
  395. data/lib/datadog/profiling/stack_recorder.rb +5 -13
  396. data/lib/datadog/profiling/tag_builder.rb +42 -38
  397. data/lib/datadog/profiling/tasks/exec.rb +10 -5
  398. data/lib/datadog/profiling/tasks/help.rb +1 -0
  399. data/lib/datadog/profiling/tasks/setup.rb +4 -2
  400. data/lib/datadog/profiling.rb +8 -11
  401. data/lib/datadog/ruby_version.rb +25 -0
  402. data/lib/datadog/single_step_instrument.rb +10 -1
  403. data/lib/datadog/symbol_database/component.rb +734 -0
  404. data/lib/datadog/symbol_database/configuration.rb +73 -0
  405. data/lib/datadog/symbol_database/extensions.rb +19 -0
  406. data/lib/datadog/symbol_database/extractor.rb +1110 -0
  407. data/lib/datadog/symbol_database/file_hash.rb +48 -0
  408. data/lib/datadog/symbol_database/logger.rb +43 -0
  409. data/lib/datadog/symbol_database/remote.rb +180 -0
  410. data/lib/datadog/symbol_database/scope.rb +102 -0
  411. data/lib/datadog/symbol_database/scope_batcher.rb +292 -0
  412. data/lib/datadog/symbol_database/service_version.rb +66 -0
  413. data/lib/datadog/symbol_database/symbol.rb +69 -0
  414. data/lib/datadog/symbol_database/transport/http/endpoint.rb +28 -0
  415. data/lib/datadog/symbol_database/transport/http.rb +45 -0
  416. data/lib/datadog/symbol_database/transport.rb +54 -0
  417. data/lib/datadog/symbol_database/uploader.rb +230 -0
  418. data/lib/datadog/symbol_database.rb +71 -0
  419. data/lib/datadog/tracing/analytics.rb +1 -1
  420. data/lib/datadog/tracing/buffer.rb +10 -10
  421. data/lib/datadog/tracing/component.rb +17 -17
  422. data/lib/datadog/tracing/configuration/dynamic.rb +6 -8
  423. data/lib/datadog/tracing/configuration/ext.rb +25 -5
  424. data/lib/datadog/tracing/configuration/settings.rb +125 -11
  425. data/lib/datadog/tracing/context.rb +2 -2
  426. data/lib/datadog/tracing/contrib/action_cable/event.rb +1 -1
  427. data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +4 -1
  428. data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +4 -1
  429. data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +4 -1
  430. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +4 -1
  431. data/lib/datadog/tracing/contrib/action_cable/integration.rb +1 -1
  432. data/lib/datadog/tracing/contrib/action_mailer/event.rb +4 -1
  433. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +1 -1
  434. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +29 -11
  435. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +47 -12
  436. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +3 -1
  437. data/lib/datadog/tracing/contrib/action_pack/ext.rb +2 -0
  438. data/lib/datadog/tracing/contrib/action_pack/integration.rb +1 -1
  439. data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
  440. data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +4 -1
  441. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +6 -3
  442. data/lib/datadog/tracing/contrib/active_job/event.rb +8 -8
  443. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +6 -3
  444. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +6 -3
  445. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +6 -3
  446. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +6 -3
  447. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +6 -3
  448. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +6 -3
  449. data/lib/datadog/tracing/contrib/active_job/integration.rb +1 -1
  450. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
  451. data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
  452. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +2 -2
  453. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +4 -4
  454. data/lib/datadog/tracing/contrib/active_model_serializers/integration.rb +1 -2
  455. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +3 -3
  456. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +2 -2
  457. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +7 -10
  458. data/lib/datadog/tracing/contrib/active_record/integration.rb +1 -1
  459. data/lib/datadog/tracing/contrib/active_record/utils.rb +16 -16
  460. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +15 -12
  461. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +1 -4
  462. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +13 -0
  463. data/lib/datadog/tracing/contrib/active_support/integration.rb +1 -1
  464. data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +2 -1
  465. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +8 -10
  466. data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
  467. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +8 -10
  468. data/lib/datadog/tracing/contrib/aws/parsed_context.rb +3 -1
  469. data/lib/datadog/tracing/contrib/aws/patcher.rb +5 -1
  470. data/lib/datadog/tracing/contrib/aws/service/base.rb +2 -1
  471. data/lib/datadog/tracing/contrib/aws/service/dynamodb.rb +1 -1
  472. data/lib/datadog/tracing/contrib/aws/service/eventbridge.rb +1 -1
  473. data/lib/datadog/tracing/contrib/aws/service/kinesis.rb +1 -1
  474. data/lib/datadog/tracing/contrib/aws/service/s3.rb +1 -1
  475. data/lib/datadog/tracing/contrib/aws/service/sns.rb +1 -1
  476. data/lib/datadog/tracing/contrib/aws/service/sqs.rb +1 -1
  477. data/lib/datadog/tracing/contrib/aws/service/states.rb +1 -1
  478. data/lib/datadog/tracing/contrib/aws/services.rb +7 -7
  479. data/lib/datadog/tracing/contrib/component.rb +3 -3
  480. data/lib/datadog/tracing/contrib/concurrent_ruby/async_patch.rb +1 -1
  481. data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +1 -1
  482. data/lib/datadog/tracing/contrib/concurrent_ruby/future_patch.rb +1 -1
  483. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +1 -1
  484. data/lib/datadog/tracing/contrib/concurrent_ruby/promises_future_patch.rb +1 -1
  485. data/lib/datadog/tracing/contrib/configurable.rb +22 -7
  486. data/lib/datadog/tracing/contrib/configuration/resolver.rb +14 -4
  487. data/lib/datadog/tracing/contrib/configuration/resolvers/pattern_resolver.rb +4 -4
  488. data/lib/datadog/tracing/contrib/dalli/ext.rb +3 -2
  489. data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +1 -5
  490. data/lib/datadog/tracing/contrib/dalli/integration.rb +5 -2
  491. data/lib/datadog/tracing/contrib/dalli/quantize.rb +1 -1
  492. data/lib/datadog/tracing/contrib/delayed_job/integration.rb +1 -1
  493. data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +2 -0
  494. data/lib/datadog/tracing/contrib/delayed_job/server_internal_tracer/worker.rb +1 -0
  495. data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +3 -2
  496. data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +4 -4
  497. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +47 -53
  498. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +7 -7
  499. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +5 -1
  500. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +7 -8
  501. data/lib/datadog/tracing/contrib/ethon/ext.rb +4 -2
  502. data/lib/datadog/tracing/contrib/ethon/integration.rb +1 -1
  503. data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +1 -8
  504. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +16 -5
  505. data/lib/datadog/tracing/contrib/excon/ext.rb +4 -2
  506. data/lib/datadog/tracing/contrib/excon/integration.rb +1 -1
  507. data/lib/datadog/tracing/contrib/excon/middleware.rb +5 -9
  508. data/lib/datadog/tracing/contrib/ext.rb +6 -4
  509. data/lib/datadog/tracing/contrib/extensions.rb +28 -11
  510. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +16 -9
  511. data/lib/datadog/tracing/contrib/faraday/ext.rb +4 -2
  512. data/lib/datadog/tracing/contrib/faraday/integration.rb +1 -1
  513. data/lib/datadog/tracing/contrib/faraday/middleware.rb +7 -9
  514. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
  515. data/lib/datadog/tracing/contrib/grape/endpoint.rb +18 -15
  516. data/lib/datadog/tracing/contrib/grape/instrumentation.rb +13 -8
  517. data/lib/datadog/tracing/contrib/grape/integration.rb +1 -1
  518. data/lib/datadog/tracing/contrib/grape/patcher.rb +6 -1
  519. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
  520. data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
  521. data/lib/datadog/tracing/contrib/graphql/integration.rb +1 -1
  522. data/lib/datadog/tracing/contrib/graphql/patcher.rb +2 -2
  523. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +105 -63
  524. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +5 -2
  525. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +11 -15
  526. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +6 -10
  527. data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +1 -1
  528. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +2 -0
  529. data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
  530. data/lib/datadog/tracing/contrib/grpc/integration.rb +1 -1
  531. data/lib/datadog/tracing/contrib/grpc.rb +1 -0
  532. data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +1 -0
  533. data/lib/datadog/tracing/contrib/hanami/ext.rb +2 -2
  534. data/lib/datadog/tracing/contrib/hanami/integration.rb +1 -1
  535. data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +2 -1
  536. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +10 -11
  537. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +16 -5
  538. data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +4 -4
  539. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +2 -0
  540. data/lib/datadog/tracing/contrib/http/ext.rb +4 -2
  541. data/lib/datadog/tracing/contrib/http/instrumentation.rb +9 -13
  542. data/lib/datadog/tracing/contrib/http/integration.rb +0 -2
  543. data/lib/datadog/tracing/contrib/http.rb +1 -0
  544. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +16 -5
  545. data/lib/datadog/tracing/contrib/httpclient/ext.rb +4 -2
  546. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +10 -10
  547. data/lib/datadog/tracing/contrib/httpclient/integration.rb +1 -1
  548. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +16 -5
  549. data/lib/datadog/tracing/contrib/httprb/ext.rb +4 -2
  550. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +8 -12
  551. data/lib/datadog/tracing/contrib/httprb/integration.rb +1 -1
  552. data/lib/datadog/tracing/contrib/kafka/event.rb +2 -1
  553. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +1 -1
  554. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +1 -1
  555. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +1 -1
  556. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +1 -1
  557. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/join_group.rb +1 -1
  558. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/leave_group.rb +1 -1
  559. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/sync_group.rb +1 -1
  560. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +1 -1
  561. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +1 -1
  562. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  563. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  564. data/lib/datadog/tracing/contrib/kafka/integration.rb +1 -1
  565. data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
  566. data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +5 -1
  567. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
  568. data/lib/datadog/tracing/contrib/karafka/ext.rb +1 -0
  569. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  570. data/lib/datadog/tracing/contrib/karafka/monitor.rb +24 -13
  571. data/lib/datadog/tracing/contrib/karafka/patcher.rb +39 -8
  572. data/lib/datadog/tracing/contrib/karafka.rb +1 -0
  573. data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +1 -1
  574. data/lib/datadog/tracing/contrib/lograge/integration.rb +1 -1
  575. data/lib/datadog/tracing/contrib/lograge/patcher.rb +4 -2
  576. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +1 -1
  577. data/lib/datadog/tracing/contrib/mongodb/ext.rb +1 -1
  578. data/lib/datadog/tracing/contrib/mongodb/integration.rb +1 -1
  579. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +6 -6
  580. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +11 -14
  581. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +6 -0
  582. data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -1
  583. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +19 -12
  584. data/lib/datadog/tracing/contrib/mysql2/integration.rb +1 -1
  585. data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +1 -1
  586. data/lib/datadog/tracing/contrib/opensearch/ext.rb +3 -2
  587. data/lib/datadog/tracing/contrib/opensearch/integration.rb +1 -2
  588. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +64 -70
  589. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +7 -7
  590. data/lib/datadog/tracing/contrib/patcher.rb +7 -9
  591. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +6 -0
  592. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +3 -6
  593. data/lib/datadog/tracing/contrib/pg/integration.rb +1 -1
  594. data/lib/datadog/tracing/contrib/presto/ext.rb +1 -1
  595. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +9 -11
  596. data/lib/datadog/tracing/contrib/presto/integration.rb +1 -1
  597. data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +1 -1
  598. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +13 -0
  599. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +6 -2
  600. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +25 -1
  601. data/lib/datadog/tracing/contrib/que/configuration/settings.rb +5 -2
  602. data/lib/datadog/tracing/contrib/que/ext.rb +1 -0
  603. data/lib/datadog/tracing/contrib/que/integration.rb +1 -1
  604. data/lib/datadog/tracing/contrib/que/tracer.rb +1 -0
  605. data/lib/datadog/tracing/contrib/racecar/event.rb +2 -6
  606. data/lib/datadog/tracing/contrib/racecar/events/batch.rb +2 -2
  607. data/lib/datadog/tracing/contrib/racecar/events/consume.rb +1 -1
  608. data/lib/datadog/tracing/contrib/racecar/events/message.rb +2 -2
  609. data/lib/datadog/tracing/contrib/racecar/integration.rb +1 -1
  610. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +11 -1
  611. data/lib/datadog/tracing/contrib/rack/ext.rb +28 -0
  612. data/lib/datadog/tracing/contrib/rack/header_collection.rb +1 -1
  613. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +55 -32
  614. data/lib/datadog/tracing/contrib/rack/integration.rb +1 -1
  615. data/lib/datadog/tracing/contrib/rack/middlewares.rb +83 -44
  616. data/lib/datadog/tracing/contrib/rack/patcher.rb +2 -2
  617. data/lib/datadog/tracing/contrib/rack/request_queue.rb +4 -3
  618. data/lib/datadog/tracing/contrib/rack/route_inference.rb +67 -0
  619. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +126 -2
  620. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +6 -3
  621. data/lib/datadog/tracing/contrib/rails/ext.rb +3 -1
  622. data/lib/datadog/tracing/contrib/rails/integration.rb +2 -2
  623. data/lib/datadog/tracing/contrib/rails/log_injection.rb +2 -2
  624. data/lib/datadog/tracing/contrib/rails/middlewares.rb +3 -3
  625. data/lib/datadog/tracing/contrib/rails/patcher.rb +4 -2
  626. data/lib/datadog/tracing/contrib/rails/runner.rb +64 -40
  627. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +10 -8
  628. data/lib/datadog/tracing/contrib/rake/integration.rb +1 -1
  629. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +2 -2
  630. data/lib/datadog/tracing/contrib/redis/ext.rb +3 -2
  631. data/lib/datadog/tracing/contrib/redis/integration.rb +2 -2
  632. data/lib/datadog/tracing/contrib/redis/patcher.rb +4 -4
  633. data/lib/datadog/tracing/contrib/redis/quantize.rb +2 -2
  634. data/lib/datadog/tracing/contrib/redis/tags.rb +2 -7
  635. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +6 -4
  636. data/lib/datadog/tracing/contrib/registerable.rb +11 -0
  637. data/lib/datadog/tracing/contrib/registry.rb +1 -1
  638. data/lib/datadog/tracing/contrib/resque/integration.rb +1 -1
  639. data/lib/datadog/tracing/contrib/resque/resque_job.rb +2 -1
  640. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +5 -2
  641. data/lib/datadog/tracing/contrib/rest_client/ext.rb +4 -2
  642. data/lib/datadog/tracing/contrib/rest_client/integration.rb +1 -1
  643. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +8 -9
  644. data/lib/datadog/tracing/contrib/roda/ext.rb +1 -0
  645. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +8 -3
  646. data/lib/datadog/tracing/contrib/roda/integration.rb +1 -1
  647. data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +1 -1
  648. data/lib/datadog/tracing/contrib/semantic_logger/integration.rb +1 -1
  649. data/lib/datadog/tracing/contrib/sequel/database.rb +6 -5
  650. data/lib/datadog/tracing/contrib/sequel/dataset.rb +2 -1
  651. data/lib/datadog/tracing/contrib/sequel/integration.rb +1 -1
  652. data/lib/datadog/tracing/contrib/sequel/utils.rb +1 -6
  653. data/lib/datadog/tracing/contrib/shoryuken/integration.rb +1 -1
  654. data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +1 -0
  655. data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +1 -0
  656. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +5 -1
  657. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +2 -0
  658. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +2 -0
  659. data/lib/datadog/tracing/contrib/sidekiq/integration.rb +1 -1
  660. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/heartbeat.rb +2 -0
  661. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/job_fetch.rb +1 -0
  662. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/redis_info.rb +1 -0
  663. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/scheduled_poller.rb +2 -0
  664. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/stop.rb +1 -0
  665. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +8 -4
  666. data/lib/datadog/tracing/contrib/sidekiq/utils.rb +1 -1
  667. data/lib/datadog/tracing/contrib/sidekiq.rb +1 -0
  668. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +5 -1
  669. data/lib/datadog/tracing/contrib/sinatra/ext.rb +1 -0
  670. data/lib/datadog/tracing/contrib/sinatra/integration.rb +1 -1
  671. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +1 -0
  672. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +40 -39
  673. data/lib/datadog/tracing/contrib/sneakers/integration.rb +15 -4
  674. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -0
  675. data/lib/datadog/tracing/contrib/span_attribute_schema.rb +1 -1
  676. data/lib/datadog/tracing/contrib/status_range_matcher.rb +13 -1
  677. data/lib/datadog/tracing/contrib/stripe/integration.rb +1 -1
  678. data/lib/datadog/tracing/contrib/stripe/request.rb +2 -2
  679. data/lib/datadog/tracing/contrib/sucker_punch/instrumentation.rb +1 -0
  680. data/lib/datadog/tracing/contrib/sucker_punch/integration.rb +1 -1
  681. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +6 -0
  682. data/lib/datadog/tracing/contrib/trilogy/ext.rb +1 -1
  683. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -6
  684. data/lib/datadog/tracing/contrib/trilogy/integration.rb +1 -1
  685. data/lib/datadog/tracing/contrib/utils/quantization/{hash.rb → hash_formatter.rb} +15 -13
  686. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +6 -6
  687. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +31 -0
  688. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +50 -0
  689. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +18 -0
  690. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  691. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  692. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +49 -0
  693. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  694. data/lib/datadog/tracing/contrib/waterdrop.rb +42 -0
  695. data/lib/datadog/tracing/contrib.rb +9 -0
  696. data/lib/datadog/tracing/diagnostics/environment_logger.rb +12 -4
  697. data/lib/datadog/tracing/distributed/b3_single.rb +1 -1
  698. data/lib/datadog/tracing/distributed/baggage.rb +133 -12
  699. data/lib/datadog/tracing/distributed/datadog.rb +17 -16
  700. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +11 -26
  701. data/lib/datadog/tracing/distributed/helpers.rb +1 -1
  702. data/lib/datadog/tracing/distributed/none.rb +4 -2
  703. data/lib/datadog/tracing/distributed/propagation.rb +39 -4
  704. data/lib/datadog/tracing/distributed/propagation_policy.rb +1 -1
  705. data/lib/datadog/tracing/distributed/trace_context.rb +107 -78
  706. data/lib/datadog/tracing/event.rb +5 -7
  707. data/lib/datadog/tracing/ext.rb +9 -0
  708. data/lib/datadog/tracing/flush.rb +1 -1
  709. data/lib/datadog/tracing/metadata/analytics.rb +1 -1
  710. data/lib/datadog/tracing/metadata/ext.rb +23 -1
  711. data/lib/datadog/tracing/metadata/tagging.rb +6 -6
  712. data/lib/datadog/tracing/pipeline/span_filter.rb +3 -1
  713. data/lib/datadog/tracing/pipeline/span_processor.rb +3 -1
  714. data/lib/datadog/tracing/pipeline.rb +2 -2
  715. data/lib/datadog/tracing/remote.rb +36 -13
  716. data/lib/datadog/tracing/sampling/ext.rb +2 -2
  717. data/lib/datadog/tracing/sampling/priority_sampler.rb +16 -1
  718. data/lib/datadog/tracing/sampling/rate_sampler.rb +8 -19
  719. data/lib/datadog/tracing/sampling/rule.rb +1 -1
  720. data/lib/datadog/tracing/sampling/rule_sampler.rb +77 -48
  721. data/lib/datadog/tracing/sampling/span/rule_parser.rb +3 -3
  722. data/lib/datadog/tracing/sampling/span/sampler.rb +0 -7
  723. data/lib/datadog/tracing/span.rb +2 -2
  724. data/lib/datadog/tracing/span_event.rb +12 -12
  725. data/lib/datadog/tracing/span_link.rb +12 -12
  726. data/lib/datadog/tracing/span_operation.rb +63 -19
  727. data/lib/datadog/tracing/sync_writer.rb +1 -2
  728. data/lib/datadog/tracing/trace_digest.rb +28 -23
  729. data/lib/datadog/tracing/trace_operation.rb +209 -98
  730. data/lib/datadog/tracing/trace_segment.rb +2 -2
  731. data/lib/datadog/tracing/tracer.rb +113 -52
  732. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  733. data/lib/datadog/tracing/transport/http/traces.rb +4 -52
  734. data/lib/datadog/tracing/transport/http.rb +15 -9
  735. data/lib/datadog/tracing/transport/io/client.rb +11 -14
  736. data/lib/datadog/tracing/transport/io/traces.rb +28 -34
  737. data/lib/datadog/tracing/transport/statistics.rb +1 -1
  738. data/lib/datadog/tracing/transport/trace_formatter.rb +22 -0
  739. data/lib/datadog/tracing/transport/traces.rb +14 -82
  740. data/lib/datadog/tracing/workers.rb +4 -3
  741. data/lib/datadog/tracing/writer.rb +1 -1
  742. data/lib/datadog/tracing.rb +2 -2
  743. data/lib/datadog/version.rb +2 -2
  744. data/lib/datadog.rb +14 -0
  745. metadata +201 -40
  746. data/ext/datadog_profiling_native_extension/clock_id_noop.c +0 -21
  747. data/ext/libdatadog_api/macos_development.md +0 -26
  748. data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -321
  749. data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -1023
  750. data/lib/datadog/appsec/configuration/settings.rb +0 -342
  751. data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
  752. data/lib/datadog/appsec/processor/rule_merger.rb +0 -171
  753. data/lib/datadog/appsec/processor.rb +0 -107
  754. data/lib/datadog/core/remote/transport/http/api.rb +0 -53
  755. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  756. data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
  757. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  758. data/lib/datadog/core/transport/http/api/spec.rb +0 -36
  759. data/lib/datadog/di/configuration/settings.rb +0 -212
  760. data/lib/datadog/di/transport/http/api.rb +0 -42
  761. data/lib/datadog/di/transport/http/client.rb +0 -47
  762. data/lib/datadog/error_tracking/configuration/settings.rb +0 -63
  763. data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
  764. data/lib/datadog/tracing/transport/http/api.rb +0 -44
  765. data/lib/datadog/tracing/workers/trace_writer.rb +0 -199
@@ -5,6 +5,18 @@
5
5
  #include "libdatadog_helpers.h"
6
6
  #include "time_helpers.h"
7
7
 
8
+ // note on calloc vs ruby_xcalloc use:
9
+ // * Whenever we're allocating memory after being called by the Ruby VM in a "regular" situation (e.g. initializer)
10
+ // we should use `ruby_xcalloc` to give the VM visibility into what we're doing + give it a chance to manage GC
11
+ // * BUT, when we're being called during a sample, being in the middle of an object allocation is a very special
12
+ // situation for the VM to be in, and we've found the hard way (e.g. https://bugs.ruby-lang.org/issues/20629 and
13
+ // https://github.com/DataDog/dd-trace-rb/pull/4240 ) that it can be easy to do things the VM didn't expect.
14
+ // * Thus, out of caution and to avoid future potential issues such as the ones above, whenever we allocate memory
15
+ // during **sampling** we use `calloc` instead of `ruby_xcalloc`. Note that we've never seen issues from using
16
+ // `ruby_xcalloc` at any time, so this is a **precaution** not a "we've seen it break". But it seems a harmless
17
+ // one to use.
18
+ // This applies to both heap_recorder.c and collectors_thread_context.c
19
+
8
20
  // Minimum age (in GC generations) of heap objects we want to include in heap
9
21
  // recorder iterations. Object with age 0 represent objects that have yet to undergo
10
22
  // a GC and, thus, may just be noise/trash at instant of iteration and are usually not
@@ -24,80 +36,37 @@
24
36
 
25
37
  // A compact representation of a stacktrace frame for a heap allocation.
26
38
  typedef struct {
27
- char *name;
28
- char *filename;
39
+ ddog_prof_ManagedStringId name;
40
+ ddog_prof_ManagedStringId filename;
29
41
  int32_t line;
30
42
  } heap_frame;
31
43
 
44
+ // We use memcmp/st_hash below to compare/hash an entire array of heap_frames, so want to make sure no padding is added
45
+ // We could define the structure to be packed, but that seems even weirder across compilers, and this seems more portable?
46
+ _Static_assert(
47
+ sizeof(heap_frame) == sizeof(ddog_prof_ManagedStringId) * 2 + sizeof(int32_t),
48
+ "Size of heap_frame does not match the sum of its members. Padding detected."
49
+ );
50
+
32
51
  // A compact representation of a stacktrace for a heap allocation.
33
- //
34
- // We could use a ddog_prof_Slice_Location instead but it has a lot of
35
- // unused fields. Because we have to keep these stacks around for at
36
- // least the lifetime of the objects allocated therein, we would be
37
- // incurring a non-negligible memory overhead for little purpose.
52
+ // Used to dedup heap allocation stacktraces across multiple objects sharing the same allocation location.
38
53
  typedef struct {
54
+ // How many objects are currently tracked in object_records recorder for this heap record.
55
+ uint32_t num_tracked_objects;
56
+
39
57
  uint16_t frames_len;
40
58
  heap_frame frames[];
41
- } heap_stack;
42
- static heap_stack* heap_stack_new(ddog_prof_Slice_Location);
43
- static void heap_stack_free(heap_stack*);
44
- static st_index_t heap_stack_hash(heap_stack*, st_index_t);
59
+ } heap_record;
60
+ static heap_record* heap_record_new(heap_recorder*, ddog_prof_Slice_Location);
61
+ static void heap_record_free(heap_recorder*, heap_record*, bool should_unintern);
45
62
 
46
63
  #if MAX_FRAMES_LIMIT > UINT16_MAX
47
64
  #error Frames len type not compatible with MAX_FRAMES_LIMIT
48
65
  #endif
49
66
 
50
- enum heap_record_key_type {
51
- HEAP_STACK,
52
- LOCATION_SLICE
53
- };
54
- // This struct allows us to use two different types of stacks when
55
- // interacting with a heap_record hash.
56
- //
57
- // The idea is that we'll always want to use heap_stack-keys when
58
- // adding new entries to the hash since that's the compact stack
59
- // representation we rely on internally.
60
- //
61
- // However, when querying for an existing heap record, we'd save a
62
- // lot of allocations if we could query with the
63
- // ddog_prof_Slice_Location we receive in our external API.
64
- //
65
- // To allow this interchange, we need a union and need to ensure
66
- // that whatever shape of the union, the heap_record_key_cmp_st
67
- // and heap_record_hash_st functions return the same results for
68
- // equivalent stacktraces.
69
- typedef struct {
70
- enum heap_record_key_type type;
71
- union {
72
- // key never owns this if set
73
- heap_stack *heap_stack;
74
- // key never owns this if set
75
- ddog_prof_Slice_Location *location_slice;
76
- };
77
- } heap_record_key;
78
- static heap_record_key* heap_record_key_new(heap_stack*);
79
- static void heap_record_key_free(heap_record_key*);
80
- static int heap_record_key_cmp_st(st_data_t, st_data_t);
81
- static st_index_t heap_record_key_hash_st(st_data_t);
82
- static const struct st_hash_type st_hash_type_heap_record_key = {
83
- heap_record_key_cmp_st,
84
- heap_record_key_hash_st,
85
- };
86
-
87
- // Need to implement these functions to support the location-slice based keys
88
- static st_index_t ddog_location_hash(ddog_prof_Location, st_index_t seed);
89
- static st_index_t ddog_location_slice_hash(ddog_prof_Slice_Location, st_index_t seed);
90
-
91
- // A heap record is used for deduping heap allocation stacktraces across multiple
92
- // objects sharing the same allocation location.
93
- typedef struct {
94
- // How many objects are currently tracked by the heap recorder for this heap record.
95
- uint32_t num_tracked_objects;
96
- // stack is owned by the associated record and gets cleaned up alongside it
97
- heap_stack *stack;
98
- } heap_record;
99
- static heap_record* heap_record_new(heap_stack*);
100
- static void heap_record_free(heap_record*);
67
+ static int heap_record_cmp_st(st_data_t, st_data_t);
68
+ static st_index_t heap_record_hash_st(st_data_t);
69
+ static const struct st_hash_type st_hash_type_heap_record = { .compare = heap_record_cmp_st, .hash = heap_record_hash_st };
101
70
 
102
71
  // An object record is used for storing data about currently tracked live objects
103
72
  typedef struct {
@@ -106,22 +75,35 @@ typedef struct {
106
75
  live_object_data object_data;
107
76
  } object_record;
108
77
  static object_record* object_record_new(long, heap_record*, live_object_data);
109
- static void object_record_free(object_record*);
110
- static VALUE object_record_inspect(object_record*);
78
+ static void object_record_free(heap_recorder*, object_record*, bool should_unintern);
79
+ static VALUE object_record_inspect(heap_recorder*, object_record*);
111
80
  static object_record SKIPPED_RECORD = {0};
112
81
 
82
+ // A pending recording is used to defer the object_id call on Ruby 4+
83
+ // where calling rb_obj_id during on_newobj_event is unsafe.
84
+ typedef struct {
85
+ VALUE object_ref;
86
+ heap_record *heap_record;
87
+ live_object_data object_data;
88
+ } pending_recording;
89
+
90
+ #define MAX_PENDING_RECORDINGS 256
91
+
113
92
  struct heap_recorder {
114
93
  // Config
115
94
  // Whether the recorder should try to determine approximate sizes for tracked objects.
116
95
  bool size_enabled;
117
96
  uint sample_rate;
118
97
 
119
- // Map[key: heap_record_key*, record: heap_record*]
120
- // NOTE: We always use heap_record_key.type == HEAP_STACK for storage but support lookups
121
- // via heap_record_key.type == LOCATION_SLICE to allow for allocation-free fast-paths.
98
+ // Map[key: heap_record*, record: nothing] (This is a set, basically)
122
99
  // NOTE: This table is currently only protected by the GVL since we never interact with it
123
100
  // outside the GVL.
124
- // NOTE: This table has ownership of both its heap_record_keys and heap_records.
101
+ // NOTE: This table has ownership of its heap_records.
102
+ //
103
+ // This is a cpu/memory trade-off: Maintaining the "heap_records" map means we spend extra CPU when sampling as we need
104
+ // to do de-duplication, but we reduce the memory footprint of the heap profiler.
105
+ // In the future, it may be worth revisiting if we can move this inside libdatadog: if libdatadog was able to track
106
+ // entire stacks for us, then we wouldn't need to do it on the Ruby side.
125
107
  st_table *heap_records;
126
108
 
127
109
  // Map[obj_id: long, record: object_record*]
@@ -132,6 +114,8 @@ struct heap_recorder {
132
114
  //
133
115
  // TODO: @ivoanjo We've evolved to actually never need to look up on object_records (we only insert and iterate),
134
116
  // so right now this seems to be just a really really fancy self-resizing list/set.
117
+ // If we replace this with a list, we could record the latest id and compare it when inserting to make sure our
118
+ // assumption of ids never reused + always increasing always holds. (This as an alternative to checking for duplicates)
135
119
  st_table *object_records;
136
120
 
137
121
  // Map[obj_id: long, record: object_record*]
@@ -156,12 +140,28 @@ struct heap_recorder {
156
140
  // Data for a heap recording that was started but not yet ended
157
141
  object_record *active_recording;
158
142
 
159
- // Reusable location array, implementing a flyweight pattern for things like iteration.
143
+ // Pending recordings that need to be finalized after on_newobj_event completes.
144
+ // On Ruby 4+, we can't call rb_obj_id during the newobj event, so we store the
145
+ // VALUE reference here and finalize it via a postponed job.
146
+ pending_recording pending_recordings[MAX_PENDING_RECORDINGS];
147
+ // Temporary storage for the recording in progress, used between start and end
148
+ VALUE active_deferred_object;
149
+ live_object_data active_deferred_object_data;
150
+ uint16_t pending_recordings_count;
151
+
152
+ // Reusable arrays, implementing a flyweight pattern for things like iteration
153
+ #define REUSABLE_LOCATIONS_SIZE MAX_FRAMES_LIMIT
160
154
  ddog_prof_Location *reusable_locations;
161
155
 
156
+ #define REUSABLE_FRAME_DETAILS_SIZE (2 * MAX_FRAMES_LIMIT) // because it'll be used for both function names AND file names)
157
+ ddog_prof_ManagedStringId *reusable_ids;
158
+ ddog_CharSlice *reusable_char_slices;
159
+
162
160
  // Sampling state
163
161
  uint num_recordings_skipped;
164
162
 
163
+ ddog_prof_ManagedStringStorage string_storage;
164
+
165
165
  struct stats_last_update {
166
166
  size_t objects_alive;
167
167
  size_t objects_dead;
@@ -182,27 +182,35 @@ struct heap_recorder {
182
182
  double ewma_objects_alive;
183
183
  double ewma_objects_dead;
184
184
  double ewma_objects_skipped;
185
+
186
+ unsigned long deferred_recordings_skipped_buffer_full;
187
+ unsigned long deferred_recordings_finalized;
185
188
  } stats_lifetime;
186
189
  };
187
190
 
188
- struct end_heap_allocation_args {
189
- struct heap_recorder *heap_recorder;
191
+ typedef struct {
192
+ heap_recorder *heap_recorder;
190
193
  ddog_prof_Slice_Location locations;
191
- };
194
+ } end_heap_allocation_args;
192
195
 
193
196
  static heap_record* get_or_create_heap_record(heap_recorder*, ddog_prof_Slice_Location);
194
197
  static void cleanup_heap_record_if_unused(heap_recorder*, heap_record*);
195
198
  static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, object_record *record);
196
- static int st_heap_record_entry_free(st_data_t, st_data_t, st_data_t);
197
- static int st_object_record_entry_free(st_data_t, st_data_t, st_data_t);
199
+ static int st_heap_record_entry_free_no_unintern(st_data_t, st_data_t, st_data_t);
200
+ static int st_object_record_entry_free_no_unintern(st_data_t, st_data_t, st_data_t);
198
201
  static int st_object_record_update(st_data_t, st_data_t, st_data_t);
199
202
  static int st_object_records_iterate(st_data_t, st_data_t, st_data_t);
200
203
  static int st_object_records_debug(st_data_t key, st_data_t value, st_data_t extra);
201
204
  static int update_object_record_entry(st_data_t*, st_data_t*, st_data_t, int);
205
+ static void inc_tracked_objects_or_fail(heap_record *heap_record);
202
206
  static void commit_recording(heap_recorder *, heap_record *, object_record *active_recording);
203
207
  static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args);
204
208
  static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update);
205
209
  static inline double ewma_stat(double previous, double current);
210
+ static void unintern_or_raise(heap_recorder *, ddog_prof_ManagedStringId);
211
+ static void unintern_all_or_raise(heap_recorder *recorder, ddog_prof_Slice_ManagedStringId ids);
212
+ static VALUE get_ruby_string_or_raise(heap_recorder*, ddog_prof_ManagedStringId);
213
+ static long obj_id_or_fail(VALUE obj);
206
214
 
207
215
  // ==========================
208
216
  // Heap Recorder External API
@@ -213,16 +221,20 @@ static inline double ewma_stat(double previous, double current);
213
221
  // happens under the GVL.
214
222
  //
215
223
  // ==========================
216
- heap_recorder* heap_recorder_new(void) {
224
+ heap_recorder* heap_recorder_new(ddog_prof_ManagedStringStorage string_storage) {
217
225
  heap_recorder *recorder = ruby_xcalloc(1, sizeof(heap_recorder));
218
226
 
219
- recorder->heap_records = st_init_table(&st_hash_type_heap_record_key);
227
+ recorder->heap_records = st_init_table(&st_hash_type_heap_record);
220
228
  recorder->object_records = st_init_numtable();
221
229
  recorder->object_records_snapshot = NULL;
222
- recorder->reusable_locations = ruby_xcalloc(MAX_FRAMES_LIMIT, sizeof(ddog_prof_Location));
230
+ recorder->reusable_locations = ruby_xcalloc(REUSABLE_LOCATIONS_SIZE, sizeof(ddog_prof_Location));
231
+ recorder->reusable_ids = ruby_xcalloc(REUSABLE_FRAME_DETAILS_SIZE, sizeof(ddog_prof_ManagedStringId));
232
+ recorder->reusable_char_slices = ruby_xcalloc(REUSABLE_FRAME_DETAILS_SIZE, sizeof(ddog_CharSlice));
223
233
  recorder->active_recording = NULL;
224
234
  recorder->size_enabled = true;
225
235
  recorder->sample_rate = 1; // By default do no sampling on top of what allocation profiling already does
236
+ recorder->string_storage = string_storage;
237
+ recorder->active_deferred_object = Qnil;
226
238
 
227
239
  return recorder;
228
240
  }
@@ -238,20 +250,29 @@ void heap_recorder_free(heap_recorder *heap_recorder) {
238
250
  heap_recorder_finish_iteration(heap_recorder);
239
251
  }
240
252
 
253
+ // NOTE: We don't unintern the strings referenced by the records we're about to free, thus we use
254
+ // `..._no_unintern` and `should_unintern: false`. This is intentional: `heap_recorder_free` is only ever called as part of
255
+ // tearing down the entire stack recorder, and the caller drops the whole managed string storage right after
256
+ // (see `stack_recorder_typed_data_free`), so there's no need to spend effort updating the managed string table.
257
+ // Crucially, this also keeps us from crashing: this code runs from the stack recorder's GC free callback, and
258
+ // because uninterning can fail, we can't raise exceptions in the middle of a dfree.
259
+
241
260
  // Clean-up all object records
242
- st_foreach(heap_recorder->object_records, st_object_record_entry_free, 0);
261
+ st_foreach(heap_recorder->object_records, st_object_record_entry_free_no_unintern, (st_data_t) heap_recorder);
243
262
  st_free_table(heap_recorder->object_records);
244
263
 
245
264
  // Clean-up all heap records (this includes those only referred to by queued_samples)
246
- st_foreach(heap_recorder->heap_records, st_heap_record_entry_free, 0);
265
+ st_foreach(heap_recorder->heap_records, st_heap_record_entry_free_no_unintern, (st_data_t) heap_recorder);
247
266
  st_free_table(heap_recorder->heap_records);
248
267
 
249
268
  if (heap_recorder->active_recording != NULL && heap_recorder->active_recording != &SKIPPED_RECORD) {
250
269
  // If there's a partial object record, clean it up as well
251
- object_record_free(heap_recorder->active_recording);
270
+ object_record_free(heap_recorder, heap_recorder->active_recording, false);
252
271
  }
253
272
 
254
273
  ruby_xfree(heap_recorder->reusable_locations);
274
+ ruby_xfree(heap_recorder->reusable_ids);
275
+ ruby_xfree(heap_recorder->reusable_char_slices);
255
276
 
256
277
  ruby_xfree(heap_recorder);
257
278
  }
@@ -270,7 +291,7 @@ void heap_recorder_set_sample_rate(heap_recorder *heap_recorder, int sample_rate
270
291
  }
271
292
 
272
293
  if (sample_rate <= 0) {
273
- rb_raise(rb_eArgError, "Heap sample rate must be a positive integer value but was %d", sample_rate);
294
+ raise_error(rb_eArgError, "Heap sample rate must be a positive integer value but was %d", sample_rate);
274
295
  }
275
296
 
276
297
  heap_recorder->sample_rate = sample_rate;
@@ -292,7 +313,7 @@ void heap_recorder_after_fork(heap_recorder *heap_recorder) {
292
313
  // simply be noticed on next heap_recorder_prepare_iteration.
293
314
  //
294
315
  // There is one small caveat though: fork only preserves one thread and in a Ruby app, that
295
- // will be the thread holding on to the GVL. Since we support iteration on the heap recorder
316
+ // will be the thread holding the GVL. Since we support iteration on the heap recorder
296
317
  // outside of the GVL, any state specific to that interaction may be inconsistent after fork
297
318
  // (e.g. an acquired lock for thread safety). Iteration operates on object_records_snapshot
298
319
  // though and that one will be updated on next heap_recorder_prepare_iteration so we really
@@ -305,88 +326,144 @@ void heap_recorder_after_fork(heap_recorder *heap_recorder) {
305
326
  heap_recorder->stats_lifetime = (struct stats_lifetime) {0};
306
327
  }
307
328
 
308
- void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight, ddog_CharSlice alloc_class) {
329
+ bool start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj, unsigned int weight, ddog_CharSlice alloc_class) {
309
330
  if (heap_recorder == NULL) {
310
- return;
331
+ return false;
311
332
  }
312
333
 
313
334
  if (heap_recorder->active_recording != NULL) {
314
- rb_raise(rb_eRuntimeError, "Detected consecutive heap allocation recording starts without end.");
335
+ raise_error(rb_eRuntimeError, "Detected consecutive heap allocation recording starts without end.");
315
336
  }
316
337
 
317
338
  if (++heap_recorder->num_recordings_skipped < heap_recorder->sample_rate ||
318
339
  #ifdef NO_IMEMO_OBJECT_ID
319
- // On Ruby 3.5, we can't ask the object_id from IMEMOs (https://github.com/ruby/ruby/pull/13347)
340
+ // On Ruby 4, we can't ask the object_id from IMEMOs (https://github.com/ruby/ruby/pull/13347)
320
341
  RB_BUILTIN_TYPE(new_obj) == RUBY_T_IMEMO
321
342
  #else
322
343
  false
323
344
  #endif
345
+ // If we got really unlucky and an allocation showed up during an update (because it triggered an allocation
346
+ // directly OR because the GVL got released in the middle of an update), let's skip this sample as well.
347
+ // See notes on `heap_recorder_update` for details.
348
+ || heap_recorder->updating
324
349
  ) {
325
350
  heap_recorder->active_recording = &SKIPPED_RECORD;
326
- return;
351
+ return false;
327
352
  }
328
353
 
329
- heap_recorder->num_recordings_skipped = 0;
354
+ bool needs_after_allocation = false;
330
355
 
331
- VALUE ruby_obj_id = rb_obj_id(new_obj);
332
- if (!FIXNUM_P(ruby_obj_id)) {
333
- rb_raise(rb_eRuntimeError, "Detected a bignum object id. These are not supported by heap profiling.");
356
+ #ifdef USE_DEFERRED_HEAP_ALLOCATION_RECORDING
357
+ // Skip if we've hit the pending recordings limit or if there's already a deferred object being recorded
358
+ if (heap_recorder->pending_recordings_count >= MAX_PENDING_RECORDINGS) {
359
+ heap_recorder->stats_lifetime.deferred_recordings_skipped_buffer_full++;
360
+ heap_recorder->active_recording = &SKIPPED_RECORD;
361
+ return true; // If the buffer is full, we keep asking for a callback (see `needs_after_allocation` below)
362
+ } else {
363
+ // The intuition here is: We start by asking for an `after_allocation` callback when the buffer is about to go
364
+ // from empty -> non-empty, because this is going to be mapped onto a postponed job, so after it gets queued once
365
+ // it doesn't seem worth it to keep spamming requests.
366
+ //
367
+ // Yet, if for some reason the postponed job doesn't flush the pending list (or if e.g. it ran with `during_sample == true` and thus
368
+ // was skipped) we need to have some mechanism to recover -- and so if the buffer starts accumulating too much we
369
+ // start always requesting the callback to happen so that we eventually flush the buffer.
370
+ needs_after_allocation =
371
+ heap_recorder->pending_recordings_count == 0 || heap_recorder->pending_recordings_count >= (MAX_PENDING_RECORDINGS / 2);
334
372
  }
373
+ #endif
335
374
 
336
- heap_recorder->active_recording = object_record_new(
337
- FIX2LONG(ruby_obj_id),
338
- NULL,
339
- (live_object_data) {
340
- .weight = weight * heap_recorder->sample_rate,
341
- .class = string_from_char_slice(alloc_class),
342
- .alloc_gen = rb_gc_count(),
343
- }
344
- );
375
+ heap_recorder->num_recordings_skipped = 0;
376
+
377
+ live_object_data object_data = (live_object_data) {
378
+ .weight = weight * heap_recorder->sample_rate,
379
+ .class = intern_or_raise(heap_recorder->string_storage, alloc_class),
380
+ .alloc_gen = rb_gc_count(),
381
+ };
382
+
383
+ #ifdef USE_DEFERRED_HEAP_ALLOCATION_RECORDING
384
+ // On Ruby 4+, we can't call rb_obj_id during on_newobj_event as it mutates the object.
385
+ // Instead, we store the VALUE reference and will get the object_id later via a postponed job.
386
+ // active_deferred_object != Qnil indicates we're in deferred mode.
387
+ heap_recorder->active_deferred_object = new_obj;
388
+ heap_recorder->active_deferred_object_data = object_data;
389
+ #else
390
+ heap_recorder->active_recording = object_record_new(obj_id_or_fail(new_obj), NULL, object_data);
391
+ #endif
392
+
393
+ return needs_after_allocation;
345
394
  }
346
395
 
347
396
  // end_heap_allocation_recording_with_rb_protect gets called while the stack_recorder is holding one of the profile
348
397
  // locks. To enable us to correctly unlock the profile on exception, we wrap the call to end_heap_allocation_recording
349
398
  // with an rb_protect.
350
399
  __attribute__((warn_unused_result))
351
- int end_heap_allocation_recording_with_rb_protect(struct heap_recorder *heap_recorder, ddog_prof_Slice_Location locations) {
400
+ int end_heap_allocation_recording_with_rb_protect(heap_recorder *heap_recorder, ddog_prof_Slice_Location locations) {
352
401
  if (heap_recorder == NULL) {
353
402
  return 0;
354
403
  }
404
+ if (heap_recorder->active_recording == &SKIPPED_RECORD) {
405
+ // Short circuit, in this case there's nothing to be done
406
+ heap_recorder->active_recording = NULL;
407
+ return 0;
408
+ }
355
409
 
356
410
  int exception_state;
357
- struct end_heap_allocation_args end_heap_allocation_args = {
411
+ end_heap_allocation_args args = {
358
412
  .heap_recorder = heap_recorder,
359
413
  .locations = locations,
360
414
  };
361
- rb_protect(end_heap_allocation_recording, (VALUE) &end_heap_allocation_args, &exception_state);
415
+ rb_protect(end_heap_allocation_recording, (VALUE) &args, &exception_state);
362
416
  return exception_state;
363
417
  }
364
418
 
365
- static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args) {
366
- struct end_heap_allocation_args *args = (struct end_heap_allocation_args *) end_heap_allocation_args;
419
+ static VALUE end_heap_allocation_recording(VALUE protect_args) {
420
+ end_heap_allocation_args *args = (end_heap_allocation_args *) protect_args;
367
421
 
368
- struct heap_recorder *heap_recorder = args->heap_recorder;
422
+ heap_recorder *heap_recorder = args->heap_recorder;
369
423
  ddog_prof_Slice_Location locations = args->locations;
370
424
 
371
- object_record *active_recording = heap_recorder->active_recording;
372
-
373
- if (active_recording == NULL) {
374
- // Recording ended without having been started?
375
- rb_raise(rb_eRuntimeError, "Ended a heap recording that was not started");
376
- }
377
- // From now on, mark the global active recording as invalid so we can short-circuit at any point
378
- // and not end up with a still active recording. the local active_recording still holds the
379
- // data required for committing though.
380
- heap_recorder->active_recording = NULL;
425
+ #ifdef USE_DEFERRED_HEAP_ALLOCATION_RECORDING
426
+ if (heap_recorder->active_deferred_object == Qnil) {
427
+ // Recording ended without having been started?
428
+ raise_error(rb_eRuntimeError, "Ended a heap recording that was not started");
429
+ }
430
+ #else
431
+ object_record *active_recording = heap_recorder->active_recording;
381
432
 
382
- if (active_recording == &SKIPPED_RECORD) { // special marker when we decided to skip due to sampling
383
- return Qnil;
384
- }
433
+ if (active_recording == NULL) {
434
+ // Recording ended without having been started?
435
+ raise_error(rb_eRuntimeError, "Ended a heap recording that was not started");
436
+ }
437
+ // From now on, mark the global active recording as invalid so we can short-circuit at any point
438
+ // and not end up with a still active recording. the local active_recording still holds the
439
+ // data required for committing though.
440
+ heap_recorder->active_recording = NULL;
441
+
442
+ if (active_recording == &SKIPPED_RECORD) {
443
+ raise_error(
444
+ rb_eRuntimeError,
445
+ "BUG: end_heap_allocation_recording should never observe SKIPPED_RECORDING because " \
446
+ "end_heap_allocation_recording_with_rb_protect is supposed to test for it directly"
447
+ );
448
+ }
449
+ #endif
385
450
 
386
451
  heap_record *heap_record = get_or_create_heap_record(heap_recorder, locations);
387
-
388
- // And then commit the new allocation.
389
- commit_recording(heap_recorder, heap_record, active_recording);
452
+ inc_tracked_objects_or_fail(heap_record);
453
+
454
+ #ifdef USE_DEFERRED_HEAP_ALLOCATION_RECORDING
455
+ // Commit is delayed, so we need to record all we'll need for it
456
+ pending_recording *pending = &heap_recorder->pending_recordings[heap_recorder->pending_recordings_count++];
457
+ pending->object_ref = heap_recorder->active_deferred_object;
458
+ pending->heap_record = heap_record;
459
+ pending->object_data = heap_recorder->active_deferred_object_data;
460
+
461
+ heap_recorder->active_deferred_object = Qnil;
462
+ heap_recorder->active_deferred_object_data = (live_object_data) {0};
463
+ #else
464
+ // And then commit the new allocation
465
+ commit_recording(heap_recorder, heap_record, active_recording);
466
+ #endif
390
467
 
391
468
  return Qnil;
392
469
  }
@@ -399,17 +476,72 @@ void heap_recorder_update_young_objects(heap_recorder *heap_recorder) {
399
476
  heap_recorder_update(heap_recorder, /* full_update: */ false);
400
477
  }
401
478
 
402
- static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update) {
403
- if (heap_recorder->updating) {
404
- if (full_update) rb_raise(rb_eRuntimeError, "BUG: full_update should not be triggered during another update");
479
+ void heap_recorder_finalize_pending_recordings(heap_recorder *heap_recorder) {
480
+ if (heap_recorder == NULL) {
481
+ return; // Nothing to do
482
+ }
483
+
484
+ uint count = heap_recorder->pending_recordings_count;
485
+ if (count == 0) {
486
+ return; // Nothing to do
487
+ }
488
+
489
+ heap_recorder->stats_lifetime.deferred_recordings_finalized += count;
490
+
491
+ for (uint i = 0; i < count; i++) {
492
+ pending_recording *pending = &heap_recorder->pending_recordings[i];
493
+
494
+ // This is the step we couldn't do during the original sample call -- we're now expected to be called in a context
495
+ // where it's finally safe to call this
496
+ long obj_id = obj_id_or_fail(pending->object_ref);
497
+
498
+ // Create the object record now that we have the object_id
499
+ object_record *record = object_record_new(obj_id, pending->heap_record, pending->object_data);
500
+
501
+ commit_recording(heap_recorder, pending->heap_record, record);
502
+ }
503
+
504
+ heap_recorder->pending_recordings_count = 0;
505
+ }
405
506
 
406
- // If we try to update while another update is still running, short-circuit.
407
- // NOTE: This runs while holding the GVL. But since updates may be triggered from GC activity, there's still
408
- // a chance for updates to be attempted concurrently if scheduling gods so determine.
409
- heap_recorder->stats_lifetime.updates_skipped_concurrent++;
507
+ // Mark pending recordings to prevent GC from collecting the objects
508
+ // while they're waiting to be finalized
509
+ void heap_recorder_mark_pending_recordings(heap_recorder *heap_recorder) {
510
+ if (heap_recorder == NULL) {
410
511
  return;
411
512
  }
412
513
 
514
+ for (uint i = 0; i < heap_recorder->pending_recordings_count; i++) {
515
+ rb_gc_mark(heap_recorder->pending_recordings[i].object_ref);
516
+ }
517
+
518
+ rb_gc_mark(heap_recorder->active_deferred_object);
519
+ }
520
+
521
+ // NOTE: This function needs and assumes it gets called with the GVL being held.
522
+ // But importantly **some of the operations inside `st_object_record_update` may cause a thread switch**,
523
+ // so we can't assume a single update happens in a single "atomic" step -- other threads may get some running time
524
+ // in the meanwhile.
525
+ static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update) {
526
+ if (heap_recorder->updating) {
527
+ if (full_update) {
528
+ // There's another thread that's already doing an update :(
529
+ //
530
+ // Because there's a lock on the `StackRecorder` (see @no_concurrent_serialize_mutex) then it's not possible that
531
+ // the other update is a full update.
532
+ // Thus we expect is happening is that the GVL got released by the other thread in the middle of a non-full update
533
+ // and the scheduler thread decided now was a great time to serialize the profile.
534
+ //
535
+ // So, let's yield the time on the current thread until Ruby goes back to the other thread doing the update and
536
+ // it finishes cleanly.
537
+ while (heap_recorder->updating) { rb_thread_schedule(); }
538
+ } else {
539
+ // Non-full updates are optional, so let's walk away
540
+ heap_recorder->stats_lifetime.updates_skipped_concurrent++;
541
+ return;
542
+ }
543
+ }
544
+
413
545
  if (heap_recorder->object_records_snapshot != NULL) {
414
546
  // While serialization is happening, it runs without the GVL and uses the object_records_snapshot.
415
547
  // Although we iterate on a snapshot of object_records, these records point to other data that has not been
@@ -474,14 +606,14 @@ void heap_recorder_prepare_iteration(heap_recorder *heap_recorder) {
474
606
 
475
607
  if (heap_recorder->object_records_snapshot != NULL) {
476
608
  // we could trivially handle this but we raise to highlight and catch unexpected usages.
477
- rb_raise(rb_eRuntimeError, "New heap recorder iteration prepared without the previous one having been finished.");
609
+ raise_error(rb_eRuntimeError, "New heap recorder iteration prepared without the previous one having been finished.");
478
610
  }
479
611
 
480
612
  heap_recorder_update(heap_recorder, /* full_update: */ true);
481
613
 
482
614
  heap_recorder->object_records_snapshot = st_copy(heap_recorder->object_records);
483
615
  if (heap_recorder->object_records_snapshot == NULL) {
484
- rb_raise(rb_eRuntimeError, "Failed to create heap snapshot.");
616
+ raise_error(rb_eRuntimeError, "Failed to create heap snapshot.");
485
617
  }
486
618
  }
487
619
 
@@ -492,7 +624,7 @@ void heap_recorder_finish_iteration(heap_recorder *heap_recorder) {
492
624
 
493
625
  if (heap_recorder->object_records_snapshot == NULL) {
494
626
  // we could trivially handle this but we raise to highlight and catch unexpected usages.
495
- rb_raise(rb_eRuntimeError, "Heap recorder iteration finished without having been prepared.");
627
+ raise_error(rb_eRuntimeError, "Heap recorder iteration finished without having been prepared.");
496
628
  }
497
629
 
498
630
  st_free_table(heap_recorder->object_records_snapshot);
@@ -534,20 +666,21 @@ bool heap_recorder_for_each_live_object(
534
666
 
535
667
  VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder) {
536
668
  VALUE arguments[] = {
537
- ID2SYM(rb_intern("num_object_records")), /* => */ LONG2NUM(heap_recorder->object_records->num_entries),
538
- ID2SYM(rb_intern("num_heap_records")), /* => */ LONG2NUM(heap_recorder->heap_records->num_entries),
669
+ ID2SYM(rb_intern("num_object_records")), /* => */ ULONG2NUM(heap_recorder->object_records->num_entries),
670
+ ID2SYM(rb_intern("num_heap_records")), /* => */ ULONG2NUM(heap_recorder->heap_records->num_entries),
671
+ ID2SYM(rb_intern("pending_recordings_count")), /* => */ ULONG2NUM(heap_recorder->pending_recordings_count),
539
672
 
540
673
  // Stats as of last update
541
- ID2SYM(rb_intern("last_update_objects_alive")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_alive),
542
- ID2SYM(rb_intern("last_update_objects_dead")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_dead),
543
- ID2SYM(rb_intern("last_update_objects_skipped")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_skipped),
544
- ID2SYM(rb_intern("last_update_objects_frozen")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_frozen),
674
+ ID2SYM(rb_intern("last_update_objects_alive")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_alive),
675
+ ID2SYM(rb_intern("last_update_objects_dead")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_dead),
676
+ ID2SYM(rb_intern("last_update_objects_skipped")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_skipped),
677
+ ID2SYM(rb_intern("last_update_objects_frozen")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_frozen),
545
678
 
546
679
  // Lifetime stats
547
- ID2SYM(rb_intern("lifetime_updates_successful")), /* => */ LONG2NUM(heap_recorder->stats_lifetime.updates_successful),
548
- ID2SYM(rb_intern("lifetime_updates_skipped_concurrent")), /* => */ LONG2NUM(heap_recorder->stats_lifetime.updates_skipped_concurrent),
549
- ID2SYM(rb_intern("lifetime_updates_skipped_gcgen")), /* => */ LONG2NUM(heap_recorder->stats_lifetime.updates_skipped_gcgen),
550
- ID2SYM(rb_intern("lifetime_updates_skipped_time")), /* => */ LONG2NUM(heap_recorder->stats_lifetime.updates_skipped_time),
680
+ ID2SYM(rb_intern("lifetime_updates_successful")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_successful),
681
+ ID2SYM(rb_intern("lifetime_updates_skipped_concurrent")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_skipped_concurrent),
682
+ ID2SYM(rb_intern("lifetime_updates_skipped_gcgen")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_skipped_gcgen),
683
+ ID2SYM(rb_intern("lifetime_updates_skipped_time")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_skipped_time),
551
684
  ID2SYM(rb_intern("lifetime_ewma_young_objects_alive")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_alive),
552
685
  ID2SYM(rb_intern("lifetime_ewma_young_objects_dead")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_dead),
553
686
  // Note: Here "young" refers to the young update; objects skipped includes non-young objects
@@ -555,61 +688,54 @@ VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder) {
555
688
  ID2SYM(rb_intern("lifetime_ewma_objects_alive")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_alive),
556
689
  ID2SYM(rb_intern("lifetime_ewma_objects_dead")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_dead),
557
690
  ID2SYM(rb_intern("lifetime_ewma_objects_skipped")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_skipped),
691
+
692
+ ID2SYM(rb_intern("lifetime_deferred_recordings_skipped_buffer_full")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.deferred_recordings_skipped_buffer_full),
693
+ ID2SYM(rb_intern("lifetime_deferred_recordings_finalized")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.deferred_recordings_finalized),
558
694
  };
559
695
  VALUE hash = rb_hash_new();
560
696
  for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
697
+
561
698
  return hash;
562
699
  }
563
700
 
564
- void heap_recorder_testonly_assert_hash_matches(ddog_prof_Slice_Location locations) {
565
- heap_stack *stack = heap_stack_new(locations);
566
- heap_record_key stack_based_key = (heap_record_key) {
567
- .type = HEAP_STACK,
568
- .heap_stack = stack,
569
- };
570
- heap_record_key location_based_key = (heap_record_key) {
571
- .type = LOCATION_SLICE,
572
- .location_slice = &locations,
573
- };
574
-
575
- st_index_t stack_hash = heap_record_key_hash_st((st_data_t) &stack_based_key);
576
- st_index_t location_hash = heap_record_key_hash_st((st_data_t) &location_based_key);
577
-
578
- heap_stack_free(stack);
579
-
580
- if (stack_hash != location_hash) {
581
- 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);
582
- }
583
- }
701
+ typedef struct {
702
+ heap_recorder *recorder;
703
+ VALUE debug_ary;
704
+ } debug_context;
584
705
 
585
706
  VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder) {
586
707
  if (heap_recorder == NULL) {
587
- rb_raise(rb_eArgError, "heap_recorder is NULL");
708
+ raise_error(rb_eArgError, "heap_recorder is NULL");
588
709
  }
589
710
 
590
- VALUE debug_str = rb_str_new2("object records:\n");
591
- st_foreach(heap_recorder->object_records, st_object_records_debug, (st_data_t) debug_str);
592
-
593
- rb_str_catf(debug_str, "state snapshot: %"PRIsVALUE"\n------\n", heap_recorder_state_snapshot(heap_recorder));
711
+ VALUE debug_ary = rb_ary_new();
712
+ debug_context context = (debug_context) {.recorder = heap_recorder, .debug_ary = debug_ary};
713
+ st_foreach(heap_recorder->object_records, st_object_records_debug, (st_data_t) &context);
594
714
 
595
- return debug_str;
715
+ return rb_ary_new_from_args(2,
716
+ rb_ary_new_from_args(2, ID2SYM(rb_intern("records")), debug_ary),
717
+ rb_ary_new_from_args(2, ID2SYM(rb_intern("state")), heap_recorder_state_snapshot(heap_recorder))
718
+ );
596
719
  }
597
720
 
598
721
  // ==========================
599
722
  // Heap Recorder Internal API
600
723
  // ==========================
601
- static int st_heap_record_entry_free(st_data_t key, st_data_t value, DDTRACE_UNUSED st_data_t extra_arg) {
602
- heap_record_key *record_key = (heap_record_key*) key;
603
- heap_record_key_free(record_key);
604
- heap_record_free((heap_record *) value);
724
+ // NOTE: Only expected to be used from heap_recorder_free, which will separately destroy the string table
725
+ static int st_heap_record_entry_free_no_unintern(st_data_t key, DDTRACE_UNUSED st_data_t value, st_data_t extra_arg) {
726
+ heap_recorder *recorder = (heap_recorder *) extra_arg;
727
+ heap_record_free(recorder, (heap_record *) key, false);
605
728
  return ST_DELETE;
606
729
  }
607
730
 
608
- static int st_object_record_entry_free(DDTRACE_UNUSED st_data_t key, st_data_t value, DDTRACE_UNUSED st_data_t extra_arg) {
609
- object_record_free((object_record *) value);
731
+ // NOTE: Only expected to be used from heap_recorder_free, which will separately destroy the string table
732
+ static int st_object_record_entry_free_no_unintern(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra_arg) {
733
+ heap_recorder *recorder = (heap_recorder *) extra_arg;
734
+ object_record_free(recorder, (object_record *) value, false);
610
735
  return ST_DELETE;
611
736
  }
612
737
 
738
+ // NOTE: Some operations inside this function can cause the GVL to be released! Plan accordingly.
613
739
  static int st_object_record_update(st_data_t key, st_data_t value, st_data_t extra_arg) {
614
740
  long obj_id = (long) key;
615
741
  object_record *record = (object_record*) value;
@@ -635,7 +761,7 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
635
761
  return ST_CONTINUE;
636
762
  }
637
763
 
638
- if (!ruby_ref_from_id(LONG2NUM(obj_id), &ref)) {
764
+ if (!ruby_ref_from_id(LONG2NUM(obj_id), &ref)) { // Note: This function call can cause the GVL to be released
639
765
  // Id no longer associated with a valid ref. Need to delete this object record!
640
766
  on_committed_object_record_cleanup(recorder, record);
641
767
  recorder->stats_last_update.objects_dead++;
@@ -651,7 +777,8 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
651
777
  ) {
652
778
  // if we were asked to update sizes and this object was not already seen as being frozen,
653
779
  // update size again.
654
- record->object_data.size = ruby_obj_memsize_of(ref);
780
+ record->object_data.size = ruby_obj_memsize_of(ref); // Note: This function call can cause the GVL to be released... maybe?
781
+ // (With T_DATA for instance, since it can be a custom method supplied by extensions)
655
782
  // Check if it's now frozen so we skip a size update next time
656
783
  record->object_data.is_frozen = RB_OBJ_FROZEN(ref);
657
784
  }
@@ -671,7 +798,7 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
671
798
  // WARN: This can get called outside the GVL. NO HEAP ALLOCATIONS OR EXCEPTIONS ARE ALLOWED.
672
799
  static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra) {
673
800
  object_record *record = (object_record*) value;
674
- const heap_stack *stack = record->heap_record->stack;
801
+ const heap_record *stack = record->heap_record;
675
802
  iteration_context *context = (iteration_context*) extra;
676
803
 
677
804
  const heap_recorder *recorder = context->heap_recorder;
@@ -687,8 +814,10 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
687
814
  locations[i] = (ddog_prof_Location) {
688
815
  .mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
689
816
  .function = {
690
- .name = {.ptr = frame->name, .len = strlen(frame->name)},
691
- .filename = {.ptr = frame->filename, .len = strlen(frame->filename)},
817
+ .name = DDOG_CHARSLICE_C(""),
818
+ .name_id = frame->name,
819
+ .filename = DDOG_CHARSLICE_C(""),
820
+ .filename_id = frame->filename,
692
821
  },
693
822
  .line = frame->line,
694
823
  };
@@ -707,11 +836,11 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
707
836
  }
708
837
 
709
838
  static int st_object_records_debug(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra) {
710
- VALUE debug_str = (VALUE) extra;
839
+ debug_context *context = (debug_context*) extra;
711
840
 
712
841
  object_record *record = (object_record*) value;
713
842
 
714
- rb_str_catf(debug_str, "%"PRIsVALUE"\n", object_record_inspect(record));
843
+ rb_ary_push(context->debug_ary, object_record_inspect(context->recorder, record));
715
844
 
716
845
  return ST_CONTINUE;
717
846
  }
@@ -725,75 +854,53 @@ static int update_object_record_entry(DDTRACE_UNUSED st_data_t *key, st_data_t *
725
854
  return ST_CONTINUE;
726
855
  }
727
856
 
857
+ static void inc_tracked_objects_or_fail(heap_record *heap_record) {
858
+ if (heap_record->num_tracked_objects == UINT32_MAX) {
859
+ raise_error(rb_eRuntimeError, "Reached maximum number of tracked objects for heap record");
860
+ }
861
+ heap_record->num_tracked_objects++;
862
+ }
863
+
728
864
  static void commit_recording(heap_recorder *heap_recorder, heap_record *heap_record, object_record *active_recording) {
729
865
  // Link the object record with the corresponding heap record. This was the last remaining thing we
730
866
  // needed to fully build the object_record.
731
867
  active_recording->heap_record = heap_record;
732
- if (heap_record->num_tracked_objects == UINT32_MAX) {
733
- rb_raise(rb_eRuntimeError, "Reached maximum number of tracked objects for heap record");
734
- }
735
- heap_record->num_tracked_objects++;
736
868
 
737
869
  int existing_error = st_update(heap_recorder->object_records, active_recording->obj_id, update_object_record_entry, (st_data_t) active_recording);
738
870
  if (existing_error) {
739
871
  object_record *existing_record = NULL;
740
872
  st_lookup(heap_recorder->object_records, active_recording->obj_id, (st_data_t *) &existing_record);
741
- if (existing_record == NULL) rb_raise(rb_eRuntimeError, "Unexpected NULL when reading existing record");
873
+ if (existing_record == NULL) raise_error(rb_eRuntimeError, "Unexpected NULL when reading existing record");
742
874
 
743
- VALUE existing_inspect = object_record_inspect(existing_record);
744
- VALUE new_inspect = object_record_inspect(active_recording);
745
- rb_raise(rb_eRuntimeError, "Object ids are supposed to be unique. We got 2 allocation recordings with "
875
+ VALUE existing_inspect = object_record_inspect(heap_recorder, existing_record);
876
+ VALUE new_inspect = object_record_inspect(heap_recorder, active_recording);
877
+ raise_error(rb_eRuntimeError, "Object ids are supposed to be unique. We got 2 allocation recordings with "
746
878
  "the same id. previous={%"PRIsVALUE"} new={%"PRIsVALUE"}", existing_inspect, new_inspect);
747
879
  }
748
880
  }
749
881
 
750
- // Struct holding data required for an update operation on heap_records
751
- typedef struct {
752
- // [in] The locations we did this update with
753
- ddog_prof_Slice_Location locations;
754
- // [out] Pointer that will be updated to the updated heap record to prevent having to do
755
- // another lookup to access the updated heap record.
756
- heap_record **record;
757
- } heap_record_update_data;
758
-
759
- // This function assumes ownership of stack_data is passed on to it so it'll either transfer ownership or clean-up.
760
882
  static int update_heap_record_entry_with_new_allocation(st_data_t *key, st_data_t *value, st_data_t data, int existing) {
761
- heap_record_update_data *update_data = (heap_record_update_data*) data;
883
+ heap_record **new_or_existing_record = (heap_record **) data;
884
+ (*new_or_existing_record) = (heap_record *) (*key);
762
885
 
763
886
  if (!existing) {
764
- // there was no matching heap record so lets create a new one...
765
- // we need to initialize a heap_record_key with a new stack and use that for the key storage. We can't use the
766
- // locations-based key we used for the update call because we don't own its lifecycle. So we create a new
767
- // heap stack and will pass ownership of it to the heap_record.
768
- heap_stack *stack = heap_stack_new(update_data->locations);
769
- (*key) = (st_data_t) heap_record_key_new(stack);
770
- (*value) = (st_data_t) heap_record_new(stack);
887
+ (*value) = (st_data_t) true; // We're only using this hash as a set
771
888
  }
772
889
 
773
- heap_record *record = (heap_record*) (*value);
774
- (*update_data->record) = record;
775
-
776
890
  return ST_CONTINUE;
777
891
  }
778
892
 
779
893
  static heap_record* get_or_create_heap_record(heap_recorder *heap_recorder, ddog_prof_Slice_Location locations) {
780
- // For performance reasons we use a stack-allocated location-slice based key. This allows us
781
- // to do allocation-free lookups and reuse of a matching existing heap record.
782
- // NOTE: If we end up creating a new record, we'll create a heap-allocated key we own and use that for storage
783
- // instead of this one.
784
- heap_record_key lookup_key = (heap_record_key) {
785
- .type = LOCATION_SLICE,
786
- .location_slice = &locations,
787
- };
894
+ // See note on "heap_records" definition for why we keep this map.
895
+ heap_record *stack = heap_record_new(heap_recorder, locations);
788
896
 
789
- heap_record *heap_record = NULL;
790
- heap_record_update_data update_data = (heap_record_update_data) {
791
- .locations = locations,
792
- .record = &heap_record,
793
- };
794
- st_update(heap_recorder->heap_records, (st_data_t) &lookup_key, update_heap_record_entry_with_new_allocation, (st_data_t) &update_data);
897
+ heap_record *new_or_existing_record = NULL; // Will be set inside update_heap_record_entry_with_new_allocation
898
+ bool 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);
899
+ if (existing) {
900
+ heap_record_free(heap_recorder, stack, true);
901
+ }
795
902
 
796
- return heap_record;
903
+ return new_or_existing_record;
797
904
  }
798
905
 
799
906
  static void cleanup_heap_record_if_unused(heap_recorder *heap_recorder, heap_record *heap_record) {
@@ -802,18 +909,10 @@ static void cleanup_heap_record_if_unused(heap_recorder *heap_recorder, heap_rec
802
909
  return;
803
910
  }
804
911
 
805
- heap_record_key heap_key = (heap_record_key) {
806
- .type = HEAP_STACK,
807
- .heap_stack = heap_record->stack,
808
- };
809
- // We need to access the deleted key to free it since we gave ownership of the keys to the hash.
810
- // st_delete will change this pointer to point to the removed key if one is found.
811
- heap_record_key *deleted_key = &heap_key;
812
- if (!st_delete(heap_recorder->heap_records, (st_data_t*) &deleted_key, NULL)) {
813
- rb_raise(rb_eRuntimeError, "Attempted to cleanup an untracked heap_record");
912
+ if (!st_delete(heap_recorder->heap_records, (st_data_t*) &heap_record, NULL)) {
913
+ raise_error(rb_eRuntimeError, "Attempted to cleanup an untracked heap_record");
814
914
  };
815
- heap_record_key_free(deleted_key);
816
- heap_record_free(heap_record);
915
+ heap_record_free(heap_recorder, heap_record, true);
817
916
  }
818
917
 
819
918
  static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, object_record *record) {
@@ -821,67 +920,56 @@ static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, obj
821
920
  // (See PROF-10656 Datadog-internal for details). Just in case, I've sprinkled a bunch of NULL tests in this function for now.
822
921
  // Once we figure out the issue we can get rid of them again.
823
922
 
824
- if (heap_recorder == NULL) rb_raise(rb_eRuntimeError, "heap_recorder was NULL in on_committed_object_record_cleanup");
825
- if (heap_recorder->heap_records == NULL) rb_raise(rb_eRuntimeError, "heap_recorder->heap_records was NULL in on_committed_object_record_cleanup");
826
- if (record == NULL) rb_raise(rb_eRuntimeError, "record was NULL in on_committed_object_record_cleanup");
923
+ if (heap_recorder == NULL) raise_error(rb_eRuntimeError, "heap_recorder was NULL in on_committed_object_record_cleanup");
924
+ if (heap_recorder->heap_records == NULL) raise_error(rb_eRuntimeError, "heap_recorder->heap_records was NULL in on_committed_object_record_cleanup");
925
+ if (record == NULL) raise_error(rb_eRuntimeError, "record was NULL in on_committed_object_record_cleanup");
827
926
 
828
927
  // Starting with the associated heap record. There will now be one less tracked object pointing to it
829
928
  heap_record *heap_record = record->heap_record;
830
929
 
831
- if (heap_record == NULL) rb_raise(rb_eRuntimeError, "heap_record was NULL in on_committed_object_record_cleanup");
832
- if (heap_record->stack == NULL) rb_raise(rb_eRuntimeError, "heap_record->stack was NULL in on_committed_object_record_cleanup");
930
+ if (heap_record == NULL) raise_error(rb_eRuntimeError, "heap_record was NULL in on_committed_object_record_cleanup");
833
931
 
834
932
  heap_record->num_tracked_objects--;
835
933
 
836
934
  // One less object using this heap record, it may have become unused...
837
935
  cleanup_heap_record_if_unused(heap_recorder, heap_record);
838
936
 
839
- object_record_free(record);
840
- }
841
-
842
- // ===============
843
- // Heap Record API
844
- // ===============
845
- heap_record* heap_record_new(heap_stack *stack) {
846
- heap_record *record = ruby_xcalloc(1, sizeof(heap_record));
847
- record->num_tracked_objects = 0;
848
- record->stack = stack;
849
- return record;
850
- }
851
-
852
- void heap_record_free(heap_record *record) {
853
- heap_stack_free(record->stack);
854
- ruby_xfree(record);
937
+ object_record_free(heap_recorder, record, true);
855
938
  }
856
939
 
857
940
  // =================
858
941
  // Object Record API
859
942
  // =================
860
943
  object_record* object_record_new(long obj_id, heap_record *heap_record, live_object_data object_data) {
861
- object_record *record = ruby_xcalloc(1, sizeof(object_record));
944
+ object_record *record = calloc(1, sizeof(object_record)); // See "note on calloc vs ruby_xcalloc use" above
862
945
  record->obj_id = obj_id;
863
946
  record->heap_record = heap_record;
864
947
  record->object_data = object_data;
865
948
  return record;
866
949
  }
867
950
 
868
- void object_record_free(object_record *record) {
869
- if (record->object_data.class != NULL) {
870
- ruby_xfree(record->object_data.class);
871
- }
872
- ruby_xfree(record);
951
+ void object_record_free(heap_recorder *recorder, object_record *record, bool should_unintern) {
952
+ // When tearing down the whole recorder state, we skip uninterning as it's not needed (the managed
953
+ // string table is going to be destroyed anyway) and if there's any failures we can't raise
954
+ // in the middle of a dfree callback.
955
+ if (should_unintern) unintern_or_raise(recorder, record->object_data.class);
956
+
957
+ free(record); // See "note on calloc vs ruby_xcalloc use" above
873
958
  }
874
959
 
875
- VALUE object_record_inspect(object_record *record) {
876
- heap_frame top_frame = record->heap_record->stack->frames[0];
960
+ VALUE object_record_inspect(heap_recorder *recorder, object_record *record) {
961
+ heap_frame top_frame = record->heap_record->frames[0];
962
+ VALUE filename = get_ruby_string_or_raise(recorder, top_frame.filename);
877
963
  live_object_data object_data = record->object_data;
878
- VALUE inspect = rb_sprintf("obj_id=%ld weight=%d size=%zu location=%s:%d alloc_gen=%zu gen_age=%zu frozen=%d ",
879
- record->obj_id, object_data.weight, object_data.size, top_frame.filename,
964
+
965
+ VALUE inspect = rb_sprintf("obj_id=%ld weight=%d size=%zu location=%"PRIsVALUE":%d alloc_gen=%zu gen_age=%zu frozen=%d ",
966
+ record->obj_id, object_data.weight, object_data.size, filename,
880
967
  (int) top_frame.line, object_data.alloc_gen, object_data.gen_age, object_data.is_frozen);
881
968
 
882
- const char *class = record->object_data.class;
883
- if (class != NULL) {
884
- rb_str_catf(inspect, "class=%s ", class);
969
+ if (record->object_data.class.value > 0) {
970
+ VALUE class = get_ruby_string_or_raise(recorder, record->object_data.class);
971
+
972
+ rb_str_catf(inspect, "class=%"PRIsVALUE" ", class);
885
973
  }
886
974
  VALUE ref;
887
975
 
@@ -901,202 +989,119 @@ VALUE object_record_inspect(object_record *record) {
901
989
  }
902
990
 
903
991
  // ==============
904
- // Heap Frame API
905
- // ==============
906
- // WARN: Must be kept in-sync with ::char_slice_hash
907
- st_index_t string_hash(char *str, st_index_t seed) {
908
- return st_hash(str, strlen(str), seed);
909
- }
910
-
911
- // WARN: Must be kept in-sync with ::string_hash
912
- st_index_t char_slice_hash(ddog_CharSlice char_slice, st_index_t seed) {
913
- return st_hash(char_slice.ptr, char_slice.len, seed);
914
- }
915
-
916
- // WARN: Must be kept in-sync with ::ddog_location_hash
917
- st_index_t heap_frame_hash(heap_frame *frame, st_index_t seed) {
918
- st_index_t hash = string_hash(frame->name, seed);
919
- hash = string_hash(frame->filename, hash);
920
- hash = st_hash(&frame->line, sizeof(frame->line), hash);
921
- return hash;
922
- }
923
-
924
- // WARN: Must be kept in-sync with ::heap_frame_hash
925
- st_index_t ddog_location_hash(ddog_prof_Location location, st_index_t seed) {
926
- st_index_t hash = char_slice_hash(location.function.name, seed);
927
- hash = char_slice_hash(location.function.filename, hash);
928
- // Convert ddog_prof line type to the same type we use for our heap_frames to
929
- // ensure we have compatible hashes
930
- int32_t line_as_int32 = (int32_t) location.line;
931
- hash = st_hash(&line_as_int32, sizeof(line_as_int32), hash);
932
- return hash;
933
- }
934
-
935
- // ==============
936
- // Heap Stack API
992
+ // Heap Record API
937
993
  // ==============
938
- heap_stack* heap_stack_new(ddog_prof_Slice_Location locations) {
994
+ heap_record* heap_record_new(heap_recorder *recorder, ddog_prof_Slice_Location locations) {
939
995
  uint16_t frames_len = locations.len;
940
996
  if (frames_len > MAX_FRAMES_LIMIT) {
941
997
  // This is not expected as MAX_FRAMES_LIMIT is shared with the stacktrace construction mechanism
942
- rb_raise(rb_eRuntimeError, "Found stack with more than %d frames (%d)", MAX_FRAMES_LIMIT, frames_len);
998
+ raise_error(rb_eRuntimeError, "Found stack with more than %d frames (%d)", MAX_FRAMES_LIMIT, frames_len);
943
999
  }
944
- heap_stack *stack = ruby_xcalloc(1, sizeof(heap_stack) + frames_len * sizeof(heap_frame));
1000
+ heap_record *stack = calloc(1, sizeof(heap_record) + frames_len * sizeof(heap_frame)); // See "note on calloc vs ruby_xcalloc use" above
1001
+ stack->num_tracked_objects = 0;
945
1002
  stack->frames_len = frames_len;
1003
+
1004
+ // Intern all these strings...
1005
+ ddog_CharSlice *strings = recorder->reusable_char_slices;
1006
+ // Put all the char slices in the same array; we'll pull them out in the same order from the ids array
946
1007
  for (uint16_t i = 0; i < stack->frames_len; i++) {
947
1008
  const ddog_prof_Location *location = &locations.ptr[i];
1009
+ strings[i] = location->function.filename;
1010
+ strings[i + stack->frames_len] = location->function.name;
1011
+ }
1012
+ 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);
1013
+
1014
+ // ...and record them for later use
1015
+ for (uint16_t i = 0; i < stack->frames_len; i++) {
948
1016
  stack->frames[i] = (heap_frame) {
949
- .name = string_from_char_slice(location->function.name),
950
- .filename = string_from_char_slice(location->function.filename),
1017
+ .filename = recorder->reusable_ids[i],
1018
+ .name = recorder->reusable_ids[i + stack->frames_len],
951
1019
  // ddog_prof_Location is a int64_t. We don't expect to have to profile files with more than
952
1020
  // 2M lines so this cast should be fairly safe?
953
- .line = (int32_t) location->line,
1021
+ .line = (int32_t) locations.ptr[i].line,
954
1022
  };
955
1023
  }
956
- return stack;
957
- }
958
1024
 
959
- void heap_stack_free(heap_stack *stack) {
960
- for (uint64_t i = 0; i < stack->frames_len; i++) {
961
- heap_frame *frame = &stack->frames[i];
962
- ruby_xfree(frame->name);
963
- ruby_xfree(frame->filename);
964
- }
965
- ruby_xfree(stack);
966
- }
967
-
968
- // WARN: Must be kept in-sync with ::ddog_location_slice_hash
969
- st_index_t heap_stack_hash(heap_stack *stack, st_index_t seed) {
970
- st_index_t hash = seed;
971
- for (uint64_t i = 0; i < stack->frames_len; i++) {
972
- hash = heap_frame_hash(&stack->frames[i], hash);
973
- }
974
- return hash;
1025
+ return stack;
975
1026
  }
976
1027
 
977
- // WARN: Must be kept in-sync with ::heap_stack_hash
978
- st_index_t ddog_location_slice_hash(ddog_prof_Slice_Location locations, st_index_t seed) {
979
- st_index_t hash = seed;
980
- for (uint64_t i = 0; i < locations.len; i++) {
981
- hash = ddog_location_hash(locations.ptr[i], hash);
1028
+ void heap_record_free(heap_recorder *recorder, heap_record *stack, bool should_unintern) {
1029
+ // When tearing down the whole recorder state, we skip uninterning as it's not needed (the managed
1030
+ // string table is going to be destroyed anyway) and if there's any failures we can't raise
1031
+ // in the middle of a dfree callback.
1032
+ if (should_unintern) {
1033
+ ddog_prof_ManagedStringId *ids = recorder->reusable_ids;
1034
+
1035
+ // Put all the ids in the same array; doesn't really matter the order
1036
+ for (u_int16_t i = 0; i < stack->frames_len; i++) {
1037
+ ids[i] = stack->frames[i].filename;
1038
+ ids[i + stack->frames_len] = stack->frames[i].name;
1039
+ }
1040
+ unintern_all_or_raise(recorder, (ddog_prof_Slice_ManagedStringId) { .ptr = ids, .len = stack->frames_len * 2 });
982
1041
  }
983
- return hash;
984
- }
985
1042
 
986
- // ===================
987
- // Heap Record Key API
988
- // ===================
989
- heap_record_key* heap_record_key_new(heap_stack *stack) {
990
- heap_record_key *key = ruby_xmalloc(sizeof(heap_record_key));
991
- key->type = HEAP_STACK;
992
- key->heap_stack = stack;
993
- return key;
1043
+ free(stack); // See "note on calloc vs ruby_xcalloc use" above
994
1044
  }
995
1045
 
996
- void heap_record_key_free(heap_record_key *key) {
997
- ruby_xfree(key);
998
- }
1046
+ // The entire stack is represented by ids (name, filename) and lines (integers) so we can treat is as just
1047
+ // a big string of bytes and compare it all in one go.
1048
+ int heap_record_cmp_st(st_data_t key1, st_data_t key2) {
1049
+ heap_record *stack1 = (heap_record*) key1;
1050
+ heap_record *stack2 = (heap_record*) key2;
999
1051
 
1000
- static inline size_t heap_record_key_len(heap_record_key *key) {
1001
- if (key->type == HEAP_STACK) {
1002
- return key->heap_stack->frames_len;
1052
+ if (stack1->frames_len != stack2->frames_len) {
1053
+ return ((int) stack1->frames_len) - ((int) stack2->frames_len);
1003
1054
  } else {
1004
- return key->location_slice->len;
1055
+ return memcmp(stack1->frames, stack2->frames, stack1->frames_len * sizeof(heap_frame));
1005
1056
  }
1006
1057
  }
1007
1058
 
1008
- static inline int64_t heap_record_key_entry_line(heap_record_key *key, size_t entry_i) {
1009
- if (key->type == HEAP_STACK) {
1010
- return key->heap_stack->frames[entry_i].line;
1011
- } else {
1012
- return key->location_slice->ptr[entry_i].line;
1013
- }
1014
- }
1059
+ // Initial seed for hash function, same as Ruby uses
1060
+ #define FNV1_32A_INIT 0x811c9dc5
1015
1061
 
1016
- static inline size_t heap_record_key_entry_name(heap_record_key *key, size_t entry_i, const char **name_ptr) {
1017
- if (key->type == HEAP_STACK) {
1018
- char *name = key->heap_stack->frames[entry_i].name;
1019
- (*name_ptr) = name;
1020
- return strlen(name);
1021
- } else {
1022
- ddog_CharSlice name = key->location_slice->ptr[entry_i].function.name;
1023
- (*name_ptr) = name.ptr;
1024
- return name.len;
1025
- }
1062
+ // The entire stack is represented by ids (name, filename) and lines (integers) so we can treat is as just
1063
+ // a big string of bytes and hash it all in one go.
1064
+ st_index_t heap_record_hash_st(st_data_t key) {
1065
+ heap_record *stack = (heap_record*) key;
1066
+ return st_hash(stack->frames, stack->frames_len * sizeof(heap_frame), FNV1_32A_INIT);
1026
1067
  }
1027
1068
 
1028
- static inline size_t heap_record_key_entry_filename(heap_record_key *key, size_t entry_i, const char **filename_ptr) {
1029
- if (key->type == HEAP_STACK) {
1030
- char *filename = key->heap_stack->frames[entry_i].filename;
1031
- (*filename_ptr) = filename;
1032
- return strlen(filename);
1033
- } else {
1034
- ddog_CharSlice filename = key->location_slice->ptr[entry_i].function.filename;
1035
- (*filename_ptr) = filename.ptr;
1036
- return filename.len;
1069
+ static void unintern_or_raise(heap_recorder *recorder, ddog_prof_ManagedStringId id) {
1070
+ if (id.value == 0) return; // Empty string, nothing to do
1071
+
1072
+ ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern(recorder->string_storage, id);
1073
+ if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
1074
+ raise_error(rb_eRuntimeError, "Failed to unintern id: %"PRIsVALUE, get_error_details_and_drop(&result.some));
1037
1075
  }
1038
1076
  }
1039
1077
 
1040
- int heap_record_key_cmp_st(st_data_t key1, st_data_t key2) {
1041
- heap_record_key *key_record1 = (heap_record_key*) key1;
1042
- heap_record_key *key_record2 = (heap_record_key*) key2;
1043
-
1044
- // Fast path, check if lengths differ
1045
- size_t key_record1_len = heap_record_key_len(key_record1);
1046
- size_t key_record2_len = heap_record_key_len(key_record2);
1047
-
1048
- if (key_record1_len != key_record2_len) {
1049
- return ((int) key_record1_len) - ((int) key_record2_len);
1078
+ static void unintern_all_or_raise(heap_recorder *recorder, ddog_prof_Slice_ManagedStringId ids) {
1079
+ ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern_all(recorder->string_storage, ids);
1080
+ if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
1081
+ raise_error(rb_eRuntimeError, "Failed to unintern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some));
1050
1082
  }
1083
+ }
1051
1084
 
1052
- // If we got this far, we have same lengths so need to check item-by-item
1053
- for (size_t i = 0; i < key_record1_len; i++) {
1054
- // Lines are faster to compare, lets do that first
1055
- size_t line1 = heap_record_key_entry_line(key_record1, i);
1056
- size_t line2 = heap_record_key_entry_line(key_record2, i);
1057
- if (line1 != line2) {
1058
- return ((int) line1) - ((int)line2);
1059
- }
1060
-
1061
- // Then come names, they are usually smaller than filenames
1062
- const char *name1, *name2;
1063
- size_t name1_len = heap_record_key_entry_name(key_record1, i, &name1);
1064
- size_t name2_len = heap_record_key_entry_name(key_record2, i, &name2);
1065
- if (name1_len != name2_len) {
1066
- return ((int) name1_len) - ((int) name2_len);
1067
- }
1068
- int name_cmp_result = strncmp(name1, name2, name1_len);
1069
- if (name_cmp_result != 0) {
1070
- return name_cmp_result;
1071
- }
1072
-
1073
- // Then come filenames
1074
- const char *filename1, *filename2;
1075
- int64_t filename1_len = heap_record_key_entry_filename(key_record1, i, &filename1);
1076
- int64_t filename2_len = heap_record_key_entry_filename(key_record2, i, &filename2);
1077
- if (filename1_len != filename2_len) {
1078
- return ((int) filename1_len) - ((int) filename2_len);
1079
- }
1080
- int filename_cmp_result = strncmp(filename1, filename2, filename1_len);
1081
- if (filename_cmp_result != 0) {
1082
- return filename_cmp_result;
1083
- }
1085
+ static VALUE get_ruby_string_or_raise(heap_recorder *recorder, ddog_prof_ManagedStringId id) {
1086
+ ddog_StringWrapperResult get_string_result = ddog_prof_ManagedStringStorage_get_string(recorder->string_storage, id);
1087
+ if (get_string_result.tag == DDOG_STRING_WRAPPER_RESULT_ERR) {
1088
+ raise_error(rb_eRuntimeError, "Failed to get string: %"PRIsVALUE, get_error_details_and_drop(&get_string_result.err));
1084
1089
  }
1090
+ VALUE ruby_string = ruby_string_from_vec_u8(get_string_result.ok.message);
1091
+ ddog_StringWrapper_drop((ddog_StringWrapper *) &get_string_result.ok);
1085
1092
 
1086
- // If we survived the above for, then everything matched
1087
- return 0;
1093
+ return ruby_string;
1088
1094
  }
1089
1095
 
1090
- // Initial seed for hash functions
1091
- #define FNV1_32A_INIT 0x811c9dc5
1092
-
1093
- st_index_t heap_record_key_hash_st(st_data_t key) {
1094
- heap_record_key *record_key = (heap_record_key*) key;
1095
- if (record_key->type == HEAP_STACK) {
1096
- return heap_stack_hash(record_key->heap_stack, FNV1_32A_INIT);
1097
- } else {
1098
- return ddog_location_slice_hash(*record_key->location_slice, FNV1_32A_INIT);
1096
+ static long obj_id_or_fail(VALUE obj) {
1097
+ VALUE ruby_obj_id = rb_obj_id(obj);
1098
+ if (!FIXNUM_P(ruby_obj_id)) {
1099
+ // Bignum object ids indicate the fixnum range is exhausted - all future IDs will also be bignums.
1100
+ // Heap profiling cannot continue.
1101
+ raise_error(rb_eRuntimeError, "Heap profiling: bignum object id detected. Heap profiling cannot continue.");
1099
1102
  }
1103
+
1104
+ return FIX2LONG(ruby_obj_id);
1100
1105
  }
1101
1106
 
1102
1107
  static inline double ewma_stat(double previous, double current) {
@@ -1106,7 +1111,7 @@ static inline double ewma_stat(double previous, double current) {
1106
1111
 
1107
1112
  VALUE heap_recorder_testonly_is_object_recorded(heap_recorder *heap_recorder, VALUE obj_id) {
1108
1113
  if (heap_recorder == NULL) {
1109
- rb_raise(rb_eArgError, "heap_recorder is NULL");
1114
+ raise_error(rb_eArgError, "heap_recorder is NULL");
1110
1115
  }
1111
1116
 
1112
1117
  // Check if object records contains an object with this object_id
@@ -1115,8 +1120,28 @@ VALUE heap_recorder_testonly_is_object_recorded(heap_recorder *heap_recorder, VA
1115
1120
 
1116
1121
  void heap_recorder_testonly_reset_last_update(heap_recorder *heap_recorder) {
1117
1122
  if (heap_recorder == NULL) {
1118
- rb_raise(rb_eArgError, "heap_recorder is NULL");
1123
+ raise_error(rb_eArgError, "heap_recorder is NULL");
1119
1124
  }
1120
1125
 
1121
1126
  heap_recorder->last_update_ns = 0;
1122
1127
  }
1128
+
1129
+ void heap_recorder_testonly_benchmark_intern(heap_recorder *heap_recorder, ddog_CharSlice string, int times, bool use_all) {
1130
+ if (heap_recorder == NULL) raise_error(rb_eArgError, "heap profiling must be enabled");
1131
+ if (times > REUSABLE_FRAME_DETAILS_SIZE) raise_error(rb_eArgError, "times cannot be > than REUSABLE_FRAME_DETAILS_SIZE");
1132
+
1133
+ if (use_all) {
1134
+ ddog_CharSlice *strings = heap_recorder->reusable_char_slices;
1135
+
1136
+ for (int i = 0; i < times; i++) strings[i] = string;
1137
+
1138
+ intern_all_or_raise(
1139
+ heap_recorder->string_storage,
1140
+ (ddog_prof_Slice_CharSlice) { .ptr = strings, .len = times },
1141
+ heap_recorder->reusable_ids,
1142
+ times
1143
+ );
1144
+ } else {
1145
+ for (int i = 0; i < times; i++) intern_or_raise(heap_recorder->string_storage, string);
1146
+ }
1147
+ }