cw-datadog 2.23.0.2

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 (944) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5142 -0
  3. data/LICENSE +6 -0
  4. data/LICENSE-3rdparty.csv +7 -0
  5. data/LICENSE.Apache +200 -0
  6. data/LICENSE.BSD3 +24 -0
  7. data/NOTICE +4 -0
  8. data/README.md +24 -0
  9. data/bin/ddprofrb +15 -0
  10. data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
  11. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +156 -0
  12. data/ext/datadog_profiling_native_extension/clock_id.h +23 -0
  13. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +55 -0
  14. data/ext/datadog_profiling_native_extension/clock_id_noop.c +21 -0
  15. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +1423 -0
  16. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +447 -0
  17. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +131 -0
  18. data/ext/datadog_profiling_native_extension/collectors_dynamic_sampling_rate.c +150 -0
  19. data/ext/datadog_profiling_native_extension/collectors_dynamic_sampling_rate.h +18 -0
  20. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +156 -0
  21. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.h +5 -0
  22. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +248 -0
  23. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +3 -0
  24. data/ext/datadog_profiling_native_extension/collectors_stack.c +659 -0
  25. data/ext/datadog_profiling_native_extension/collectors_stack.h +44 -0
  26. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +2221 -0
  27. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +31 -0
  28. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +80 -0
  29. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +63 -0
  30. data/ext/datadog_profiling_native_extension/encoded_profile.c +79 -0
  31. data/ext/datadog_profiling_native_extension/encoded_profile.h +8 -0
  32. data/ext/datadog_profiling_native_extension/extconf.rb +321 -0
  33. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +52 -0
  34. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +67 -0
  35. data/ext/datadog_profiling_native_extension/heap_recorder.c +998 -0
  36. data/ext/datadog_profiling_native_extension/heap_recorder.h +177 -0
  37. data/ext/datadog_profiling_native_extension/helpers.h +12 -0
  38. data/ext/datadog_profiling_native_extension/http_transport.c +280 -0
  39. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +84 -0
  40. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +28 -0
  41. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +244 -0
  42. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +881 -0
  43. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +81 -0
  44. data/ext/datadog_profiling_native_extension/profiling.c +284 -0
  45. data/ext/datadog_profiling_native_extension/ruby_helpers.c +235 -0
  46. data/ext/datadog_profiling_native_extension/ruby_helpers.h +88 -0
  47. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +115 -0
  48. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +12 -0
  49. data/ext/datadog_profiling_native_extension/stack_recorder.c +1145 -0
  50. data/ext/datadog_profiling_native_extension/stack_recorder.h +31 -0
  51. data/ext/datadog_profiling_native_extension/time_helpers.c +38 -0
  52. data/ext/datadog_profiling_native_extension/time_helpers.h +56 -0
  53. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +47 -0
  54. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +31 -0
  55. data/ext/libdatadog_api/crashtracker.c +125 -0
  56. data/ext/libdatadog_api/crashtracker.h +5 -0
  57. data/ext/libdatadog_api/datadog_ruby_common.c +80 -0
  58. data/ext/libdatadog_api/datadog_ruby_common.h +63 -0
  59. data/ext/libdatadog_api/ddsketch.c +106 -0
  60. data/ext/libdatadog_api/extconf.rb +110 -0
  61. data/ext/libdatadog_api/init.c +18 -0
  62. data/ext/libdatadog_api/library_config.c +172 -0
  63. data/ext/libdatadog_api/library_config.h +25 -0
  64. data/ext/libdatadog_api/process_discovery.c +118 -0
  65. data/ext/libdatadog_api/process_discovery.h +5 -0
  66. data/ext/libdatadog_extconf_helpers.rb +140 -0
  67. data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +89 -0
  68. data/lib/datadog/appsec/actions_handler.rb +49 -0
  69. data/lib/datadog/appsec/anonymizer.rb +16 -0
  70. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
  71. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +59 -0
  72. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +29 -0
  73. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
  74. data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
  75. data/lib/datadog/appsec/api_security/route_extractor.rb +77 -0
  76. data/lib/datadog/appsec/api_security/sampler.rb +60 -0
  77. data/lib/datadog/appsec/api_security.rb +23 -0
  78. data/lib/datadog/appsec/assets/blocked.html +99 -0
  79. data/lib/datadog/appsec/assets/blocked.json +1 -0
  80. data/lib/datadog/appsec/assets/blocked.text +5 -0
  81. data/lib/datadog/appsec/assets/waf_rules/README.md +46 -0
  82. data/lib/datadog/appsec/assets/waf_rules/recommended.json +10504 -0
  83. data/lib/datadog/appsec/assets/waf_rules/strict.json +3066 -0
  84. data/lib/datadog/appsec/assets.rb +46 -0
  85. data/lib/datadog/appsec/autoload.rb +13 -0
  86. data/lib/datadog/appsec/component.rb +89 -0
  87. data/lib/datadog/appsec/compressed_json.rb +40 -0
  88. data/lib/datadog/appsec/configuration/settings.rb +409 -0
  89. data/lib/datadog/appsec/configuration.rb +11 -0
  90. data/lib/datadog/appsec/context.rb +97 -0
  91. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +94 -0
  92. data/lib/datadog/appsec/contrib/active_record/integration.rb +41 -0
  93. data/lib/datadog/appsec/contrib/active_record/patcher.rb +101 -0
  94. data/lib/datadog/appsec/contrib/auto_instrument.rb +25 -0
  95. data/lib/datadog/appsec/contrib/devise/configuration.rb +52 -0
  96. data/lib/datadog/appsec/contrib/devise/data_extractor.rb +78 -0
  97. data/lib/datadog/appsec/contrib/devise/ext.rb +35 -0
  98. data/lib/datadog/appsec/contrib/devise/integration.rb +41 -0
  99. data/lib/datadog/appsec/contrib/devise/patcher.rb +63 -0
  100. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +103 -0
  101. data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +70 -0
  102. data/lib/datadog/appsec/contrib/devise/patches/skip_signin_tracking_patch.rb +21 -0
  103. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +106 -0
  104. data/lib/datadog/appsec/contrib/excon/integration.rb +41 -0
  105. data/lib/datadog/appsec/contrib/excon/patcher.rb +28 -0
  106. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +42 -0
  107. data/lib/datadog/appsec/contrib/faraday/connection_patch.rb +22 -0
  108. data/lib/datadog/appsec/contrib/faraday/integration.rb +42 -0
  109. data/lib/datadog/appsec/contrib/faraday/patcher.rb +53 -0
  110. data/lib/datadog/appsec/contrib/faraday/rack_builder_patch.rb +22 -0
  111. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +42 -0
  112. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +29 -0
  113. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +109 -0
  114. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +56 -0
  115. data/lib/datadog/appsec/contrib/graphql/integration.rb +54 -0
  116. data/lib/datadog/appsec/contrib/graphql/patcher.rb +34 -0
  117. data/lib/datadog/appsec/contrib/integration.rb +37 -0
  118. data/lib/datadog/appsec/contrib/rack/ext.rb +47 -0
  119. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +101 -0
  120. data/lib/datadog/appsec/contrib/rack/gateway/response.rb +30 -0
  121. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +141 -0
  122. data/lib/datadog/appsec/contrib/rack/integration.rb +44 -0
  123. data/lib/datadog/appsec/contrib/rack/patcher.rb +31 -0
  124. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +43 -0
  125. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +218 -0
  126. data/lib/datadog/appsec/contrib/rails/ext.rb +13 -0
  127. data/lib/datadog/appsec/contrib/rails/framework.rb +16 -0
  128. data/lib/datadog/appsec/contrib/rails/gateway/request.rb +67 -0
  129. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +78 -0
  130. data/lib/datadog/appsec/contrib/rails/integration.rb +43 -0
  131. data/lib/datadog/appsec/contrib/rails/patcher.rb +171 -0
  132. data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +27 -0
  133. data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +33 -0
  134. data/lib/datadog/appsec/contrib/rails/request.rb +36 -0
  135. data/lib/datadog/appsec/contrib/rails/request_middleware.rb +20 -0
  136. data/lib/datadog/appsec/contrib/rest_client/integration.rb +45 -0
  137. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +28 -0
  138. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +39 -0
  139. data/lib/datadog/appsec/contrib/sinatra/framework.rb +20 -0
  140. data/lib/datadog/appsec/contrib/sinatra/gateway/request.rb +17 -0
  141. data/lib/datadog/appsec/contrib/sinatra/gateway/route_params.rb +23 -0
  142. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +105 -0
  143. data/lib/datadog/appsec/contrib/sinatra/integration.rb +43 -0
  144. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +132 -0
  145. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +31 -0
  146. data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +20 -0
  147. data/lib/datadog/appsec/event.rb +139 -0
  148. data/lib/datadog/appsec/ext.rb +23 -0
  149. data/lib/datadog/appsec/extensions.rb +16 -0
  150. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +43 -0
  151. data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +24 -0
  152. data/lib/datadog/appsec/instrumentation/gateway.rb +59 -0
  153. data/lib/datadog/appsec/instrumentation.rb +9 -0
  154. data/lib/datadog/appsec/metrics/collector.rb +58 -0
  155. data/lib/datadog/appsec/metrics/exporter.rb +35 -0
  156. data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
  157. data/lib/datadog/appsec/metrics/telemetry_exporter.rb +29 -0
  158. data/lib/datadog/appsec/metrics.rb +14 -0
  159. data/lib/datadog/appsec/monitor/gateway/watcher.rb +85 -0
  160. data/lib/datadog/appsec/monitor.rb +11 -0
  161. data/lib/datadog/appsec/processor/rule_loader.rb +119 -0
  162. data/lib/datadog/appsec/rate_limiter.rb +45 -0
  163. data/lib/datadog/appsec/remote.rb +119 -0
  164. data/lib/datadog/appsec/response.rb +99 -0
  165. data/lib/datadog/appsec/sample_rate.rb +21 -0
  166. data/lib/datadog/appsec/security_engine/engine.rb +176 -0
  167. data/lib/datadog/appsec/security_engine/result.rb +102 -0
  168. data/lib/datadog/appsec/security_engine/runner.rb +111 -0
  169. data/lib/datadog/appsec/security_engine.rb +9 -0
  170. data/lib/datadog/appsec/security_event.rb +37 -0
  171. data/lib/datadog/appsec/thread_safe_ref.rb +61 -0
  172. data/lib/datadog/appsec/trace_keeper.rb +24 -0
  173. data/lib/datadog/appsec/utils/hash_coercion.rb +23 -0
  174. data/lib/datadog/appsec/utils/http/media_range.rb +201 -0
  175. data/lib/datadog/appsec/utils/http/media_type.rb +87 -0
  176. data/lib/datadog/appsec/utils/http.rb +11 -0
  177. data/lib/datadog/appsec/utils.rb +9 -0
  178. data/lib/datadog/appsec.rb +65 -0
  179. data/lib/datadog/auto_instrument.rb +19 -0
  180. data/lib/datadog/auto_instrument_base.rb +9 -0
  181. data/lib/datadog/core/buffer/cruby.rb +55 -0
  182. data/lib/datadog/core/buffer/random.rb +150 -0
  183. data/lib/datadog/core/buffer/thread_safe.rb +58 -0
  184. data/lib/datadog/core/chunker.rb +35 -0
  185. data/lib/datadog/core/cloudwise/IMPLEMENTATION_V2.md +517 -0
  186. data/lib/datadog/core/cloudwise/QUICKSTART.md +398 -0
  187. data/lib/datadog/core/cloudwise/README.md +722 -0
  188. data/lib/datadog/core/cloudwise/app_registration_worker.rb +90 -0
  189. data/lib/datadog/core/cloudwise/client.rb +490 -0
  190. data/lib/datadog/core/cloudwise/component.rb +351 -0
  191. data/lib/datadog/core/cloudwise/heartbeat_worker.rb +137 -0
  192. data/lib/datadog/core/cloudwise/host_id_worker.rb +85 -0
  193. data/lib/datadog/core/cloudwise/license_worker.rb +108 -0
  194. data/lib/datadog/core/cloudwise/probe_state.rb +160 -0
  195. data/lib/datadog/core/configuration/agent_settings.rb +52 -0
  196. data/lib/datadog/core/configuration/agent_settings_resolver.rb +339 -0
  197. data/lib/datadog/core/configuration/agentless_settings_resolver.rb +176 -0
  198. data/lib/datadog/core/configuration/base.rb +91 -0
  199. data/lib/datadog/core/configuration/components.rb +386 -0
  200. data/lib/datadog/core/configuration/components_state.rb +23 -0
  201. data/lib/datadog/core/configuration/config_helper.rb +100 -0
  202. data/lib/datadog/core/configuration/deprecations.rb +36 -0
  203. data/lib/datadog/core/configuration/ext.rb +49 -0
  204. data/lib/datadog/core/configuration/option.rb +368 -0
  205. data/lib/datadog/core/configuration/option_definition.rb +158 -0
  206. data/lib/datadog/core/configuration/options.rb +134 -0
  207. data/lib/datadog/core/configuration/settings.rb +1087 -0
  208. data/lib/datadog/core/configuration/stable_config.rb +32 -0
  209. data/lib/datadog/core/configuration/supported_configurations.rb +347 -0
  210. data/lib/datadog/core/configuration.rb +328 -0
  211. data/lib/datadog/core/contrib/rails/utils.rb +24 -0
  212. data/lib/datadog/core/crashtracking/component.rb +105 -0
  213. data/lib/datadog/core/crashtracking/tag_builder.rb +21 -0
  214. data/lib/datadog/core/ddsketch.rb +19 -0
  215. data/lib/datadog/core/deprecations.rb +58 -0
  216. data/lib/datadog/core/diagnostics/environment_logger.rb +170 -0
  217. data/lib/datadog/core/diagnostics/health.rb +19 -0
  218. data/lib/datadog/core/encoding.rb +90 -0
  219. data/lib/datadog/core/environment/agent_info.rb +78 -0
  220. data/lib/datadog/core/environment/cgroup.rb +51 -0
  221. data/lib/datadog/core/environment/class_count.rb +21 -0
  222. data/lib/datadog/core/environment/container.rb +89 -0
  223. data/lib/datadog/core/environment/execution.rb +103 -0
  224. data/lib/datadog/core/environment/ext.rb +45 -0
  225. data/lib/datadog/core/environment/gc.rb +20 -0
  226. data/lib/datadog/core/environment/git.rb +26 -0
  227. data/lib/datadog/core/environment/identity.rb +84 -0
  228. data/lib/datadog/core/environment/platform.rb +46 -0
  229. data/lib/datadog/core/environment/socket.rb +24 -0
  230. data/lib/datadog/core/environment/thread_count.rb +20 -0
  231. data/lib/datadog/core/environment/variable_helpers.rb +53 -0
  232. data/lib/datadog/core/environment/vm_cache.rb +64 -0
  233. data/lib/datadog/core/environment/yjit.rb +69 -0
  234. data/lib/datadog/core/error.rb +102 -0
  235. data/lib/datadog/core/extensions.rb +16 -0
  236. data/lib/datadog/core/git/ext.rb +16 -0
  237. data/lib/datadog/core/header_collection.rb +43 -0
  238. data/lib/datadog/core/logger.rb +45 -0
  239. data/lib/datadog/core/logging/ext.rb +13 -0
  240. data/lib/datadog/core/metrics/client.rb +206 -0
  241. data/lib/datadog/core/metrics/ext.rb +18 -0
  242. data/lib/datadog/core/metrics/helpers.rb +25 -0
  243. data/lib/datadog/core/metrics/logging.rb +44 -0
  244. data/lib/datadog/core/metrics/metric.rb +14 -0
  245. data/lib/datadog/core/metrics/options.rb +52 -0
  246. data/lib/datadog/core/pin.rb +71 -0
  247. data/lib/datadog/core/process_discovery/tracer_memfd.rb +13 -0
  248. data/lib/datadog/core/process_discovery.rb +61 -0
  249. data/lib/datadog/core/rate_limiter.rb +185 -0
  250. data/lib/datadog/core/remote/client/capabilities.rb +70 -0
  251. data/lib/datadog/core/remote/client.rb +245 -0
  252. data/lib/datadog/core/remote/component.rb +161 -0
  253. data/lib/datadog/core/remote/configuration/content.rb +111 -0
  254. data/lib/datadog/core/remote/configuration/digest.rb +62 -0
  255. data/lib/datadog/core/remote/configuration/path.rb +90 -0
  256. data/lib/datadog/core/remote/configuration/repository.rb +307 -0
  257. data/lib/datadog/core/remote/configuration/target.rb +74 -0
  258. data/lib/datadog/core/remote/configuration.rb +18 -0
  259. data/lib/datadog/core/remote/dispatcher.rb +59 -0
  260. data/lib/datadog/core/remote/ext.rb +13 -0
  261. data/lib/datadog/core/remote/negotiation.rb +70 -0
  262. data/lib/datadog/core/remote/tie/tracing.rb +39 -0
  263. data/lib/datadog/core/remote/tie.rb +29 -0
  264. data/lib/datadog/core/remote/transport/config.rb +61 -0
  265. data/lib/datadog/core/remote/transport/http/api.rb +53 -0
  266. data/lib/datadog/core/remote/transport/http/client.rb +49 -0
  267. data/lib/datadog/core/remote/transport/http/config.rb +252 -0
  268. data/lib/datadog/core/remote/transport/http/negotiation.rb +103 -0
  269. data/lib/datadog/core/remote/transport/http.rb +83 -0
  270. data/lib/datadog/core/remote/transport/negotiation.rb +75 -0
  271. data/lib/datadog/core/remote/worker.rb +105 -0
  272. data/lib/datadog/core/remote.rb +24 -0
  273. data/lib/datadog/core/runtime/ext.rb +40 -0
  274. data/lib/datadog/core/runtime/metrics.rb +202 -0
  275. data/lib/datadog/core/semaphore.rb +35 -0
  276. data/lib/datadog/core/tag_builder.rb +52 -0
  277. data/lib/datadog/core/telemetry/component.rb +206 -0
  278. data/lib/datadog/core/telemetry/emitter.rb +56 -0
  279. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +66 -0
  280. data/lib/datadog/core/telemetry/event/app_closing.rb +18 -0
  281. data/lib/datadog/core/telemetry/event/app_dependencies_loaded.rb +33 -0
  282. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
  283. data/lib/datadog/core/telemetry/event/app_heartbeat.rb +18 -0
  284. data/lib/datadog/core/telemetry/event/app_integrations_change.rb +58 -0
  285. data/lib/datadog/core/telemetry/event/app_started.rb +287 -0
  286. data/lib/datadog/core/telemetry/event/base.rb +40 -0
  287. data/lib/datadog/core/telemetry/event/distributions.rb +18 -0
  288. data/lib/datadog/core/telemetry/event/generate_metrics.rb +43 -0
  289. data/lib/datadog/core/telemetry/event/log.rb +76 -0
  290. data/lib/datadog/core/telemetry/event/message_batch.rb +42 -0
  291. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +43 -0
  292. data/lib/datadog/core/telemetry/event.rb +37 -0
  293. data/lib/datadog/core/telemetry/ext.rb +20 -0
  294. data/lib/datadog/core/telemetry/http/adapters/net.rb +26 -0
  295. data/lib/datadog/core/telemetry/logger.rb +52 -0
  296. data/lib/datadog/core/telemetry/logging.rb +71 -0
  297. data/lib/datadog/core/telemetry/metric.rb +189 -0
  298. data/lib/datadog/core/telemetry/metrics_collection.rb +81 -0
  299. data/lib/datadog/core/telemetry/metrics_manager.rb +81 -0
  300. data/lib/datadog/core/telemetry/request.rb +71 -0
  301. data/lib/datadog/core/telemetry/transport/http/api.rb +43 -0
  302. data/lib/datadog/core/telemetry/transport/http/client.rb +49 -0
  303. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +92 -0
  304. data/lib/datadog/core/telemetry/transport/http.rb +63 -0
  305. data/lib/datadog/core/telemetry/transport/telemetry.rb +51 -0
  306. data/lib/datadog/core/telemetry/worker.rb +276 -0
  307. data/lib/datadog/core/transport/ext.rb +44 -0
  308. data/lib/datadog/core/transport/http/adapters/net.rb +175 -0
  309. data/lib/datadog/core/transport/http/adapters/registry.rb +29 -0
  310. data/lib/datadog/core/transport/http/adapters/test.rb +90 -0
  311. data/lib/datadog/core/transport/http/adapters/unix_socket.rb +83 -0
  312. data/lib/datadog/core/transport/http/api/endpoint.rb +31 -0
  313. data/lib/datadog/core/transport/http/api/fallbacks.rb +26 -0
  314. data/lib/datadog/core/transport/http/api/instance.rb +54 -0
  315. data/lib/datadog/core/transport/http/api/map.rb +18 -0
  316. data/lib/datadog/core/transport/http/api/spec.rb +36 -0
  317. data/lib/datadog/core/transport/http/builder.rb +184 -0
  318. data/lib/datadog/core/transport/http/env.rb +70 -0
  319. data/lib/datadog/core/transport/http/response.rb +60 -0
  320. data/lib/datadog/core/transport/http.rb +75 -0
  321. data/lib/datadog/core/transport/parcel.rb +22 -0
  322. data/lib/datadog/core/transport/request.rb +17 -0
  323. data/lib/datadog/core/transport/response.rb +71 -0
  324. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
  325. data/lib/datadog/core/utils/base64.rb +22 -0
  326. data/lib/datadog/core/utils/duration.rb +52 -0
  327. data/lib/datadog/core/utils/forking.rb +63 -0
  328. data/lib/datadog/core/utils/hash.rb +79 -0
  329. data/lib/datadog/core/utils/lru_cache.rb +45 -0
  330. data/lib/datadog/core/utils/network.rb +142 -0
  331. data/lib/datadog/core/utils/only_once.rb +42 -0
  332. data/lib/datadog/core/utils/only_once_successful.rb +87 -0
  333. data/lib/datadog/core/utils/safe_dup.rb +40 -0
  334. data/lib/datadog/core/utils/sequence.rb +26 -0
  335. data/lib/datadog/core/utils/time.rb +84 -0
  336. data/lib/datadog/core/utils/truncation.rb +21 -0
  337. data/lib/datadog/core/utils/url.rb +25 -0
  338. data/lib/datadog/core/utils.rb +101 -0
  339. data/lib/datadog/core/vendor/multipart-post/LICENSE +11 -0
  340. data/lib/datadog/core/vendor/multipart-post/multipart/post/composite_read_io.rb +118 -0
  341. data/lib/datadog/core/vendor/multipart-post/multipart/post/multipartable.rb +59 -0
  342. data/lib/datadog/core/vendor/multipart-post/multipart/post/parts.rb +137 -0
  343. data/lib/datadog/core/vendor/multipart-post/multipart/post/version.rb +11 -0
  344. data/lib/datadog/core/vendor/multipart-post/multipart/post.rb +10 -0
  345. data/lib/datadog/core/vendor/multipart-post/multipart.rb +14 -0
  346. data/lib/datadog/core/vendor/multipart-post/net/http/post/multipart.rb +34 -0
  347. data/lib/datadog/core/worker.rb +24 -0
  348. data/lib/datadog/core/workers/async.rb +202 -0
  349. data/lib/datadog/core/workers/interval_loop.rb +134 -0
  350. data/lib/datadog/core/workers/polling.rb +59 -0
  351. data/lib/datadog/core/workers/queue.rb +44 -0
  352. data/lib/datadog/core/workers/runtime_metrics.rb +62 -0
  353. data/lib/datadog/core.rb +38 -0
  354. data/lib/datadog/data_streams/configuration/settings.rb +49 -0
  355. data/lib/datadog/data_streams/configuration.rb +11 -0
  356. data/lib/datadog/data_streams/ext.rb +11 -0
  357. data/lib/datadog/data_streams/extensions.rb +16 -0
  358. data/lib/datadog/data_streams/pathway_context.rb +169 -0
  359. data/lib/datadog/data_streams/processor.rb +509 -0
  360. data/lib/datadog/data_streams/transport/http/api.rb +33 -0
  361. data/lib/datadog/data_streams/transport/http/client.rb +49 -0
  362. data/lib/datadog/data_streams/transport/http/stats.rb +87 -0
  363. data/lib/datadog/data_streams/transport/http.rb +41 -0
  364. data/lib/datadog/data_streams/transport/stats.rb +60 -0
  365. data/lib/datadog/data_streams.rb +100 -0
  366. data/lib/datadog/di/base.rb +115 -0
  367. data/lib/datadog/di/boot.rb +43 -0
  368. data/lib/datadog/di/code_tracker.rb +204 -0
  369. data/lib/datadog/di/component.rb +122 -0
  370. data/lib/datadog/di/configuration/settings.rb +212 -0
  371. data/lib/datadog/di/configuration.rb +11 -0
  372. data/lib/datadog/di/context.rb +70 -0
  373. data/lib/datadog/di/contrib/active_record.rb +12 -0
  374. data/lib/datadog/di/contrib/railtie.rb +15 -0
  375. data/lib/datadog/di/contrib.rb +28 -0
  376. data/lib/datadog/di/el/compiler.rb +164 -0
  377. data/lib/datadog/di/el/evaluator.rb +159 -0
  378. data/lib/datadog/di/el/expression.rb +42 -0
  379. data/lib/datadog/di/el.rb +5 -0
  380. data/lib/datadog/di/error.rb +82 -0
  381. data/lib/datadog/di/extensions.rb +16 -0
  382. data/lib/datadog/di/instrumenter.rb +566 -0
  383. data/lib/datadog/di/logger.rb +30 -0
  384. data/lib/datadog/di/preload.rb +18 -0
  385. data/lib/datadog/di/probe.rb +231 -0
  386. data/lib/datadog/di/probe_builder.rb +86 -0
  387. data/lib/datadog/di/probe_file_loader/railtie.rb +15 -0
  388. data/lib/datadog/di/probe_file_loader.rb +82 -0
  389. data/lib/datadog/di/probe_manager.rb +261 -0
  390. data/lib/datadog/di/probe_notification_builder.rb +236 -0
  391. data/lib/datadog/di/probe_notifier_worker.rb +305 -0
  392. data/lib/datadog/di/proc_responder.rb +32 -0
  393. data/lib/datadog/di/redactor.rb +187 -0
  394. data/lib/datadog/di/remote.rb +145 -0
  395. data/lib/datadog/di/serializer.rb +422 -0
  396. data/lib/datadog/di/transport/diagnostics.rb +62 -0
  397. data/lib/datadog/di/transport/http/api.rb +42 -0
  398. data/lib/datadog/di/transport/http/client.rb +47 -0
  399. data/lib/datadog/di/transport/http/diagnostics.rb +65 -0
  400. data/lib/datadog/di/transport/http/input.rb +77 -0
  401. data/lib/datadog/di/transport/http.rb +57 -0
  402. data/lib/datadog/di/transport/input.rb +70 -0
  403. data/lib/datadog/di/utils.rb +142 -0
  404. data/lib/datadog/di.rb +36 -0
  405. data/lib/datadog/error_tracking/collector.rb +87 -0
  406. data/lib/datadog/error_tracking/component.rb +167 -0
  407. data/lib/datadog/error_tracking/configuration/settings.rb +63 -0
  408. data/lib/datadog/error_tracking/configuration.rb +11 -0
  409. data/lib/datadog/error_tracking/ext.rb +18 -0
  410. data/lib/datadog/error_tracking/extensions.rb +16 -0
  411. data/lib/datadog/error_tracking/filters.rb +77 -0
  412. data/lib/datadog/error_tracking.rb +18 -0
  413. data/lib/datadog/kit/appsec/events/v2.rb +196 -0
  414. data/lib/datadog/kit/appsec/events.rb +180 -0
  415. data/lib/datadog/kit/enable_core_dumps.rb +49 -0
  416. data/lib/datadog/kit/identity.rb +114 -0
  417. data/lib/datadog/kit.rb +11 -0
  418. data/lib/datadog/opentelemetry/api/baggage.rb +90 -0
  419. data/lib/datadog/opentelemetry/api/baggage.rbs +26 -0
  420. data/lib/datadog/opentelemetry/api/context.rb +208 -0
  421. data/lib/datadog/opentelemetry/api/trace/span.rb +14 -0
  422. data/lib/datadog/opentelemetry/sdk/configurator.rb +37 -0
  423. data/lib/datadog/opentelemetry/sdk/id_generator.rb +26 -0
  424. data/lib/datadog/opentelemetry/sdk/propagator.rb +89 -0
  425. data/lib/datadog/opentelemetry/sdk/span_processor.rb +169 -0
  426. data/lib/datadog/opentelemetry/sdk/trace/span.rb +182 -0
  427. data/lib/datadog/opentelemetry/trace.rb +59 -0
  428. data/lib/datadog/opentelemetry.rb +52 -0
  429. data/lib/datadog/profiling/collectors/code_provenance.rb +150 -0
  430. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +147 -0
  431. data/lib/datadog/profiling/collectors/dynamic_sampling_rate.rb +14 -0
  432. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +69 -0
  433. data/lib/datadog/profiling/collectors/info.rb +156 -0
  434. data/lib/datadog/profiling/collectors/stack.rb +13 -0
  435. data/lib/datadog/profiling/collectors/thread_context.rb +102 -0
  436. data/lib/datadog/profiling/component.rb +445 -0
  437. data/lib/datadog/profiling/encoded_profile.rb +11 -0
  438. data/lib/datadog/profiling/exporter.rb +111 -0
  439. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +410 -0
  440. data/lib/datadog/profiling/ext.rb +22 -0
  441. data/lib/datadog/profiling/flush.rb +40 -0
  442. data/lib/datadog/profiling/http_transport.rb +67 -0
  443. data/lib/datadog/profiling/load_native_extension.rb +9 -0
  444. data/lib/datadog/profiling/native_extension.rb +20 -0
  445. data/lib/datadog/profiling/preload.rb +5 -0
  446. data/lib/datadog/profiling/profiler.rb +70 -0
  447. data/lib/datadog/profiling/scheduler.rb +153 -0
  448. data/lib/datadog/profiling/sequence_tracker.rb +44 -0
  449. data/lib/datadog/profiling/stack_recorder.rb +104 -0
  450. data/lib/datadog/profiling/tag_builder.rb +59 -0
  451. data/lib/datadog/profiling/tasks/exec.rb +50 -0
  452. data/lib/datadog/profiling/tasks/help.rb +18 -0
  453. data/lib/datadog/profiling/tasks/setup.rb +43 -0
  454. data/lib/datadog/profiling.rb +167 -0
  455. data/lib/datadog/single_step_instrument.rb +21 -0
  456. data/lib/datadog/tracing/analytics.rb +25 -0
  457. data/lib/datadog/tracing/buffer.rb +129 -0
  458. data/lib/datadog/tracing/client_ip.rb +61 -0
  459. data/lib/datadog/tracing/component.rb +216 -0
  460. data/lib/datadog/tracing/configuration/dynamic/option.rb +71 -0
  461. data/lib/datadog/tracing/configuration/dynamic.rb +100 -0
  462. data/lib/datadog/tracing/configuration/ext.rb +118 -0
  463. data/lib/datadog/tracing/configuration/http.rb +74 -0
  464. data/lib/datadog/tracing/configuration/settings.rb +579 -0
  465. data/lib/datadog/tracing/context.rb +68 -0
  466. data/lib/datadog/tracing/context_provider.rb +82 -0
  467. data/lib/datadog/tracing/contrib/action_cable/configuration/settings.rb +39 -0
  468. data/lib/datadog/tracing/contrib/action_cable/event.rb +71 -0
  469. data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +58 -0
  470. data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +63 -0
  471. data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +59 -0
  472. data/lib/datadog/tracing/contrib/action_cable/events.rb +37 -0
  473. data/lib/datadog/tracing/contrib/action_cable/ext.rb +33 -0
  474. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +86 -0
  475. data/lib/datadog/tracing/contrib/action_cable/integration.rb +53 -0
  476. data/lib/datadog/tracing/contrib/action_cable/patcher.rb +31 -0
  477. data/lib/datadog/tracing/contrib/action_mailer/configuration/settings.rb +43 -0
  478. data/lib/datadog/tracing/contrib/action_mailer/event.rb +50 -0
  479. data/lib/datadog/tracing/contrib/action_mailer/events/deliver.rb +65 -0
  480. data/lib/datadog/tracing/contrib/action_mailer/events/process.rb +48 -0
  481. data/lib/datadog/tracing/contrib/action_mailer/events.rb +34 -0
  482. data/lib/datadog/tracing/contrib/action_mailer/ext.rb +34 -0
  483. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +54 -0
  484. data/lib/datadog/tracing/contrib/action_mailer/patcher.rb +29 -0
  485. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +158 -0
  486. data/lib/datadog/tracing/contrib/action_pack/action_controller/patcher.rb +29 -0
  487. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +85 -0
  488. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
  489. data/lib/datadog/tracing/contrib/action_pack/configuration/settings.rb +40 -0
  490. data/lib/datadog/tracing/contrib/action_pack/ext.rb +25 -0
  491. data/lib/datadog/tracing/contrib/action_pack/integration.rb +54 -0
  492. data/lib/datadog/tracing/contrib/action_pack/patcher.rb +29 -0
  493. data/lib/datadog/tracing/contrib/action_pack/utils.rb +39 -0
  494. data/lib/datadog/tracing/contrib/action_view/configuration/settings.rb +43 -0
  495. data/lib/datadog/tracing/contrib/action_view/event.rb +35 -0
  496. data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +50 -0
  497. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +57 -0
  498. data/lib/datadog/tracing/contrib/action_view/events.rb +34 -0
  499. data/lib/datadog/tracing/contrib/action_view/ext.rb +25 -0
  500. data/lib/datadog/tracing/contrib/action_view/integration.rb +61 -0
  501. data/lib/datadog/tracing/contrib/action_view/patcher.rb +34 -0
  502. data/lib/datadog/tracing/contrib/action_view/utils.rb +36 -0
  503. data/lib/datadog/tracing/contrib/active_job/configuration/settings.rb +39 -0
  504. data/lib/datadog/tracing/contrib/active_job/event.rb +58 -0
  505. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +50 -0
  506. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +49 -0
  507. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +49 -0
  508. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +51 -0
  509. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +49 -0
  510. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +50 -0
  511. data/lib/datadog/tracing/contrib/active_job/events.rb +42 -0
  512. data/lib/datadog/tracing/contrib/active_job/ext.rb +40 -0
  513. data/lib/datadog/tracing/contrib/active_job/integration.rb +53 -0
  514. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +38 -0
  515. data/lib/datadog/tracing/contrib/active_job/patcher.rb +40 -0
  516. data/lib/datadog/tracing/contrib/active_model_serializers/configuration/settings.rb +37 -0
  517. data/lib/datadog/tracing/contrib/active_model_serializers/event.rb +68 -0
  518. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +45 -0
  519. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +47 -0
  520. data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +34 -0
  521. data/lib/datadog/tracing/contrib/active_model_serializers/ext.rb +25 -0
  522. data/lib/datadog/tracing/contrib/active_model_serializers/integration.rb +44 -0
  523. data/lib/datadog/tracing/contrib/active_model_serializers/patcher.rb +32 -0
  524. data/lib/datadog/tracing/contrib/active_record/configuration/makara_resolver.rb +36 -0
  525. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +152 -0
  526. data/lib/datadog/tracing/contrib/active_record/configuration/settings.rb +48 -0
  527. data/lib/datadog/tracing/contrib/active_record/event.rb +30 -0
  528. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +60 -0
  529. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +80 -0
  530. data/lib/datadog/tracing/contrib/active_record/events.rb +34 -0
  531. data/lib/datadog/tracing/contrib/active_record/ext.rb +30 -0
  532. data/lib/datadog/tracing/contrib/active_record/integration.rb +71 -0
  533. data/lib/datadog/tracing/contrib/active_record/patcher.rb +27 -0
  534. data/lib/datadog/tracing/contrib/active_record/utils.rb +128 -0
  535. data/lib/datadog/tracing/contrib/active_support/cache/event.rb +32 -0
  536. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +171 -0
  537. data/lib/datadog/tracing/contrib/active_support/cache/events.rb +34 -0
  538. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +225 -0
  539. data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +57 -0
  540. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +60 -0
  541. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +70 -0
  542. data/lib/datadog/tracing/contrib/active_support/ext.rb +32 -0
  543. data/lib/datadog/tracing/contrib/active_support/integration.rb +55 -0
  544. data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +95 -0
  545. data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +83 -0
  546. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +166 -0
  547. data/lib/datadog/tracing/contrib/active_support/patcher.rb +27 -0
  548. data/lib/datadog/tracing/contrib/analytics.rb +33 -0
  549. data/lib/datadog/tracing/contrib/auto_instrument.rb +53 -0
  550. data/lib/datadog/tracing/contrib/aws/configuration/settings.rb +53 -0
  551. data/lib/datadog/tracing/contrib/aws/ext.rb +50 -0
  552. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +136 -0
  553. data/lib/datadog/tracing/contrib/aws/integration.rb +50 -0
  554. data/lib/datadog/tracing/contrib/aws/parsed_context.rb +70 -0
  555. data/lib/datadog/tracing/contrib/aws/patcher.rb +61 -0
  556. data/lib/datadog/tracing/contrib/aws/service/base.rb +17 -0
  557. data/lib/datadog/tracing/contrib/aws/service/dynamodb.rb +22 -0
  558. data/lib/datadog/tracing/contrib/aws/service/eventbridge.rb +22 -0
  559. data/lib/datadog/tracing/contrib/aws/service/kinesis.rb +32 -0
  560. data/lib/datadog/tracing/contrib/aws/service/s3.rb +22 -0
  561. data/lib/datadog/tracing/contrib/aws/service/sns.rb +30 -0
  562. data/lib/datadog/tracing/contrib/aws/service/sqs.rb +27 -0
  563. data/lib/datadog/tracing/contrib/aws/service/states.rb +40 -0
  564. data/lib/datadog/tracing/contrib/aws/services.rb +139 -0
  565. data/lib/datadog/tracing/contrib/cloudwise/propagation.rb +315 -0
  566. data/lib/datadog/tracing/contrib/component.rb +41 -0
  567. data/lib/datadog/tracing/contrib/concurrent_ruby/async_patch.rb +20 -0
  568. data/lib/datadog/tracing/contrib/concurrent_ruby/configuration/settings.rb +24 -0
  569. data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +53 -0
  570. data/lib/datadog/tracing/contrib/concurrent_ruby/ext.rb +16 -0
  571. data/lib/datadog/tracing/contrib/concurrent_ruby/future_patch.rb +20 -0
  572. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +47 -0
  573. data/lib/datadog/tracing/contrib/concurrent_ruby/patcher.rb +49 -0
  574. data/lib/datadog/tracing/contrib/concurrent_ruby/promises_future_patch.rb +22 -0
  575. data/lib/datadog/tracing/contrib/configurable.rb +102 -0
  576. data/lib/datadog/tracing/contrib/configuration/resolver.rb +128 -0
  577. data/lib/datadog/tracing/contrib/configuration/resolvers/pattern_resolver.rb +43 -0
  578. data/lib/datadog/tracing/contrib/configuration/settings.rb +43 -0
  579. data/lib/datadog/tracing/contrib/dalli/configuration/settings.rb +58 -0
  580. data/lib/datadog/tracing/contrib/dalli/ext.rb +41 -0
  581. data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +75 -0
  582. data/lib/datadog/tracing/contrib/dalli/integration.rb +52 -0
  583. data/lib/datadog/tracing/contrib/dalli/patcher.rb +28 -0
  584. data/lib/datadog/tracing/contrib/dalli/quantize.rb +26 -0
  585. data/lib/datadog/tracing/contrib/delayed_job/configuration/settings.rb +49 -0
  586. data/lib/datadog/tracing/contrib/delayed_job/ext.rb +29 -0
  587. data/lib/datadog/tracing/contrib/delayed_job/integration.rb +43 -0
  588. data/lib/datadog/tracing/contrib/delayed_job/patcher.rb +37 -0
  589. data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +108 -0
  590. data/lib/datadog/tracing/contrib/delayed_job/server_internal_tracer/worker.rb +34 -0
  591. data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +61 -0
  592. data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +35 -0
  593. data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +50 -0
  594. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +172 -0
  595. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +87 -0
  596. data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +56 -0
  597. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +229 -0
  598. data/lib/datadog/tracing/contrib/ethon/ext.rb +33 -0
  599. data/lib/datadog/tracing/contrib/ethon/integration.rb +48 -0
  600. data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +102 -0
  601. data/lib/datadog/tracing/contrib/ethon/patcher.rb +30 -0
  602. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +82 -0
  603. data/lib/datadog/tracing/contrib/excon/ext.rb +31 -0
  604. data/lib/datadog/tracing/contrib/excon/integration.rb +48 -0
  605. data/lib/datadog/tracing/contrib/excon/middleware.rb +201 -0
  606. data/lib/datadog/tracing/contrib/excon/patcher.rb +31 -0
  607. data/lib/datadog/tracing/contrib/ext.rb +70 -0
  608. data/lib/datadog/tracing/contrib/extensions.rb +255 -0
  609. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +81 -0
  610. data/lib/datadog/tracing/contrib/faraday/connection.rb +22 -0
  611. data/lib/datadog/tracing/contrib/faraday/ext.rb +31 -0
  612. data/lib/datadog/tracing/contrib/faraday/integration.rb +48 -0
  613. data/lib/datadog/tracing/contrib/faraday/middleware.rb +128 -0
  614. data/lib/datadog/tracing/contrib/faraday/patcher.rb +56 -0
  615. data/lib/datadog/tracing/contrib/faraday/rack_builder.rb +22 -0
  616. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +59 -0
  617. data/lib/datadog/tracing/contrib/grape/endpoint.rb +316 -0
  618. data/lib/datadog/tracing/contrib/grape/ext.rb +30 -0
  619. data/lib/datadog/tracing/contrib/grape/instrumentation.rb +37 -0
  620. data/lib/datadog/tracing/contrib/grape/integration.rb +44 -0
  621. data/lib/datadog/tracing/contrib/grape/patcher.rb +33 -0
  622. data/lib/datadog/tracing/contrib/graphql/configuration/error_extension_env_parser.rb +21 -0
  623. data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +73 -0
  624. data/lib/datadog/tracing/contrib/graphql/ext.rb +26 -0
  625. data/lib/datadog/tracing/contrib/graphql/integration.rb +56 -0
  626. data/lib/datadog/tracing/contrib/graphql/patcher.rb +58 -0
  627. data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +24 -0
  628. data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +28 -0
  629. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +297 -0
  630. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +31 -0
  631. data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +58 -0
  632. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +123 -0
  633. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +96 -0
  634. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor.rb +107 -0
  635. data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +26 -0
  636. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +49 -0
  637. data/lib/datadog/tracing/contrib/grpc/ext.rb +29 -0
  638. data/lib/datadog/tracing/contrib/grpc/formatting.rb +127 -0
  639. data/lib/datadog/tracing/contrib/grpc/integration.rb +50 -0
  640. data/lib/datadog/tracing/contrib/grpc/intercept_with_datadog.rb +53 -0
  641. data/lib/datadog/tracing/contrib/grpc/patcher.rb +34 -0
  642. data/lib/datadog/tracing/contrib/grpc.rb +45 -0
  643. data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +47 -0
  644. data/lib/datadog/tracing/contrib/hanami/configuration/settings.rb +23 -0
  645. data/lib/datadog/tracing/contrib/hanami/ext.rb +24 -0
  646. data/lib/datadog/tracing/contrib/hanami/integration.rb +44 -0
  647. data/lib/datadog/tracing/contrib/hanami/patcher.rb +33 -0
  648. data/lib/datadog/tracing/contrib/hanami/plugin.rb +23 -0
  649. data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +41 -0
  650. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +42 -0
  651. data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +34 -0
  652. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +77 -0
  653. data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +38 -0
  654. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +48 -0
  655. data/lib/datadog/tracing/contrib/http/ext.rb +30 -0
  656. data/lib/datadog/tracing/contrib/http/instrumentation.rb +152 -0
  657. data/lib/datadog/tracing/contrib/http/integration.rb +52 -0
  658. data/lib/datadog/tracing/contrib/http/patcher.rb +30 -0
  659. data/lib/datadog/tracing/contrib/http.rb +45 -0
  660. data/lib/datadog/tracing/contrib/http_annotation_helper.rb +17 -0
  661. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +76 -0
  662. data/lib/datadog/tracing/contrib/httpclient/ext.rb +31 -0
  663. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +132 -0
  664. data/lib/datadog/tracing/contrib/httpclient/integration.rb +48 -0
  665. data/lib/datadog/tracing/contrib/httpclient/patcher.rb +29 -0
  666. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +76 -0
  667. data/lib/datadog/tracing/contrib/httprb/ext.rb +30 -0
  668. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +146 -0
  669. data/lib/datadog/tracing/contrib/httprb/integration.rb +51 -0
  670. data/lib/datadog/tracing/contrib/httprb/patcher.rb +29 -0
  671. data/lib/datadog/tracing/contrib/integration.rb +78 -0
  672. data/lib/datadog/tracing/contrib/kafka/configuration/settings.rb +39 -0
  673. data/lib/datadog/tracing/contrib/kafka/consumer_event.rb +19 -0
  674. data/lib/datadog/tracing/contrib/kafka/consumer_group_event.rb +18 -0
  675. data/lib/datadog/tracing/contrib/kafka/event.rb +53 -0
  676. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +42 -0
  677. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +49 -0
  678. data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +47 -0
  679. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +47 -0
  680. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/join_group.rb +37 -0
  681. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/leave_group.rb +37 -0
  682. data/lib/datadog/tracing/contrib/kafka/events/consumer_group/sync_group.rb +37 -0
  683. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +41 -0
  684. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +44 -0
  685. data/lib/datadog/tracing/contrib/kafka/events.rb +48 -0
  686. data/lib/datadog/tracing/contrib/kafka/ext.rb +55 -0
  687. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  688. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  689. data/lib/datadog/tracing/contrib/kafka/integration.rb +47 -0
  690. data/lib/datadog/tracing/contrib/kafka/patcher.rb +43 -0
  691. data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +27 -0
  692. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +48 -0
  693. data/lib/datadog/tracing/contrib/karafka/ext.rb +27 -0
  694. data/lib/datadog/tracing/contrib/karafka/integration.rb +45 -0
  695. data/lib/datadog/tracing/contrib/karafka/monitor.rb +77 -0
  696. data/lib/datadog/tracing/contrib/karafka/patcher.rb +89 -0
  697. data/lib/datadog/tracing/contrib/karafka.rb +37 -0
  698. data/lib/datadog/tracing/contrib/lograge/configuration/settings.rb +24 -0
  699. data/lib/datadog/tracing/contrib/lograge/ext.rb +15 -0
  700. data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +31 -0
  701. data/lib/datadog/tracing/contrib/lograge/integration.rb +50 -0
  702. data/lib/datadog/tracing/contrib/lograge/patcher.rb +46 -0
  703. data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +64 -0
  704. data/lib/datadog/tracing/contrib/mongodb/ext.rb +39 -0
  705. data/lib/datadog/tracing/contrib/mongodb/instrumentation.rb +47 -0
  706. data/lib/datadog/tracing/contrib/mongodb/integration.rb +51 -0
  707. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +49 -0
  708. data/lib/datadog/tracing/contrib/mongodb/patcher.rb +34 -0
  709. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +160 -0
  710. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +69 -0
  711. data/lib/datadog/tracing/contrib/mysql2/ext.rb +28 -0
  712. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +109 -0
  713. data/lib/datadog/tracing/contrib/mysql2/integration.rb +43 -0
  714. data/lib/datadog/tracing/contrib/mysql2/patcher.rb +31 -0
  715. data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +71 -0
  716. data/lib/datadog/tracing/contrib/opensearch/ext.rb +48 -0
  717. data/lib/datadog/tracing/contrib/opensearch/integration.rb +46 -0
  718. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +144 -0
  719. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +81 -0
  720. data/lib/datadog/tracing/contrib/patchable.rb +109 -0
  721. data/lib/datadog/tracing/contrib/patcher.rb +87 -0
  722. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +69 -0
  723. data/lib/datadog/tracing/contrib/pg/ext.rb +35 -0
  724. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +214 -0
  725. data/lib/datadog/tracing/contrib/pg/integration.rb +43 -0
  726. data/lib/datadog/tracing/contrib/pg/patcher.rb +31 -0
  727. data/lib/datadog/tracing/contrib/presto/configuration/settings.rb +52 -0
  728. data/lib/datadog/tracing/contrib/presto/ext.rb +38 -0
  729. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +138 -0
  730. data/lib/datadog/tracing/contrib/presto/integration.rb +46 -0
  731. data/lib/datadog/tracing/contrib/presto/patcher.rb +25 -0
  732. data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +41 -0
  733. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +61 -0
  734. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +32 -0
  735. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +67 -0
  736. data/lib/datadog/tracing/contrib/que/configuration/settings.rb +55 -0
  737. data/lib/datadog/tracing/contrib/que/ext.rb +33 -0
  738. data/lib/datadog/tracing/contrib/que/integration.rb +44 -0
  739. data/lib/datadog/tracing/contrib/que/patcher.rb +26 -0
  740. data/lib/datadog/tracing/contrib/que/tracer.rb +63 -0
  741. data/lib/datadog/tracing/contrib/racecar/configuration/settings.rb +47 -0
  742. data/lib/datadog/tracing/contrib/racecar/event.rb +81 -0
  743. data/lib/datadog/tracing/contrib/racecar/events/batch.rb +38 -0
  744. data/lib/datadog/tracing/contrib/racecar/events/consume.rb +35 -0
  745. data/lib/datadog/tracing/contrib/racecar/events/message.rb +38 -0
  746. data/lib/datadog/tracing/contrib/racecar/events.rb +36 -0
  747. data/lib/datadog/tracing/contrib/racecar/ext.rb +33 -0
  748. data/lib/datadog/tracing/contrib/racecar/integration.rb +44 -0
  749. data/lib/datadog/tracing/contrib/racecar/patcher.rb +29 -0
  750. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +59 -0
  751. data/lib/datadog/tracing/contrib/rack/ext.rb +30 -0
  752. data/lib/datadog/tracing/contrib/rack/header_collection.rb +50 -0
  753. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +63 -0
  754. data/lib/datadog/tracing/contrib/rack/integration.rb +50 -0
  755. data/lib/datadog/tracing/contrib/rack/middlewares.rb +475 -0
  756. data/lib/datadog/tracing/contrib/rack/patcher.rb +119 -0
  757. data/lib/datadog/tracing/contrib/rack/request_queue.rb +49 -0
  758. data/lib/datadog/tracing/contrib/rack/route_inference.rb +53 -0
  759. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +58 -0
  760. data/lib/datadog/tracing/contrib/rails/auto_instrument_railtie.rb +10 -0
  761. data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +76 -0
  762. data/lib/datadog/tracing/contrib/rails/ext.rb +33 -0
  763. data/lib/datadog/tracing/contrib/rails/framework.rb +148 -0
  764. data/lib/datadog/tracing/contrib/rails/integration.rb +52 -0
  765. data/lib/datadog/tracing/contrib/rails/log_injection.rb +29 -0
  766. data/lib/datadog/tracing/contrib/rails/middlewares.rb +46 -0
  767. data/lib/datadog/tracing/contrib/rails/patcher.rb +98 -0
  768. data/lib/datadog/tracing/contrib/rails/railtie.rb +19 -0
  769. data/lib/datadog/tracing/contrib/rails/runner.rb +117 -0
  770. data/lib/datadog/tracing/contrib/rake/configuration/settings.rb +55 -0
  771. data/lib/datadog/tracing/contrib/rake/ext.rb +27 -0
  772. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +103 -0
  773. data/lib/datadog/tracing/contrib/rake/integration.rb +43 -0
  774. data/lib/datadog/tracing/contrib/rake/patcher.rb +33 -0
  775. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +49 -0
  776. data/lib/datadog/tracing/contrib/redis/configuration/settings.rb +57 -0
  777. data/lib/datadog/tracing/contrib/redis/ext.rb +36 -0
  778. data/lib/datadog/tracing/contrib/redis/instrumentation.rb +53 -0
  779. data/lib/datadog/tracing/contrib/redis/integration.rb +80 -0
  780. data/lib/datadog/tracing/contrib/redis/patcher.rb +92 -0
  781. data/lib/datadog/tracing/contrib/redis/quantize.rb +80 -0
  782. data/lib/datadog/tracing/contrib/redis/tags.rb +72 -0
  783. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +85 -0
  784. data/lib/datadog/tracing/contrib/redis/vendor/LICENSE +20 -0
  785. data/lib/datadog/tracing/contrib/redis/vendor/resolver.rb +160 -0
  786. data/lib/datadog/tracing/contrib/registerable.rb +50 -0
  787. data/lib/datadog/tracing/contrib/registry.rb +52 -0
  788. data/lib/datadog/tracing/contrib/resque/configuration/settings.rb +42 -0
  789. data/lib/datadog/tracing/contrib/resque/ext.rb +22 -0
  790. data/lib/datadog/tracing/contrib/resque/integration.rb +48 -0
  791. data/lib/datadog/tracing/contrib/resque/patcher.rb +29 -0
  792. data/lib/datadog/tracing/contrib/resque/resque_job.rb +106 -0
  793. data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +55 -0
  794. data/lib/datadog/tracing/contrib/rest_client/ext.rb +29 -0
  795. data/lib/datadog/tracing/contrib/rest_client/integration.rb +46 -0
  796. data/lib/datadog/tracing/contrib/rest_client/patcher.rb +28 -0
  797. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +137 -0
  798. data/lib/datadog/tracing/contrib/roda/configuration/settings.rb +38 -0
  799. data/lib/datadog/tracing/contrib/roda/ext.rb +19 -0
  800. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +78 -0
  801. data/lib/datadog/tracing/contrib/roda/integration.rb +45 -0
  802. data/lib/datadog/tracing/contrib/roda/patcher.rb +30 -0
  803. data/lib/datadog/tracing/contrib/semantic_logger/configuration/settings.rb +24 -0
  804. data/lib/datadog/tracing/contrib/semantic_logger/ext.rb +15 -0
  805. data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +35 -0
  806. data/lib/datadog/tracing/contrib/semantic_logger/integration.rb +52 -0
  807. data/lib/datadog/tracing/contrib/semantic_logger/patcher.rb +29 -0
  808. data/lib/datadog/tracing/contrib/sequel/configuration/settings.rb +37 -0
  809. data/lib/datadog/tracing/contrib/sequel/database.rb +62 -0
  810. data/lib/datadog/tracing/contrib/sequel/dataset.rb +67 -0
  811. data/lib/datadog/tracing/contrib/sequel/ext.rb +23 -0
  812. data/lib/datadog/tracing/contrib/sequel/integration.rb +43 -0
  813. data/lib/datadog/tracing/contrib/sequel/patcher.rb +37 -0
  814. data/lib/datadog/tracing/contrib/sequel/utils.rb +90 -0
  815. data/lib/datadog/tracing/contrib/shoryuken/configuration/settings.rb +43 -0
  816. data/lib/datadog/tracing/contrib/shoryuken/ext.rb +27 -0
  817. data/lib/datadog/tracing/contrib/shoryuken/integration.rb +44 -0
  818. data/lib/datadog/tracing/contrib/shoryuken/patcher.rb +28 -0
  819. data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +65 -0
  820. data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +67 -0
  821. data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +47 -0
  822. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +49 -0
  823. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +45 -0
  824. data/lib/datadog/tracing/contrib/sidekiq/integration.rb +61 -0
  825. data/lib/datadog/tracing/contrib/sidekiq/patcher.rb +90 -0
  826. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/heartbeat.rb +61 -0
  827. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/job_fetch.rb +36 -0
  828. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/redis_info.rb +34 -0
  829. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/scheduled_poller.rb +57 -0
  830. data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/stop.rb +34 -0
  831. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +91 -0
  832. data/lib/datadog/tracing/contrib/sidekiq/utils.rb +44 -0
  833. data/lib/datadog/tracing/contrib/sidekiq.rb +37 -0
  834. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +46 -0
  835. data/lib/datadog/tracing/contrib/sinatra/env.rb +38 -0
  836. data/lib/datadog/tracing/contrib/sinatra/ext.rb +31 -0
  837. data/lib/datadog/tracing/contrib/sinatra/framework.rb +116 -0
  838. data/lib/datadog/tracing/contrib/sinatra/integration.rb +43 -0
  839. data/lib/datadog/tracing/contrib/sinatra/patcher.rb +75 -0
  840. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +90 -0
  841. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +109 -0
  842. data/lib/datadog/tracing/contrib/sneakers/configuration/settings.rb +43 -0
  843. data/lib/datadog/tracing/contrib/sneakers/ext.rb +27 -0
  844. data/lib/datadog/tracing/contrib/sneakers/integration.rb +44 -0
  845. data/lib/datadog/tracing/contrib/sneakers/patcher.rb +27 -0
  846. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +60 -0
  847. data/lib/datadog/tracing/contrib/span_attribute_schema.rb +97 -0
  848. data/lib/datadog/tracing/contrib/status_range_env_parser.rb +33 -0
  849. data/lib/datadog/tracing/contrib/status_range_matcher.rb +32 -0
  850. data/lib/datadog/tracing/contrib/stripe/configuration/settings.rb +37 -0
  851. data/lib/datadog/tracing/contrib/stripe/ext.rb +27 -0
  852. data/lib/datadog/tracing/contrib/stripe/integration.rb +43 -0
  853. data/lib/datadog/tracing/contrib/stripe/patcher.rb +28 -0
  854. data/lib/datadog/tracing/contrib/stripe/request.rb +68 -0
  855. data/lib/datadog/tracing/contrib/sucker_punch/configuration/settings.rb +39 -0
  856. data/lib/datadog/tracing/contrib/sucker_punch/exception_handler.rb +28 -0
  857. data/lib/datadog/tracing/contrib/sucker_punch/ext.rb +28 -0
  858. data/lib/datadog/tracing/contrib/sucker_punch/instrumentation.rb +104 -0
  859. data/lib/datadog/tracing/contrib/sucker_punch/integration.rb +43 -0
  860. data/lib/datadog/tracing/contrib/sucker_punch/patcher.rb +35 -0
  861. data/lib/datadog/tracing/contrib/support.rb +28 -0
  862. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +63 -0
  863. data/lib/datadog/tracing/contrib/trilogy/ext.rb +27 -0
  864. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +97 -0
  865. data/lib/datadog/tracing/contrib/trilogy/integration.rb +43 -0
  866. data/lib/datadog/tracing/contrib/trilogy/patcher.rb +31 -0
  867. data/lib/datadog/tracing/contrib/utils/database.rb +31 -0
  868. data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +111 -0
  869. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +179 -0
  870. data/lib/datadog/tracing/contrib.rb +82 -0
  871. data/lib/datadog/tracing/correlation.rb +113 -0
  872. data/lib/datadog/tracing/diagnostics/environment_logger.rb +163 -0
  873. data/lib/datadog/tracing/diagnostics/ext.rb +36 -0
  874. data/lib/datadog/tracing/diagnostics/health.rb +40 -0
  875. data/lib/datadog/tracing/distributed/b3_multi.rb +73 -0
  876. data/lib/datadog/tracing/distributed/b3_single.rb +71 -0
  877. data/lib/datadog/tracing/distributed/baggage.rb +196 -0
  878. data/lib/datadog/tracing/distributed/datadog.rb +201 -0
  879. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +82 -0
  880. data/lib/datadog/tracing/distributed/fetcher.rb +21 -0
  881. data/lib/datadog/tracing/distributed/helpers.rb +65 -0
  882. data/lib/datadog/tracing/distributed/none.rb +20 -0
  883. data/lib/datadog/tracing/distributed/propagation.rb +187 -0
  884. data/lib/datadog/tracing/distributed/propagation_policy.rb +42 -0
  885. data/lib/datadog/tracing/distributed/trace_context.rb +444 -0
  886. data/lib/datadog/tracing/event.rb +74 -0
  887. data/lib/datadog/tracing/flush.rb +96 -0
  888. data/lib/datadog/tracing/metadata/analytics.rb +26 -0
  889. data/lib/datadog/tracing/metadata/errors.rb +32 -0
  890. data/lib/datadog/tracing/metadata/ext.rb +213 -0
  891. data/lib/datadog/tracing/metadata/metastruct.rb +36 -0
  892. data/lib/datadog/tracing/metadata/metastruct_tagging.rb +42 -0
  893. data/lib/datadog/tracing/metadata/tagging.rb +131 -0
  894. data/lib/datadog/tracing/metadata.rb +22 -0
  895. data/lib/datadog/tracing/pipeline/span_filter.rb +48 -0
  896. data/lib/datadog/tracing/pipeline/span_processor.rb +41 -0
  897. data/lib/datadog/tracing/pipeline.rb +63 -0
  898. data/lib/datadog/tracing/remote.rb +85 -0
  899. data/lib/datadog/tracing/runtime/metrics.rb +17 -0
  900. data/lib/datadog/tracing/sampling/all_sampler.rb +24 -0
  901. data/lib/datadog/tracing/sampling/ext.rb +58 -0
  902. data/lib/datadog/tracing/sampling/matcher.rb +119 -0
  903. data/lib/datadog/tracing/sampling/priority_sampler.rb +160 -0
  904. data/lib/datadog/tracing/sampling/rate_by_key_sampler.rb +87 -0
  905. data/lib/datadog/tracing/sampling/rate_by_service_sampler.rb +63 -0
  906. data/lib/datadog/tracing/sampling/rate_sampler.rb +59 -0
  907. data/lib/datadog/tracing/sampling/rule.rb +86 -0
  908. data/lib/datadog/tracing/sampling/rule_sampler.rb +172 -0
  909. data/lib/datadog/tracing/sampling/sampler.rb +32 -0
  910. data/lib/datadog/tracing/sampling/span/ext.rb +25 -0
  911. data/lib/datadog/tracing/sampling/span/matcher.rb +61 -0
  912. data/lib/datadog/tracing/sampling/span/rule.rb +77 -0
  913. data/lib/datadog/tracing/sampling/span/rule_parser.rb +104 -0
  914. data/lib/datadog/tracing/sampling/span/sampler.rb +70 -0
  915. data/lib/datadog/tracing/span.rb +236 -0
  916. data/lib/datadog/tracing/span_event.rb +161 -0
  917. data/lib/datadog/tracing/span_link.rb +92 -0
  918. data/lib/datadog/tracing/span_operation.rb +561 -0
  919. data/lib/datadog/tracing/sync_writer.rb +71 -0
  920. data/lib/datadog/tracing/trace_digest.rb +190 -0
  921. data/lib/datadog/tracing/trace_operation.rb +556 -0
  922. data/lib/datadog/tracing/trace_segment.rb +227 -0
  923. data/lib/datadog/tracing/tracer.rb +644 -0
  924. data/lib/datadog/tracing/transport/http/api.rb +44 -0
  925. data/lib/datadog/tracing/transport/http/client.rb +59 -0
  926. data/lib/datadog/tracing/transport/http/statistics.rb +47 -0
  927. data/lib/datadog/tracing/transport/http/traces.rb +155 -0
  928. data/lib/datadog/tracing/transport/http.rb +44 -0
  929. data/lib/datadog/tracing/transport/io/client.rb +90 -0
  930. data/lib/datadog/tracing/transport/io/response.rb +27 -0
  931. data/lib/datadog/tracing/transport/io/traces.rb +101 -0
  932. data/lib/datadog/tracing/transport/io.rb +30 -0
  933. data/lib/datadog/tracing/transport/serializable_trace.rb +155 -0
  934. data/lib/datadog/tracing/transport/statistics.rb +77 -0
  935. data/lib/datadog/tracing/transport/trace_formatter.rb +276 -0
  936. data/lib/datadog/tracing/transport/traces.rb +258 -0
  937. data/lib/datadog/tracing/utils.rb +99 -0
  938. data/lib/datadog/tracing/workers/trace_writer.rb +199 -0
  939. data/lib/datadog/tracing/workers.rb +126 -0
  940. data/lib/datadog/tracing/writer.rb +190 -0
  941. data/lib/datadog/tracing.rb +214 -0
  942. data/lib/datadog/version.rb +27 -0
  943. data/lib/datadog.rb +20 -0
  944. metadata +1074 -0
@@ -0,0 +1,2221 @@
1
+ #include <ruby.h>
2
+
3
+ #include "datadog_ruby_common.h"
4
+ #include "collectors_thread_context.h"
5
+ #include "clock_id.h"
6
+ #include "collectors_stack.h"
7
+ #include "collectors_gc_profiling_helper.h"
8
+ #include "helpers.h"
9
+ #include "libdatadog_helpers.h"
10
+ #include "private_vm_api_access.h"
11
+ #include "stack_recorder.h"
12
+ #include "time_helpers.h"
13
+ #include "unsafe_api_calls_check.h"
14
+ #include "extconf.h"
15
+
16
+ // Used to trigger sampling of threads, based on external "events", such as:
17
+ // * periodic timer for cpu-time and wall-time
18
+ // * VM garbage collection events
19
+ // * VM object allocation events
20
+ //
21
+ // This file implements the native bits of the Datadog::Profiling::Collectors::ThreadContext class
22
+ //
23
+ // Triggering of this component (e.g. watching for the above "events") is implemented by Collectors::CpuAndWallTimeWorker.
24
+
25
+ // ---
26
+ // ## Tracking of cpu-time and wall-time spent during garbage collection
27
+ //
28
+ // This feature works by having a special state that a thread can be in: doing garbage collection. This state is
29
+ // tracked inside the thread's `per_thread_context.gc_tracking` data, and three functions, listed below. The functions
30
+ // will get called by the `Collectors::CpuAndWallTimeWorker` at very specific times in the VM lifetime.
31
+ //
32
+ // * `thread_context_collector_on_gc_start`: Called at the very beginning of the garbage collection process.
33
+ // The internal VM `during_gc` flag is set to `true`, but Ruby has not done any work yet.
34
+ // * `thread_context_collector_on_gc_finish`: Called at the very end of the garbage collection process.
35
+ // The internal VM `during_gc` flag is still set to `true`, but all the work has been done.
36
+ // * `thread_context_collector_sample_after_gc`: Called shortly after the garbage collection process.
37
+ // The internal VM `during_gc` flag is set to `false`.
38
+ //
39
+ // Inside this component, here's what happens inside those three functions:
40
+ //
41
+ // When `thread_context_collector_on_gc_start` gets called, the current cpu and wall-time get recorded to the thread
42
+ // context: `cpu_time_at_gc_start_ns` and `wall_time_at_gc_start_ns`.
43
+ //
44
+ // While `cpu_time_at_gc_start_ns` is set, regular samples (if any) do not account for cpu-time any time that passes
45
+ // after this timestamp. The idea is that this cpu-time will be blamed separately on GC, and not on the user thread.
46
+ // Wall-time accounting is not affected by this (e.g. we still record 60 seconds every 60 seconds).
47
+ //
48
+ // (Regular samples can still account for the cpu-time between the previous sample and the start of GC.)
49
+ //
50
+ // When `thread_context_collector_on_gc_finish` gets called, the cpu-time and wall-time spent during GC gets recorded
51
+ // into the global gc_tracking structure, and further samples are not affected. (The `cpu_time_at_previous_sample_ns`
52
+ // of the thread that did GC also gets adjusted to avoid double-accounting.)
53
+ //
54
+ // Finally, when `thread_context_collector_sample_after_gc` gets called, a sample gets recorded with a stack having
55
+ // a single placeholder `Garbage Collection` frame. This sample gets
56
+ // assigned the cpu-time and wall-time that was recorded between calls to `on_gc_start` and `on_gc_finish`, as well
57
+ // as metadata for the last GC.
58
+ //
59
+ // Note that the Ruby GC does not usually do all of the GC work in one go. Instead, it breaks it up into smaller steps
60
+ // so that the application can keep doing user work in between GC steps.
61
+ // The `on_gc_start` / `on_gc_finish` will trigger each time the VM executes these smaller steps, and on a benchmark
62
+ // that executes `Object.new` in a loop, I measured more than 50k of this steps per second (!!).
63
+ // Creating these many events for every GC step is a lot of overhead, so instead `on_gc_finish` coalesces time
64
+ // spent in GC and only flushes it at most every 10 ms/every complete GC collection. This reduces the amount of
65
+ // individual GC events we need to record. We use the latest GC metadata for this event, reflecting the last GC that
66
+ // happened in the coalesced period.
67
+ //
68
+ // In an earlier attempt at implementing this functionality (https://github.com/DataDog/dd-trace-rb/pull/2308), we
69
+ // discovered that we needed to factor the sampling work away from `thread_context_collector_on_gc_finish` and into a
70
+ // separate `thread_context_collector_sample_after_gc` because (as documented in more detail below),
71
+ // `sample_after_gc` could trigger memory allocation in rare occasions (usually exceptions), which is actually not
72
+ // allowed to happen during Ruby's garbage collection start/finish hooks.
73
+ // ---
74
+
75
+ #define THREAD_ID_LIMIT_CHARS 44 // Why 44? "#{2**64} (#{2**64})".size + 1 for \0
76
+ #define THREAD_INVOKE_LOCATION_LIMIT_CHARS 512
77
+ #define IS_WALL_TIME true
78
+ #define IS_NOT_WALL_TIME false
79
+ #define MISSING_TRACER_CONTEXT_KEY 0
80
+ #define TIME_BETWEEN_GC_EVENTS_NS MILLIS_AS_NS(10)
81
+
82
+ // This is used as a placeholder to mark threads that are allowed to be profiled (enabled)
83
+ // (e.g. to avoid trying to gvl profile threads that are not from the main Ractor)
84
+ // and for which there's no data yet
85
+ #define GVL_WAITING_ENABLED_EMPTY RUBY_FIXNUM_MAX
86
+
87
+ static ID at_active_span_id; // id of :@active_span in Ruby
88
+ static ID at_active_trace_id; // id of :@active_trace in Ruby
89
+ static ID at_id_id; // id of :@id in Ruby
90
+ static ID at_resource_id; // id of :@resource in Ruby
91
+ static ID at_root_span_id; // id of :@root_span in Ruby
92
+ static ID at_type_id; // id of :@type in Ruby
93
+ static ID at_otel_values_id; // id of :@otel_values in Ruby
94
+ static ID at_parent_span_id_id; // id of :@parent_span_id in Ruby
95
+ static ID at_datadog_trace_id; // id of :@datadog_trace in Ruby
96
+
97
+ // Used to support reading trace identifiers from the opentelemetry Ruby library when the ddtrace gem tracing
98
+ // integration is NOT in use.
99
+ static ID at_span_id_id; // id of :@span_id in Ruby
100
+ static ID at_trace_id_id; // id of :@trace_id in Ruby
101
+ static ID at_entries_id; // id of :@entries in Ruby
102
+ static ID at_context_id; // id of :@context in Ruby
103
+ static ID at_kind_id; // id of :@kind in Ruby
104
+ static ID at_name_id; // id of :@name in Ruby
105
+ static ID server_id; // id of :server in Ruby
106
+ static ID otel_context_storage_id; // id of :__opentelemetry_context_storage__ in Ruby
107
+ static ID otel_fiber_context_storage_id; // id of :@opentelemetry_context in Ruby
108
+
109
+ // This is used by `thread_context_collector_on_gvl_running`. Because when that method gets called we're not sure if
110
+ // it's safe to access the state of the thread context collector, we store this setting as a global value. This does
111
+ // mean this setting is shared among all thread context collectors, and thus it's "last writer wins".
112
+ // In production this should not be a problem: there should only be one profiler, which is the last one created,
113
+ // and that'll be the one that last wrote this setting.
114
+ static uint32_t global_waiting_for_gvl_threshold_ns = MILLIS_AS_NS(10);
115
+
116
+ typedef enum { OTEL_CONTEXT_ENABLED_FALSE, OTEL_CONTEXT_ENABLED_ONLY, OTEL_CONTEXT_ENABLED_BOTH } otel_context_enabled;
117
+ typedef enum { OTEL_CONTEXT_SOURCE_UNKNOWN, OTEL_CONTEXT_SOURCE_FIBER_IVAR, OTEL_CONTEXT_SOURCE_FIBER_LOCAL } otel_context_source;
118
+
119
+ // Contains state for a single ThreadContext instance
120
+ typedef struct {
121
+ // Note: Places in this file that usually need to be changed when this struct is changed are tagged with
122
+ // "Update this when modifying state struct"
123
+
124
+ // Required by Datadog::Profiling::Collectors::Stack as a scratch buffer during sampling
125
+ ddog_prof_Location *locations;
126
+ uint16_t max_frames;
127
+ // Hashmap <Thread Object, per_thread_context>
128
+ // Note: Be very careful when mutating this map, as it gets read e.g. in the middle of GC and signal handlers.
129
+ st_table *hash_map_per_thread_context;
130
+ // Datadog::Profiling::StackRecorder instance
131
+ VALUE recorder_instance;
132
+ // If the tracer is available and enabled, this will be the fiber-local symbol for accessing its running context,
133
+ // to enable code hotspots and endpoint aggregation.
134
+ // When not available, this is set to MISSING_TRACER_CONTEXT_KEY.
135
+ ID tracer_context_key;
136
+ // Track how many regular samples we've taken. Does not include garbage collection samples.
137
+ // Currently **outside** of stats struct because we also use it to decide when to clean the contexts, and thus this
138
+ // is not (just) a stat.
139
+ unsigned int sample_count;
140
+ // Reusable array to get list of threads
141
+ VALUE thread_list_buffer;
142
+ // Used to omit endpoint names (retrieved from tracer) from collected data
143
+ bool endpoint_collection_enabled;
144
+ // Used to omit timestamps / timeline events from collected data
145
+ bool timeline_enabled;
146
+ // Used to control context collection
147
+ otel_context_enabled otel_context_enabled;
148
+ // Used to remember where otel context is being stored after we observe it the first time
149
+ otel_context_source otel_context_source;
150
+ // Used when calling monotonic_to_system_epoch_ns
151
+ monotonic_to_system_epoch_state time_converter_state;
152
+ // Used to identify the main thread, to give it a fallback name
153
+ VALUE main_thread;
154
+ // Used when extracting trace identifiers from otel spans. Lazily initialized.
155
+ // Qtrue serves as a marker we've not yet extracted it; when we try to extract it, we set it to an object if
156
+ // successful and Qnil if not.
157
+ VALUE otel_current_span_key;
158
+ // Used to enable native filenames in stack traces
159
+ bool native_filenames_enabled;
160
+ // Used to cache native filename lookup results (Map[void *function_pointer, char *filename])
161
+ st_table *native_filenames_cache;
162
+
163
+ struct stats {
164
+ // Track how many garbage collection samples we've taken.
165
+ unsigned int gc_samples;
166
+ // See thread_context_collector_on_gc_start for details
167
+ unsigned int gc_samples_missed_due_to_missing_context;
168
+ } stats;
169
+
170
+ struct {
171
+ unsigned long accumulated_cpu_time_ns;
172
+ unsigned long accumulated_wall_time_ns;
173
+
174
+ long wall_time_at_previous_gc_ns; // Will be INVALID_TIME unless there's accumulated time above
175
+ long wall_time_at_last_flushed_gc_event_ns; // Starts at 0 and then will always be valid
176
+ } gc_tracking;
177
+ } thread_context_collector_state;
178
+
179
+ // Tracks per-thread state
180
+ typedef struct {
181
+ sampling_buffer sampling_buffer;
182
+ char thread_id[THREAD_ID_LIMIT_CHARS];
183
+ ddog_CharSlice thread_id_char_slice;
184
+ char thread_invoke_location[THREAD_INVOKE_LOCATION_LIMIT_CHARS];
185
+ ddog_CharSlice thread_invoke_location_char_slice;
186
+ thread_cpu_time_id thread_cpu_time_id;
187
+ long cpu_time_at_previous_sample_ns; // Can be INVALID_TIME until initialized or if getting it fails for another reason
188
+ long wall_time_at_previous_sample_ns; // Can be INVALID_TIME until initialized
189
+
190
+ struct {
191
+ // Both of these fields are set by on_gc_start and kept until on_gc_finish is called.
192
+ // Outside of this window, they will be INVALID_TIME.
193
+ long cpu_time_at_start_ns;
194
+ long wall_time_at_start_ns;
195
+ } gc_tracking;
196
+ } per_thread_context;
197
+
198
+ // Used to correlate profiles with traces
199
+ typedef struct {
200
+ bool valid;
201
+ uint64_t local_root_span_id;
202
+ uint64_t span_id;
203
+ VALUE trace_endpoint;
204
+ } trace_identifiers;
205
+
206
+ typedef struct {
207
+ VALUE span;
208
+ VALUE span_id;
209
+ VALUE trace_id;
210
+ } otel_span;
211
+
212
+ static void thread_context_collector_typed_data_mark(void *state_ptr);
213
+ static void thread_context_collector_typed_data_free(void *state_ptr);
214
+ static int hash_map_per_thread_context_mark(st_data_t key_thread, st_data_t value_thread_context, DDTRACE_UNUSED st_data_t _argument);
215
+ static int hash_map_per_thread_context_free_values(st_data_t _thread, st_data_t value_per_thread_context, st_data_t _argument);
216
+ static VALUE _native_new(VALUE klass);
217
+ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
218
+ static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE profiler_overhead_stack_thread, VALUE allow_exception);
219
+ static VALUE _native_on_gc_start(VALUE self, VALUE collector_instance);
220
+ static VALUE _native_on_gc_finish(VALUE self, VALUE collector_instance);
221
+ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE allow_exception);
222
+ static void update_metrics_and_sample(
223
+ thread_context_collector_state *state,
224
+ VALUE thread_being_sampled,
225
+ VALUE stack_from_thread,
226
+ per_thread_context *thread_context,
227
+ sampling_buffer* sampling_buffer,
228
+ long current_cpu_time_ns,
229
+ long current_monotonic_wall_time_ns
230
+ );
231
+ static void trigger_sample_for_thread(
232
+ thread_context_collector_state *state,
233
+ VALUE thread,
234
+ VALUE stack_from_thread,
235
+ per_thread_context *thread_context,
236
+ sampling_buffer* sampling_buffer,
237
+ sample_values values,
238
+ long current_monotonic_wall_time_ns,
239
+ ddog_CharSlice *ruby_vm_type,
240
+ ddog_CharSlice *class_name,
241
+ bool is_gvl_waiting_state,
242
+ bool is_safe_to_allocate_objects
243
+ );
244
+ static VALUE _native_thread_list(VALUE self);
245
+ static per_thread_context *get_or_create_context_for(VALUE thread, thread_context_collector_state *state);
246
+ static per_thread_context *get_context_for(VALUE thread, thread_context_collector_state *state);
247
+ static void initialize_context(VALUE thread, per_thread_context *thread_context, thread_context_collector_state *state);
248
+ static void free_context(per_thread_context* thread_context);
249
+ static VALUE _native_inspect(VALUE self, VALUE collector_instance);
250
+ static VALUE per_thread_context_st_table_as_ruby_hash(thread_context_collector_state *state);
251
+ static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash);
252
+ static VALUE stats_as_ruby_hash(thread_context_collector_state *state);
253
+ static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state);
254
+ static void remove_context_for_dead_threads(thread_context_collector_state *state);
255
+ static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, st_data_t _argument);
256
+ static VALUE _native_per_thread_context(VALUE self, VALUE collector_instance);
257
+ static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, long gc_start_time_ns, bool is_wall_time);
258
+ static long cpu_time_now_ns(per_thread_context *thread_context);
259
+ static long thread_id_for(VALUE thread);
260
+ static VALUE _native_stats(VALUE self, VALUE collector_instance);
261
+ static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
262
+ static void trace_identifiers_for(
263
+ thread_context_collector_state *state,
264
+ VALUE thread,
265
+ trace_identifiers *trace_identifiers_result,
266
+ bool is_safe_to_allocate_objects
267
+ );
268
+ static bool should_collect_resource(VALUE root_span);
269
+ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
270
+ static VALUE thread_list(thread_context_collector_state *state);
271
+ static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object);
272
+ static VALUE _native_new_empty_thread(VALUE self);
273
+ static ddog_CharSlice ruby_value_type_to_class_name(enum ruby_value_type type);
274
+ static void ddtrace_otel_trace_identifiers_for(
275
+ thread_context_collector_state *state,
276
+ VALUE *active_trace,
277
+ VALUE *root_span,
278
+ VALUE *numeric_span_id,
279
+ VALUE active_span,
280
+ VALUE otel_values,
281
+ bool is_safe_to_allocate_objects
282
+ );
283
+ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples);
284
+ static bool handle_gvl_waiting(
285
+ thread_context_collector_state *state,
286
+ VALUE thread_being_sampled,
287
+ VALUE stack_from_thread,
288
+ per_thread_context *thread_context,
289
+ sampling_buffer* sampling_buffer,
290
+ long current_cpu_time_ns
291
+ );
292
+ static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread);
293
+ static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread);
294
+ static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread);
295
+ static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
296
+ static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns);
297
+ static void otel_without_ddtrace_trace_identifiers_for(
298
+ thread_context_collector_state *state,
299
+ VALUE thread,
300
+ trace_identifiers *trace_identifiers_result,
301
+ bool is_safe_to_allocate_objects
302
+ );
303
+ static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
304
+ static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
305
+ static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
306
+ static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
307
+ static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
308
+
309
+ void collectors_thread_context_init(VALUE profiling_module) {
310
+ VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
311
+ VALUE collectors_thread_context_class = rb_define_class_under(collectors_module, "ThreadContext", rb_cObject);
312
+ // Hosts methods used for testing the native code using RSpec
313
+ VALUE testing_module = rb_define_module_under(collectors_thread_context_class, "Testing");
314
+
315
+ // Instances of the ThreadContext class are "TypedData" objects.
316
+ // "TypedData" objects are special objects in the Ruby VM that can wrap C structs.
317
+ // In this case, it wraps the thread_context_collector_state.
318
+ //
319
+ // Because Ruby doesn't know how to initialize native-level structs, we MUST override the allocation function for objects
320
+ // of this class so that we can manage this part. Not overriding or disabling the allocation function is a common
321
+ // gotcha for "TypedData" objects that can very easily lead to VM crashes, see for instance
322
+ // https://bugs.ruby-lang.org/issues/18007 for a discussion around this.
323
+ rb_define_alloc_func(collectors_thread_context_class, _native_new);
324
+
325
+ rb_define_singleton_method(collectors_thread_context_class, "_native_initialize", _native_initialize, -1);
326
+ rb_define_singleton_method(collectors_thread_context_class, "_native_inspect", _native_inspect, 1);
327
+ rb_define_singleton_method(collectors_thread_context_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
328
+ rb_define_singleton_method(testing_module, "_native_sample", _native_sample, 3);
329
+ rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
330
+ rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
331
+ rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
332
+ rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 2);
333
+ rb_define_singleton_method(testing_module, "_native_thread_list", _native_thread_list, 0);
334
+ rb_define_singleton_method(testing_module, "_native_per_thread_context", _native_per_thread_context, 1);
335
+ rb_define_singleton_method(testing_module, "_native_stats", _native_stats, 1);
336
+ rb_define_singleton_method(testing_module, "_native_gc_tracking", _native_gc_tracking, 1);
337
+ rb_define_singleton_method(testing_module, "_native_new_empty_thread", _native_new_empty_thread, 0);
338
+ rb_define_singleton_method(testing_module, "_native_sample_skipped_allocation_samples", _native_sample_skipped_allocation_samples, 2);
339
+ rb_define_singleton_method(testing_module, "_native_system_epoch_time_now_ns", _native_system_epoch_time_now_ns, 1);
340
+ rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler, 1);
341
+ #ifndef NO_GVL_INSTRUMENTATION
342
+ rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
343
+ rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
344
+ rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 1);
345
+ rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 2);
346
+ rb_define_singleton_method(testing_module, "_native_apply_delta_to_cpu_time_at_previous_sample_ns", _native_apply_delta_to_cpu_time_at_previous_sample_ns, 3);
347
+ #endif
348
+
349
+ at_active_span_id = rb_intern_const("@active_span");
350
+ at_active_trace_id = rb_intern_const("@active_trace");
351
+ at_id_id = rb_intern_const("@id");
352
+ at_resource_id = rb_intern_const("@resource");
353
+ at_root_span_id = rb_intern_const("@root_span");
354
+ at_type_id = rb_intern_const("@type");
355
+ at_otel_values_id = rb_intern_const("@otel_values");
356
+ at_parent_span_id_id = rb_intern_const("@parent_span_id");
357
+ at_datadog_trace_id = rb_intern_const("@datadog_trace");
358
+ at_span_id_id = rb_intern_const("@span_id");
359
+ at_trace_id_id = rb_intern_const("@trace_id");
360
+ at_entries_id = rb_intern_const("@entries");
361
+ at_context_id = rb_intern_const("@context");
362
+ at_kind_id = rb_intern_const("@kind");
363
+ at_name_id = rb_intern_const("@name");
364
+ server_id = rb_intern_const("server");
365
+ otel_context_storage_id = rb_intern_const("__opentelemetry_context_storage__");
366
+ otel_fiber_context_storage_id = rb_intern_const("@opentelemetry_context");
367
+
368
+ #ifndef NO_GVL_INSTRUMENTATION
369
+ // This will raise if Ruby already ran out of thread-local keys
370
+ gvl_profiling_init();
371
+ #endif
372
+
373
+ gc_profiling_init();
374
+ }
375
+
376
+ // This structure is used to define a Ruby object that stores a pointer to a thread_context_collector_state
377
+ // See also https://github.com/ruby/ruby/blob/master/doc/extension.rdoc for how this works
378
+ static const rb_data_type_t thread_context_collector_typed_data = {
379
+ .wrap_struct_name = "Datadog::Profiling::Collectors::ThreadContext",
380
+ .function = {
381
+ .dmark = thread_context_collector_typed_data_mark,
382
+ .dfree = thread_context_collector_typed_data_free,
383
+ .dsize = NULL, // We don't track profile memory usage (although it'd be cool if we did!)
384
+ //.dcompact = NULL, // FIXME: Add support for compaction
385
+ },
386
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
387
+ };
388
+
389
+ // This function is called by the Ruby GC to give us a chance to mark any Ruby objects that we're holding on to,
390
+ // so that they don't get garbage collected
391
+ static void thread_context_collector_typed_data_mark(void *state_ptr) {
392
+ thread_context_collector_state *state = (thread_context_collector_state *) state_ptr;
393
+
394
+ // Update this when modifying state struct
395
+ rb_gc_mark(state->recorder_instance);
396
+ st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_mark, 0 /* unused */);
397
+ rb_gc_mark(state->thread_list_buffer);
398
+ rb_gc_mark(state->main_thread);
399
+ rb_gc_mark(state->otel_current_span_key);
400
+ }
401
+
402
+ static void thread_context_collector_typed_data_free(void *state_ptr) {
403
+ thread_context_collector_state *state = (thread_context_collector_state *) state_ptr;
404
+
405
+ // Update this when modifying state struct
406
+
407
+ // Important: Remember that we're only guaranteed to see here what's been set in _native_new, aka
408
+ // pointers that have been set NULL there may still be NULL here.
409
+ if (state->locations != NULL) ruby_xfree(state->locations);
410
+
411
+ // Free each entry in the map
412
+ st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
413
+ // ...and then the map
414
+ st_free_table(state->hash_map_per_thread_context);
415
+
416
+ st_free_table(state->native_filenames_cache);
417
+
418
+ ruby_xfree(state);
419
+ }
420
+
421
+ // Mark Ruby thread references we keep as keys in hash_map_per_thread_context
422
+ static int hash_map_per_thread_context_mark(st_data_t key_thread, st_data_t value_thread_context, DDTRACE_UNUSED st_data_t _argument) {
423
+ VALUE thread = (VALUE) key_thread;
424
+ per_thread_context *thread_context = (per_thread_context *) value_thread_context;
425
+
426
+ rb_gc_mark(thread);
427
+ if (sampling_buffer_needs_marking(&thread_context->sampling_buffer)) {
428
+ sampling_buffer_mark(&thread_context->sampling_buffer);
429
+ }
430
+
431
+ return ST_CONTINUE;
432
+ }
433
+
434
+ // Used to clear each of the per_thread_contexts inside the hash_map_per_thread_context
435
+ static int hash_map_per_thread_context_free_values(DDTRACE_UNUSED st_data_t _thread, st_data_t value_per_thread_context, DDTRACE_UNUSED st_data_t _argument) {
436
+ per_thread_context *thread_context = (per_thread_context*) value_per_thread_context;
437
+ free_context(thread_context);
438
+ return ST_CONTINUE;
439
+ }
440
+
441
+ static VALUE _native_new(VALUE klass) {
442
+ thread_context_collector_state *state = ruby_xcalloc(1, sizeof(thread_context_collector_state));
443
+
444
+ // Note: Any exceptions raised from this note until the TypedData_Wrap_Struct call will lead to the state memory
445
+ // being leaked.
446
+
447
+ // Update this when modifying state struct
448
+ state->locations = NULL;
449
+ state->max_frames = 0;
450
+ state->hash_map_per_thread_context =
451
+ // "numtable" is an awful name, but TL;DR it's what should be used when keys are `VALUE`s.
452
+ st_init_numtable();
453
+ state->recorder_instance = Qnil;
454
+ state->tracer_context_key = MISSING_TRACER_CONTEXT_KEY;
455
+ VALUE thread_list_buffer = rb_ary_new();
456
+ state->thread_list_buffer = thread_list_buffer;
457
+ state->endpoint_collection_enabled = true;
458
+ state->timeline_enabled = true;
459
+ state->native_filenames_enabled = false;
460
+ state->native_filenames_cache = st_init_numtable();
461
+ state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
462
+ state->otel_context_source = OTEL_CONTEXT_SOURCE_UNKNOWN;
463
+ state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
464
+ VALUE main_thread = rb_thread_main();
465
+ state->main_thread = main_thread;
466
+ state->otel_current_span_key = Qtrue;
467
+ state->gc_tracking.wall_time_at_previous_gc_ns = INVALID_TIME;
468
+ state->gc_tracking.wall_time_at_last_flushed_gc_event_ns = 0;
469
+
470
+ // Note: Remember to keep any new allocated objects that get stored in the state also on the stack + mark them with
471
+ // RB_GC_GUARD -- otherwise it's possible for a GC to run and
472
+ // since the instance representing the state does not yet exist, such objects will not get marked.
473
+
474
+ VALUE instance = TypedData_Wrap_Struct(klass, &thread_context_collector_typed_data, state);
475
+
476
+ RB_GC_GUARD(thread_list_buffer);
477
+ RB_GC_GUARD(main_thread); // Arguably not needed, but perhaps can be move in some future Ruby release?
478
+
479
+ return instance;
480
+ }
481
+
482
+ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
483
+ VALUE options;
484
+ rb_scan_args(argc, argv, "0:", &options);
485
+ if (options == Qnil) options = rb_hash_new();
486
+
487
+ VALUE self_instance = rb_hash_fetch(options, ID2SYM(rb_intern("self_instance")));
488
+ VALUE recorder_instance = rb_hash_fetch(options, ID2SYM(rb_intern("recorder")));
489
+ VALUE max_frames = rb_hash_fetch(options, ID2SYM(rb_intern("max_frames")));
490
+ VALUE tracer_context_key = rb_hash_fetch(options, ID2SYM(rb_intern("tracer_context_key")));
491
+ VALUE endpoint_collection_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("endpoint_collection_enabled")));
492
+ VALUE timeline_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("timeline_enabled")));
493
+ VALUE waiting_for_gvl_threshold_ns = rb_hash_fetch(options, ID2SYM(rb_intern("waiting_for_gvl_threshold_ns")));
494
+ VALUE otel_context_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("otel_context_enabled")));
495
+ VALUE native_filenames_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("native_filenames_enabled")));
496
+
497
+ ENFORCE_TYPE(max_frames, T_FIXNUM);
498
+ ENFORCE_BOOLEAN(endpoint_collection_enabled);
499
+ ENFORCE_BOOLEAN(timeline_enabled);
500
+ ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
501
+ ENFORCE_BOOLEAN(native_filenames_enabled);
502
+
503
+ thread_context_collector_state *state;
504
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
505
+
506
+ // Update this when modifying state struct
507
+ state->max_frames = sampling_buffer_check_max_frames(NUM2INT(max_frames));
508
+ state->locations = ruby_xcalloc(state->max_frames, sizeof(ddog_prof_Location));
509
+ // hash_map_per_thread_context is already initialized, nothing to do here
510
+ state->recorder_instance = enforce_recorder_instance(recorder_instance);
511
+ state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
512
+ state->timeline_enabled = (timeline_enabled == Qtrue);
513
+ state->native_filenames_enabled = (native_filenames_enabled == Qtrue);
514
+ if (otel_context_enabled == Qfalse || otel_context_enabled == Qnil) {
515
+ state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
516
+ } else if (otel_context_enabled == ID2SYM(rb_intern("only"))) {
517
+ state->otel_context_enabled = OTEL_CONTEXT_ENABLED_ONLY;
518
+ } else if (otel_context_enabled == ID2SYM(rb_intern("both"))) {
519
+ state->otel_context_enabled = OTEL_CONTEXT_ENABLED_BOTH;
520
+ } else {
521
+ rb_raise(rb_eArgError, "Unexpected value for otel_context_enabled: %+" PRIsVALUE, otel_context_enabled);
522
+ }
523
+
524
+ global_waiting_for_gvl_threshold_ns = NUM2UINT(waiting_for_gvl_threshold_ns);
525
+
526
+ if (RTEST(tracer_context_key)) {
527
+ ENFORCE_TYPE(tracer_context_key, T_SYMBOL);
528
+ // Note about rb_to_id and dynamic symbols: calling `rb_to_id` prevents symbols from ever being garbage collected.
529
+ // In this case, we can't really escape this because as of this writing, ruby master still calls `rb_to_id` inside
530
+ // the implementation of Thread#[]= so any symbol that gets used as a key there will already be prevented from GC.
531
+ state->tracer_context_key = rb_to_id(tracer_context_key);
532
+ }
533
+
534
+ return Qtrue;
535
+ }
536
+
537
+ // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
538
+ // It SHOULD NOT be used for other purposes.
539
+ static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE profiler_overhead_stack_thread, VALUE allow_exception) {
540
+ ENFORCE_BOOLEAN(allow_exception);
541
+
542
+ if (!is_thread_alive(profiler_overhead_stack_thread)) rb_raise(rb_eArgError, "Unexpected: profiler_overhead_stack_thread is not alive");
543
+
544
+ if (allow_exception == Qfalse) debug_enter_unsafe_context();
545
+
546
+ thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE), profiler_overhead_stack_thread);
547
+
548
+ if (allow_exception == Qfalse) debug_leave_unsafe_context();
549
+
550
+ return Qtrue;
551
+ }
552
+
553
+ // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
554
+ // It SHOULD NOT be used for other purposes.
555
+ static VALUE _native_on_gc_start(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
556
+ debug_enter_unsafe_context();
557
+
558
+ thread_context_collector_on_gc_start(collector_instance);
559
+
560
+ debug_leave_unsafe_context();
561
+
562
+ return Qtrue;
563
+ }
564
+
565
+ // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
566
+ // It SHOULD NOT be used for other purposes.
567
+ static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
568
+ debug_enter_unsafe_context();
569
+
570
+ (void) !thread_context_collector_on_gc_finish(collector_instance);
571
+
572
+ debug_leave_unsafe_context();
573
+
574
+ return Qtrue;
575
+ }
576
+
577
+ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE allow_exception) {
578
+ ENFORCE_BOOLEAN(allow_exception);
579
+
580
+ if (allow_exception == Qfalse) debug_enter_unsafe_context();
581
+
582
+ thread_context_collector_sample_after_gc(collector_instance);
583
+
584
+ if (allow_exception == Qfalse) debug_leave_unsafe_context();
585
+
586
+ return Qtrue;
587
+ }
588
+
589
+ // This function gets called from the Collectors::CpuAndWallTimeWorker to trigger the actual sampling.
590
+ //
591
+ // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
592
+ // Assumption 2: This function is allowed to raise exceptions. Caller is responsible for handling them, if needed.
593
+ // Assumption 3: This function IS NOT called from a signal handler. This function is not async-signal-safe.
594
+ // Assumption 4: This function IS NOT called in a reentrant way.
595
+ // Assumption 5: This function is called from the main Ractor (if Ruby has support for Ractors).
596
+ //
597
+ // The `profiler_overhead_stack_thread` is used to attribute the profiler overhead to a stack borrowed from a different thread
598
+ // (belonging to ddtrace), so that the overhead is visible in the profile rather than blamed on user code.
599
+ void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns, VALUE profiler_overhead_stack_thread) {
600
+ thread_context_collector_state *state;
601
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
602
+
603
+ VALUE current_thread = rb_thread_current();
604
+ per_thread_context *current_thread_context = get_or_create_context_for(current_thread, state);
605
+ long cpu_time_at_sample_start_for_current_thread = cpu_time_now_ns(current_thread_context);
606
+
607
+ VALUE threads = thread_list(state);
608
+
609
+ const long thread_count = RARRAY_LEN(threads);
610
+ for (long i = 0; i < thread_count; i++) {
611
+ VALUE thread = RARRAY_AREF(threads, i);
612
+ per_thread_context *thread_context = get_or_create_context_for(thread, state);
613
+
614
+ // We account for cpu-time for the current thread in a different way -- we use the cpu-time at sampling start, to avoid
615
+ // blaming the time the profiler took on whatever's running on the thread right now
616
+ long current_cpu_time_ns = thread != current_thread ? cpu_time_now_ns(thread_context) : cpu_time_at_sample_start_for_current_thread;
617
+
618
+ update_metrics_and_sample(
619
+ state,
620
+ /* thread_being_sampled: */ thread,
621
+ /* stack_from_thread: */ thread,
622
+ thread_context,
623
+ &thread_context->sampling_buffer,
624
+ current_cpu_time_ns,
625
+ current_monotonic_wall_time_ns
626
+ );
627
+ }
628
+
629
+ state->sample_count++;
630
+
631
+ // TODO: This seems somewhat overkill and inefficient to do often; right now we just do it every few samples
632
+ // but there's probably a better way to do this if we actually track when threads finish
633
+ if (state->sample_count % 100 == 0) remove_context_for_dead_threads(state);
634
+
635
+ update_metrics_and_sample(
636
+ state,
637
+ /* thread_being_sampled: */ current_thread,
638
+ /* stack_from_thread: */ profiler_overhead_stack_thread,
639
+ current_thread_context,
640
+ // Here we use the overhead thread's sampling buffer so as to not invalidate the cache in the buffer of the thread being sampled
641
+ &get_or_create_context_for(profiler_overhead_stack_thread, state)->sampling_buffer,
642
+ cpu_time_now_ns(current_thread_context),
643
+ monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
644
+ );
645
+ }
646
+
647
+ static void update_metrics_and_sample(
648
+ thread_context_collector_state *state,
649
+ VALUE thread_being_sampled,
650
+ VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
651
+ per_thread_context *thread_context,
652
+ sampling_buffer* sampling_buffer,
653
+ long current_cpu_time_ns,
654
+ long current_monotonic_wall_time_ns
655
+ ) {
656
+ bool is_gvl_waiting_state =
657
+ handle_gvl_waiting(state, thread_being_sampled, stack_from_thread, thread_context, sampling_buffer, current_cpu_time_ns);
658
+
659
+ // Don't assign/update cpu during "Waiting for GVL"
660
+ long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_time_since_previous_sample(
661
+ &thread_context->cpu_time_at_previous_sample_ns,
662
+ current_cpu_time_ns,
663
+ thread_context->gc_tracking.cpu_time_at_start_ns,
664
+ IS_NOT_WALL_TIME
665
+ );
666
+
667
+ long wall_time_elapsed_ns = update_time_since_previous_sample(
668
+ &thread_context->wall_time_at_previous_sample_ns,
669
+ current_monotonic_wall_time_ns,
670
+ // We explicitly pass in `INVALID_TIME` as an argument for `gc_start_time_ns` here because we don't want wall-time
671
+ // accounting to change during GC.
672
+ // E.g. if 60 seconds pass in the real world, 60 seconds of wall-time are recorded, regardless of the thread doing
673
+ // GC or not.
674
+ INVALID_TIME,
675
+ IS_WALL_TIME
676
+ );
677
+
678
+ // A thread enters "Waiting for GVL", well, as the name implies, without the GVL.
679
+ //
680
+ // As a consequence, it's possible that a thread enters "Waiting for GVL" in parallel with the current thread working
681
+ // on sampling, and thus for the `current_monotonic_wall_time_ns` (which is recorded at the start of sampling)
682
+ // to be < the time at which we started Waiting for GVL.
683
+ //
684
+ // All together, this means that when `handle_gvl_waiting` creates an extra sample (see comments on that function for
685
+ // what the extra sample is), it's possible that there's no more wall-time to be assigned.
686
+ // Thus, in this case, we don't want to produce a sample representing Waiting for GVL with a wall-time of 0, and
687
+ // thus we skip creating such a sample.
688
+ if (is_gvl_waiting_state && wall_time_elapsed_ns == 0) return;
689
+ // ...you may also wonder: is there any other situation where it makes sense to produce a sample with
690
+ // wall_time_elapsed_ns == 0? I believe that yes, because the sample still includes a timestamp and a stack, but we
691
+ // may revisit/change our minds on this in the future.
692
+
693
+ trigger_sample_for_thread(
694
+ state,
695
+ thread_being_sampled,
696
+ stack_from_thread,
697
+ thread_context,
698
+ sampling_buffer,
699
+ (sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = wall_time_elapsed_ns},
700
+ current_monotonic_wall_time_ns,
701
+ NULL,
702
+ NULL,
703
+ is_gvl_waiting_state,
704
+ /* is_safe_to_allocate_objects: */ true // We called from a context that's safe to run any regular code, including allocations
705
+ );
706
+ }
707
+
708
+ // This function gets called when Ruby is about to start running the Garbage Collector on the current thread.
709
+ // It updates the per_thread_context of the current thread to include the current cpu/wall times, to be used to later
710
+ // create an event including the cpu/wall time spent in garbage collector work.
711
+ //
712
+ // Safety: This function gets called while Ruby is doing garbage collection. While Ruby is doing garbage collection,
713
+ // *NO ALLOCATION* is allowed. This function, and any it calls must never trigger memory or object allocation.
714
+ // This includes exceptions and use of ruby_xcalloc (because xcalloc can trigger GC)!
715
+ //
716
+ // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
717
+ // Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
718
+ void thread_context_collector_on_gc_start(VALUE self_instance) {
719
+ thread_context_collector_state *state;
720
+ if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return;
721
+ // This should never fail the the above check passes
722
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
723
+
724
+ per_thread_context *thread_context = get_context_for(rb_thread_current(), state);
725
+
726
+ // If there was no previously-existing context for this thread, we won't allocate one (see safety). For now we just drop
727
+ // the GC sample, under the assumption that "a thread that is so new that we never sampled it even once before it triggers
728
+ // GC" is a rare enough case that we can just ignore it.
729
+ // We can always improve this later if we find that this happens often (and we have the counter to help us figure that out)!
730
+ if (thread_context == NULL) {
731
+ state->stats.gc_samples_missed_due_to_missing_context++;
732
+ return;
733
+ }
734
+
735
+ // Here we record the wall-time first and in on_gc_finish we record it second to try to avoid having wall-time be slightly < cpu-time
736
+ thread_context->gc_tracking.wall_time_at_start_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
737
+ thread_context->gc_tracking.cpu_time_at_start_ns = cpu_time_now_ns(thread_context);
738
+ }
739
+
740
+ // This function gets called when Ruby has finished running the Garbage Collector on the current thread.
741
+ // It records the cpu/wall-time observed during GC, which will be used to later
742
+ // create an event including the cpu/wall time spent from the start of garbage collector work until now.
743
+ //
744
+ // Safety: This function gets called while Ruby is doing garbage collection. While Ruby is doing garbage collection,
745
+ // *NO ALLOCATION* is allowed. This function, and any it calls must never trigger memory or object allocation.
746
+ // This includes exceptions and use of ruby_xcalloc (because xcalloc can trigger GC)!
747
+ //
748
+ // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
749
+ // Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
750
+ __attribute__((warn_unused_result))
751
+ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
752
+ thread_context_collector_state *state;
753
+ if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
754
+ // This should never fail the the above check passes
755
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
756
+
757
+ per_thread_context *thread_context = get_context_for(rb_thread_current(), state);
758
+
759
+ // If there was no previously-existing context for this thread, we won't allocate one (see safety). We keep a metric for
760
+ // how often this happens -- see on_gc_start.
761
+ if (thread_context == NULL) return false;
762
+
763
+ long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
764
+ long wall_time_at_start_ns = thread_context->gc_tracking.wall_time_at_start_ns;
765
+
766
+ if (cpu_time_at_start_ns == INVALID_TIME && wall_time_at_start_ns == INVALID_TIME) {
767
+ // If this happened, it means that on_gc_start was either never called for the thread OR it was called but no thread
768
+ // context existed at the time. The former can be the result of a bug, but since we can't distinguish them, we just
769
+ // do nothing.
770
+ return false;
771
+ }
772
+
773
+ // Mark thread as no longer in GC
774
+ thread_context->gc_tracking.cpu_time_at_start_ns = INVALID_TIME;
775
+ thread_context->gc_tracking.wall_time_at_start_ns = INVALID_TIME;
776
+
777
+ // Here we record the wall-time second and in on_gc_start we record it first to try to avoid having wall-time be slightly < cpu-time
778
+ long cpu_time_at_finish_ns = cpu_time_now_ns(thread_context);
779
+ long wall_time_at_finish_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
780
+
781
+ // If our end timestamp is not OK, we bail out
782
+ if (wall_time_at_finish_ns == 0) return false;
783
+
784
+ long gc_cpu_time_elapsed_ns = cpu_time_at_finish_ns - cpu_time_at_start_ns;
785
+ long gc_wall_time_elapsed_ns = wall_time_at_finish_ns - wall_time_at_start_ns;
786
+
787
+ // Wall-time can go backwards if the system clock gets changed (and we observed spurious jumps back on macOS as well)
788
+ // so let's ensure we don't get negative values for time deltas.
789
+ gc_cpu_time_elapsed_ns = long_max_of(gc_cpu_time_elapsed_ns, 0);
790
+ gc_wall_time_elapsed_ns = long_max_of(gc_wall_time_elapsed_ns, 0);
791
+
792
+ if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
793
+ state->gc_tracking.accumulated_cpu_time_ns = 0;
794
+ state->gc_tracking.accumulated_wall_time_ns = 0;
795
+ }
796
+
797
+ state->gc_tracking.accumulated_cpu_time_ns += gc_cpu_time_elapsed_ns;
798
+ state->gc_tracking.accumulated_wall_time_ns += gc_wall_time_elapsed_ns;
799
+ state->gc_tracking.wall_time_at_previous_gc_ns = wall_time_at_finish_ns;
800
+
801
+ // Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample
802
+ // We don't update the wall-time because we don't subtract the wall-time spent in GC (see call to
803
+ // `update_time_since_previous_sample` for wall-time in `update_metrics_and_sample`).
804
+ if (thread_context->cpu_time_at_previous_sample_ns != INVALID_TIME) {
805
+ thread_context->cpu_time_at_previous_sample_ns += gc_cpu_time_elapsed_ns;
806
+ }
807
+
808
+ // Let the caller know if it should schedule a flush or not. Returning true every time would cause a lot of overhead
809
+ // on the application (see GC tracking introduction at the top of the file), so instead we try to accumulate a few
810
+ // samples first.
811
+ bool over_flush_time_treshold =
812
+ (wall_time_at_finish_ns - state->gc_tracking.wall_time_at_last_flushed_gc_event_ns) >= TIME_BETWEEN_GC_EVENTS_NS;
813
+
814
+ if (over_flush_time_treshold) {
815
+ return true;
816
+ } else {
817
+ return gc_profiling_has_major_gc_finished();
818
+ }
819
+ }
820
+
821
+ // This function gets called after one or more GC work steps (calls to on_gc_start/on_gc_finish).
822
+ // It creates a new sample including the cpu and wall-time spent by the garbage collector work, and resets any
823
+ // GC-related tracking.
824
+ //
825
+ // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
826
+ // Assumption 2: This function is allowed to raise exceptions. Caller is responsible for handling them, if needed.
827
+ // Assumption 3: Unlike `on_gc_start` and `on_gc_finish`, this method is allowed to allocate memory as needed.
828
+ // Assumption 4: This function is called from the main Ractor (if Ruby has support for Ractors).
829
+ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
830
+ thread_context_collector_state *state;
831
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
832
+
833
+ if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
834
+ rb_raise(rb_eRuntimeError, "BUG: Unexpected call to sample_after_gc without valid GC information available");
835
+ }
836
+
837
+ int max_labels_needed_for_gc = 7; // Magic number gets validated inside gc_profiling_set_metadata
838
+ ddog_prof_Label labels[max_labels_needed_for_gc];
839
+ uint8_t label_pos = gc_profiling_set_metadata(labels, max_labels_needed_for_gc);
840
+
841
+ ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = label_pos};
842
+
843
+ // The end_timestamp_ns is treated specially by libdatadog and that's why it's not added as a ddog_prof_Label
844
+ int64_t end_timestamp_ns = 0;
845
+
846
+ if (state->timeline_enabled) {
847
+ end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, state->gc_tracking.wall_time_at_previous_gc_ns);
848
+ }
849
+
850
+ record_placeholder_stack(
851
+ state->recorder_instance,
852
+ (sample_values) {
853
+ // This event gets both a regular cpu/wall-time duration, as a normal cpu/wall-time sample would, as well as a
854
+ // timeline duration.
855
+ // This is done to enable two use-cases:
856
+ // * regular cpu/wall-time makes this event show up as a regular stack in the flamegraph
857
+ // * the timeline duration is used when the event shows up in the timeline
858
+ .cpu_time_ns = state->gc_tracking.accumulated_cpu_time_ns,
859
+ .cpu_or_wall_samples = 1,
860
+ .wall_time_ns = state->gc_tracking.accumulated_wall_time_ns,
861
+ .timeline_wall_time_ns = state->gc_tracking.accumulated_wall_time_ns,
862
+ },
863
+ (sample_labels) {.labels = slice_labels, .state_label = NULL, .end_timestamp_ns = end_timestamp_ns},
864
+ DDOG_CHARSLICE_C("Garbage Collection")
865
+ );
866
+
867
+ state->gc_tracking.wall_time_at_last_flushed_gc_event_ns = state->gc_tracking.wall_time_at_previous_gc_ns;
868
+ state->gc_tracking.wall_time_at_previous_gc_ns = INVALID_TIME;
869
+
870
+ state->stats.gc_samples++;
871
+
872
+ // Let recorder do any cleanup/updates it requires after a GC step.
873
+ recorder_after_gc_step(state->recorder_instance);
874
+
875
+ // Return a VALUE to make it easier to call this function from Ruby APIs that expect a return value (such as rb_rescue2)
876
+ return Qnil;
877
+ }
878
+
879
+ static void trigger_sample_for_thread(
880
+ thread_context_collector_state *state,
881
+ VALUE thread,
882
+ VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
883
+ per_thread_context *thread_context,
884
+ sampling_buffer* sampling_buffer,
885
+ sample_values values,
886
+ long current_monotonic_wall_time_ns,
887
+ // These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
888
+ ddog_CharSlice *ruby_vm_type,
889
+ ddog_CharSlice *class_name,
890
+ bool is_gvl_waiting_state,
891
+ // If the Ruby VM is at a state that can allocate objects safely, or not. Added for allocation profiling: we're not
892
+ // allowed to allocate objects (or raise exceptions) when inside the NEWOBJ tracepoint.
893
+ bool is_safe_to_allocate_objects
894
+ ) {
895
+ int max_label_count =
896
+ 1 + // thread id
897
+ 1 + // thread name
898
+ 1 + // profiler overhead
899
+ 2 + // ruby vm type and allocation class
900
+ 1 + // state (only set for cpu/wall-time samples)
901
+ 2; // local root span id and span id
902
+ ddog_prof_Label labels[max_label_count];
903
+ int label_pos = 0;
904
+
905
+ labels[label_pos++] = (ddog_prof_Label) {
906
+ .key = DDOG_CHARSLICE_C("thread id"),
907
+ .str = thread_context->thread_id_char_slice
908
+ };
909
+
910
+ VALUE thread_name = thread_name_for(thread);
911
+ if (thread_name != Qnil) {
912
+ labels[label_pos++] = (ddog_prof_Label) {
913
+ .key = DDOG_CHARSLICE_C("thread name"),
914
+ .str = char_slice_from_ruby_string(thread_name)
915
+ };
916
+ } else if (thread == state->main_thread) { // Threads are often not named, but we can have a nice fallback for this special thread
917
+ ddog_CharSlice main_thread_name = DDOG_CHARSLICE_C("main");
918
+ labels[label_pos++] = (ddog_prof_Label) {
919
+ .key = DDOG_CHARSLICE_C("thread name"),
920
+ .str = main_thread_name
921
+ };
922
+ } else {
923
+ // For other threads without name, we use the "invoke location" (first file:line of the block used to start the thread), if any.
924
+ // This is what Ruby shows in `Thread#to_s`.
925
+ labels[label_pos++] = (ddog_prof_Label) {
926
+ .key = DDOG_CHARSLICE_C("thread name"),
927
+ .str = thread_context->thread_invoke_location_char_slice // This is an empty string if no invoke location was available
928
+ };
929
+ }
930
+
931
+ trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
932
+ trace_identifiers_for(state, thread, &trace_identifiers_result, is_safe_to_allocate_objects);
933
+
934
+ if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
935
+ // If we couldn't get something with ddtrace, let's see if we can get some trace identifiers from opentelemetry directly
936
+ otel_without_ddtrace_trace_identifiers_for(state, thread, &trace_identifiers_result, is_safe_to_allocate_objects);
937
+ }
938
+
939
+ if (trace_identifiers_result.valid) {
940
+ labels[label_pos++] = (ddog_prof_Label) {.key = DDOG_CHARSLICE_C("local root span id"), .num = trace_identifiers_result.local_root_span_id};
941
+ labels[label_pos++] = (ddog_prof_Label) {.key = DDOG_CHARSLICE_C("span id"), .num = trace_identifiers_result.span_id};
942
+
943
+ if (trace_identifiers_result.trace_endpoint != Qnil) {
944
+ // The endpoint gets recorded in a different way because it is mutable in the tracer and can change during a
945
+ // trace.
946
+ //
947
+ // Instead of each sample for the same local_root_span_id getting a potentially-different endpoint,
948
+ // `record_endpoint` (via libdatadog) keeps a list of local_root_span_id values and their most-recently-seen
949
+ // endpoint values, and at serialization time the most-recently-seen endpoint is applied to all relevant samples.
950
+ //
951
+ // This is why the endpoint is not directly added in this function to the labels array, although it will later
952
+ // show up in the array in the output pprof.
953
+ record_endpoint(
954
+ state->recorder_instance,
955
+ trace_identifiers_result.local_root_span_id,
956
+ char_slice_from_ruby_string(trace_identifiers_result.trace_endpoint)
957
+ );
958
+ }
959
+ }
960
+
961
+ if (thread != stack_from_thread) {
962
+ labels[label_pos++] = (ddog_prof_Label) {
963
+ .key = DDOG_CHARSLICE_C("profiler overhead"),
964
+ .num = 1
965
+ };
966
+ }
967
+
968
+ if (ruby_vm_type != NULL) {
969
+ labels[label_pos++] = (ddog_prof_Label) {
970
+ .key = DDOG_CHARSLICE_C("ruby vm type"),
971
+ .str = *ruby_vm_type
972
+ };
973
+ }
974
+
975
+ if (class_name != NULL) {
976
+ labels[label_pos++] = (ddog_prof_Label) {
977
+ .key = DDOG_CHARSLICE_C("allocation class"),
978
+ .str = *class_name
979
+ };
980
+ }
981
+
982
+ // This label is handled specially:
983
+ // 1. It's only set for cpu/wall-time samples
984
+ // 2. We set it here to its default state of "unknown", but the `Collectors::Stack` may choose to override it with
985
+ // something more interesting.
986
+ ddog_prof_Label *state_label = NULL;
987
+ if (values.cpu_or_wall_samples > 0) {
988
+ state_label = &labels[label_pos++];
989
+ *state_label = (ddog_prof_Label) {
990
+ .key = DDOG_CHARSLICE_C("state"),
991
+ .str = DDOG_CHARSLICE_C("unknown"),
992
+ .num = 0, // This shouldn't be needed but the tracer-2.7 docker image ships a buggy gcc that complains about this
993
+ };
994
+ }
995
+
996
+ // The number of times `label_pos++` shows up in this function needs to match `max_label_count`. To avoid "oops I
997
+ // forgot to update max_label_count" in the future, we've also added this validation.
998
+ // @ivoanjo: I wonder if C compilers are smart enough to statically prove this check never triggers unless someone
999
+ // changes the code erroneously and remove it entirely?
1000
+ if (label_pos > max_label_count) {
1001
+ rb_raise(rb_eRuntimeError, "BUG: Unexpected label_pos (%d) > max_label_count (%d)", label_pos, max_label_count);
1002
+ }
1003
+
1004
+ ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = label_pos};
1005
+
1006
+ // The end_timestamp_ns is treated specially by libdatadog and that's why it's not added as a ddog_prof_Label
1007
+ int64_t end_timestamp_ns = 0;
1008
+ if (state->timeline_enabled && current_monotonic_wall_time_ns != INVALID_TIME) {
1009
+ end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, current_monotonic_wall_time_ns);
1010
+ }
1011
+
1012
+ sample_thread(
1013
+ stack_from_thread,
1014
+ sampling_buffer,
1015
+ state->recorder_instance,
1016
+ values,
1017
+ (sample_labels) {
1018
+ .labels = slice_labels,
1019
+ .state_label = state_label,
1020
+ .end_timestamp_ns = end_timestamp_ns,
1021
+ .is_gvl_waiting_state = is_gvl_waiting_state,
1022
+ },
1023
+ state->native_filenames_enabled,
1024
+ state->native_filenames_cache
1025
+ );
1026
+ }
1027
+
1028
+ // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
1029
+ // It SHOULD NOT be used for other purposes.
1030
+ static VALUE _native_thread_list(DDTRACE_UNUSED VALUE _self) {
1031
+ VALUE result = rb_ary_new();
1032
+
1033
+ debug_enter_unsafe_context();
1034
+
1035
+ ddtrace_thread_list(result);
1036
+
1037
+ debug_leave_unsafe_context();
1038
+
1039
+ return result;
1040
+ }
1041
+
1042
+ static per_thread_context *get_or_create_context_for(VALUE thread, thread_context_collector_state *state) {
1043
+ per_thread_context* thread_context = NULL;
1044
+ st_data_t value_context = 0;
1045
+
1046
+ if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
1047
+ thread_context = (per_thread_context*) value_context;
1048
+ } else {
1049
+ thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
1050
+ initialize_context(thread, thread_context, state);
1051
+ st_insert(state->hash_map_per_thread_context, (st_data_t) thread, (st_data_t) thread_context);
1052
+ }
1053
+
1054
+ return thread_context;
1055
+ }
1056
+
1057
+ static per_thread_context *get_context_for(VALUE thread, thread_context_collector_state *state) {
1058
+ per_thread_context* thread_context = NULL;
1059
+ st_data_t value_context = 0;
1060
+
1061
+ if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
1062
+ thread_context = (per_thread_context*) value_context;
1063
+ }
1064
+
1065
+ return thread_context;
1066
+ }
1067
+
1068
+ #define LOGGING_GEM_PATH "/lib/logging/diagnostic_context.rb"
1069
+
1070
+ // The `logging` gem monkey patches thread creation, which makes the `invoke_location_for` useless, since every thread
1071
+ // will point to the `logging` gem. When that happens, we avoid using the invoke location.
1072
+ //
1073
+ // TODO: This approach is a bit brittle, since it matches on the specific gem path, and only works for the `logging`
1074
+ // gem.
1075
+ // In the future we should probably explore a more generic fix (e.g. using Thread.method(:new).source_location or
1076
+ // something like that to detect redefinition of the `Thread` methods). One difficulty of doing it is that we need
1077
+ // to either run Ruby code during sampling (not great), or otherwise use some of the VM private APIs to detect this.
1078
+ //
1079
+ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
1080
+ unsigned long logging_gem_path_len = strlen(LOGGING_GEM_PATH);
1081
+ char *invoke_file = StringValueCStr(invoke_file_location);
1082
+ unsigned long invoke_file_len = strlen(invoke_file);
1083
+
1084
+ if (invoke_file_len < logging_gem_path_len) return false;
1085
+
1086
+ return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
1087
+ }
1088
+
1089
+ static void initialize_context(VALUE thread, per_thread_context *thread_context, thread_context_collector_state *state) {
1090
+ sampling_buffer_initialize(&thread_context->sampling_buffer, state->max_frames, state->locations);
1091
+
1092
+ snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
1093
+ thread_context->thread_id_char_slice = (ddog_CharSlice) {.ptr = thread_context->thread_id, .len = strlen(thread_context->thread_id)};
1094
+
1095
+ int invoke_line_location;
1096
+ VALUE invoke_file_location = invoke_location_for(thread, &invoke_line_location);
1097
+ if (invoke_file_location != Qnil) {
1098
+ if (!is_logging_gem_monkey_patch(invoke_file_location)) {
1099
+ snprintf(
1100
+ thread_context->thread_invoke_location,
1101
+ THREAD_INVOKE_LOCATION_LIMIT_CHARS,
1102
+ "%s:%d",
1103
+ StringValueCStr(invoke_file_location),
1104
+ invoke_line_location
1105
+ );
1106
+ } else {
1107
+ snprintf(thread_context->thread_invoke_location, THREAD_INVOKE_LOCATION_LIMIT_CHARS, "%s", "(Unnamed thread)");
1108
+ }
1109
+ } else if (thread != state->main_thread) {
1110
+ // If the first function of a thread is native code, there won't be an invoke location, so we use this fallback.
1111
+ // NOTE: In the future, I wonder if we could take the pointer to the native function, and try to see if there's a native
1112
+ // symbol attached to it.
1113
+ snprintf(thread_context->thread_invoke_location, THREAD_INVOKE_LOCATION_LIMIT_CHARS, "%s", "(Unnamed thread from native code)");
1114
+ }
1115
+
1116
+ thread_context->thread_invoke_location_char_slice = (ddog_CharSlice) {
1117
+ .ptr = thread_context->thread_invoke_location,
1118
+ .len = strlen(thread_context->thread_invoke_location)
1119
+ };
1120
+
1121
+ thread_context->thread_cpu_time_id = thread_cpu_time_id_for(thread);
1122
+
1123
+ // These will get initialized during actual sampling
1124
+ thread_context->cpu_time_at_previous_sample_ns = INVALID_TIME;
1125
+ thread_context->wall_time_at_previous_sample_ns = INVALID_TIME;
1126
+
1127
+ // These will only be used during a GC operation
1128
+ thread_context->gc_tracking.cpu_time_at_start_ns = INVALID_TIME;
1129
+ thread_context->gc_tracking.wall_time_at_start_ns = INVALID_TIME;
1130
+
1131
+ #ifndef NO_GVL_INSTRUMENTATION
1132
+ // We use this special location to store data that can be accessed without any
1133
+ // kind of synchronization (e.g. by threads without the GVL).
1134
+ //
1135
+ // We set this marker here for two purposes:
1136
+ // * To make sure there's no stale data from a previous execution of the profiler.
1137
+ // * To mark threads that are actually being profiled
1138
+ //
1139
+ // (Setting this is potentially a race, but what we want is to avoid _stale_ data, so
1140
+ // if this gets set concurrently with context initialization, then such a value will belong
1141
+ // to the current profiler instance, so that's OK)
1142
+ gvl_profiling_state_thread_object_set(thread, GVL_WAITING_ENABLED_EMPTY);
1143
+ #endif
1144
+ }
1145
+
1146
+ static void free_context(per_thread_context* thread_context) {
1147
+ sampling_buffer_free(&thread_context->sampling_buffer);
1148
+ free(thread_context); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
1149
+ }
1150
+
1151
+ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
1152
+ thread_context_collector_state *state;
1153
+ TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1154
+
1155
+ VALUE result = rb_str_new2(" (native state)");
1156
+
1157
+ // Update this when modifying state struct
1158
+ rb_str_concat(result, rb_sprintf(" max_frames=%d", state->max_frames));
1159
+ rb_str_concat(result, rb_sprintf(" hash_map_per_thread_context=%"PRIsVALUE, per_thread_context_st_table_as_ruby_hash(state)));
1160
+ rb_str_concat(result, rb_sprintf(" recorder_instance=%"PRIsVALUE, state->recorder_instance));
1161
+ VALUE tracer_context_key = state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY ? Qnil : ID2SYM(state->tracer_context_key);
1162
+ rb_str_concat(result, rb_sprintf(" tracer_context_key=%+"PRIsVALUE, tracer_context_key));
1163
+ rb_str_concat(result, rb_sprintf(" sample_count=%u", state->sample_count));
1164
+ rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_as_ruby_hash(state)));
1165
+ rb_str_concat(result, rb_sprintf(" endpoint_collection_enabled=%"PRIsVALUE, state->endpoint_collection_enabled ? Qtrue : Qfalse));
1166
+ rb_str_concat(result, rb_sprintf(" timeline_enabled=%"PRIsVALUE, state->timeline_enabled ? Qtrue : Qfalse));
1167
+ rb_str_concat(result, rb_sprintf(" native_filenames_enabled=%"PRIsVALUE, state->native_filenames_enabled ? Qtrue : Qfalse));
1168
+ // Note: `st_table_size()` is available from Ruby 3.2+ but not before
1169
+ rb_str_concat(result, rb_sprintf(" native_filenames_cache_size=%zu", state->native_filenames_cache->num_entries));
1170
+ rb_str_concat(result, rb_sprintf(" otel_context_enabled=%d", state->otel_context_enabled));
1171
+ rb_str_concat(result, rb_sprintf(
1172
+ " time_converter_state={.system_epoch_ns_reference=%ld, .delta_to_epoch_ns=%ld}",
1173
+ state->time_converter_state.system_epoch_ns_reference,
1174
+ state->time_converter_state.delta_to_epoch_ns
1175
+ ));
1176
+ rb_str_concat(result, rb_sprintf(" main_thread=%"PRIsVALUE, state->main_thread));
1177
+ rb_str_concat(result, rb_sprintf(" gc_tracking=%"PRIsVALUE, gc_tracking_as_ruby_hash(state)));
1178
+ rb_str_concat(result, rb_sprintf(" otel_current_span_key=%"PRIsVALUE, state->otel_current_span_key));
1179
+ rb_str_concat(result, rb_sprintf(" global_waiting_for_gvl_threshold_ns=%u", global_waiting_for_gvl_threshold_ns));
1180
+
1181
+ return result;
1182
+ }
1183
+
1184
+ static VALUE per_thread_context_st_table_as_ruby_hash(thread_context_collector_state *state) {
1185
+ VALUE result = rb_hash_new();
1186
+ st_foreach(state->hash_map_per_thread_context, per_thread_context_as_ruby_hash, result);
1187
+ return result;
1188
+ }
1189
+
1190
+ static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash) {
1191
+ VALUE thread = (VALUE) key_thread;
1192
+ per_thread_context *thread_context = (per_thread_context*) value_context;
1193
+ VALUE result = (VALUE) result_hash;
1194
+ VALUE context_as_hash = rb_hash_new();
1195
+ rb_hash_aset(result, thread, context_as_hash);
1196
+
1197
+ VALUE arguments[] = {
1198
+ ID2SYM(rb_intern("thread_id")), /* => */ rb_str_new2(thread_context->thread_id),
1199
+ ID2SYM(rb_intern("thread_invoke_location")), /* => */ rb_str_new2(thread_context->thread_invoke_location),
1200
+ ID2SYM(rb_intern("thread_cpu_time_id_valid?")), /* => */ thread_context->thread_cpu_time_id.valid ? Qtrue : Qfalse,
1201
+ ID2SYM(rb_intern("thread_cpu_time_id")), /* => */ CLOCKID2NUM(thread_context->thread_cpu_time_id.clock_id),
1202
+ ID2SYM(rb_intern("cpu_time_at_previous_sample_ns")), /* => */ LONG2NUM(thread_context->cpu_time_at_previous_sample_ns),
1203
+ ID2SYM(rb_intern("wall_time_at_previous_sample_ns")), /* => */ LONG2NUM(thread_context->wall_time_at_previous_sample_ns),
1204
+
1205
+ ID2SYM(rb_intern("gc_tracking.cpu_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.cpu_time_at_start_ns),
1206
+ ID2SYM(rb_intern("gc_tracking.wall_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.wall_time_at_start_ns),
1207
+
1208
+ #ifndef NO_GVL_INSTRUMENTATION
1209
+ ID2SYM(rb_intern("gvl_waiting_at")), /* => */ LONG2NUM(gvl_profiling_state_thread_object_get(thread)),
1210
+ #endif
1211
+ };
1212
+ for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(context_as_hash, arguments[i], arguments[i+1]);
1213
+
1214
+ return ST_CONTINUE;
1215
+ }
1216
+
1217
+ static VALUE stats_as_ruby_hash(thread_context_collector_state *state) {
1218
+ // Update this when modifying state struct (stats inner struct)
1219
+ VALUE stats_as_hash = rb_hash_new();
1220
+ VALUE arguments[] = {
1221
+ ID2SYM(rb_intern("gc_samples")), /* => */ UINT2NUM(state->stats.gc_samples),
1222
+ ID2SYM(rb_intern("gc_samples_missed_due_to_missing_context")), /* => */ UINT2NUM(state->stats.gc_samples_missed_due_to_missing_context),
1223
+ };
1224
+ for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(stats_as_hash, arguments[i], arguments[i+1]);
1225
+ return stats_as_hash;
1226
+ }
1227
+
1228
+ static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state) {
1229
+ // Update this when modifying state struct (gc_tracking inner struct)
1230
+ VALUE result = rb_hash_new();
1231
+ VALUE arguments[] = {
1232
+ ID2SYM(rb_intern("accumulated_cpu_time_ns")), /* => */ ULONG2NUM(state->gc_tracking.accumulated_cpu_time_ns),
1233
+ ID2SYM(rb_intern("accumulated_wall_time_ns")), /* => */ ULONG2NUM(state->gc_tracking.accumulated_wall_time_ns),
1234
+ ID2SYM(rb_intern("wall_time_at_previous_gc_ns")), /* => */ LONG2NUM(state->gc_tracking.wall_time_at_previous_gc_ns),
1235
+ ID2SYM(rb_intern("wall_time_at_last_flushed_gc_event_ns")), /* => */ LONG2NUM(state->gc_tracking.wall_time_at_last_flushed_gc_event_ns),
1236
+ };
1237
+ for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(result, arguments[i], arguments[i+1]);
1238
+ return result;
1239
+ }
1240
+
1241
+ static void remove_context_for_dead_threads(thread_context_collector_state *state) {
1242
+ st_foreach(state->hash_map_per_thread_context, remove_if_dead_thread, 0 /* unused */);
1243
+ }
1244
+
1245
+ static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, DDTRACE_UNUSED st_data_t _argument) {
1246
+ VALUE thread = (VALUE) key_thread;
1247
+ per_thread_context* thread_context = (per_thread_context*) value_context;
1248
+
1249
+ if (is_thread_alive(thread)) return ST_CONTINUE;
1250
+
1251
+ free_context(thread_context);
1252
+ return ST_DELETE;
1253
+ }
1254
+
1255
+ // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
1256
+ // It SHOULD NOT be used for other purposes.
1257
+ //
1258
+ // Returns the whole contents of the per_thread_context structs being tracked.
1259
+ static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
1260
+ thread_context_collector_state *state;
1261
+ TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1262
+
1263
+ return per_thread_context_st_table_as_ruby_hash(state);
1264
+ }
1265
+
1266
+ static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, long gc_start_time_ns, bool is_wall_time) {
1267
+ // If we didn't have a time for the previous sample, we use the current one
1268
+ if (*time_at_previous_sample_ns == INVALID_TIME) *time_at_previous_sample_ns = current_time_ns;
1269
+
1270
+ bool is_thread_doing_gc = gc_start_time_ns != INVALID_TIME;
1271
+ long elapsed_time_ns = -1;
1272
+
1273
+ if (is_thread_doing_gc) {
1274
+ bool previous_sample_was_during_gc = gc_start_time_ns <= *time_at_previous_sample_ns;
1275
+
1276
+ if (previous_sample_was_during_gc) {
1277
+ elapsed_time_ns = 0; // No time to account for -- any time since the last sample is going to get assigned to GC separately
1278
+ } else {
1279
+ elapsed_time_ns = gc_start_time_ns - *time_at_previous_sample_ns; // Capture time between previous sample and start of GC only
1280
+ }
1281
+
1282
+ // Remaining time (from gc_start_time to current_time_ns) will be accounted for inside `sample_after_gc`
1283
+ *time_at_previous_sample_ns = gc_start_time_ns;
1284
+ } else {
1285
+ elapsed_time_ns = current_time_ns - *time_at_previous_sample_ns; // Capture all time since previous sample
1286
+ *time_at_previous_sample_ns = current_time_ns;
1287
+ }
1288
+
1289
+ if (elapsed_time_ns < 0) {
1290
+ if (is_wall_time) {
1291
+ // Wall-time can actually go backwards (e.g. when the system clock gets set) so we can't assume time going backwards
1292
+ // was a bug.
1293
+ // @ivoanjo: I've also observed time going backwards spuriously on macOS, see discussion on
1294
+ // https://github.com/DataDog/dd-trace-rb/pull/2336.
1295
+ elapsed_time_ns = 0;
1296
+ } else {
1297
+ // We don't expect non-wall time to go backwards, so let's flag this as a bug
1298
+ rb_raise(rb_eRuntimeError, "BUG: Unexpected negative elapsed_time_ns between samples");
1299
+ }
1300
+ }
1301
+
1302
+ return elapsed_time_ns;
1303
+ }
1304
+
1305
+ // Safety: This function is assumed never to raise exceptions by callers
1306
+ static long cpu_time_now_ns(per_thread_context *thread_context) {
1307
+ thread_cpu_time cpu_time = thread_cpu_time_for(thread_context->thread_cpu_time_id);
1308
+
1309
+ if (!cpu_time.valid) {
1310
+ // Invalidate previous state of the counter (if any), it's no longer accurate. We need to get two good reads
1311
+ // in a row to have an accurate delta.
1312
+ thread_context->cpu_time_at_previous_sample_ns = INVALID_TIME;
1313
+ return 0;
1314
+ }
1315
+
1316
+ return cpu_time.result_ns;
1317
+ }
1318
+
1319
+ static long thread_id_for(VALUE thread) {
1320
+ VALUE object_id = rb_obj_id(thread);
1321
+
1322
+ // The API docs for Ruby state that `rb_obj_id` COULD be a BIGNUM and that if you want to be really sure you don't
1323
+ // get a BIGNUM, then you should use `rb_memory_id`. But `rb_memory_id` is less interesting because it's less visible
1324
+ // at the user level than the result of calling `#object_id`.
1325
+ //
1326
+ // It also seems uncommon to me that we'd ever get a BIGNUM; on old Ruby versions (pre-GC compaction), the object id
1327
+ // was the pointer to the object, so that's not going to be a BIGNUM; on modern Ruby versions, Ruby keeps
1328
+ // a counter, and only increments it for objects for which `#object_id`/`rb_obj_id` is called (e.g. most objects
1329
+ // won't actually have an object id allocated).
1330
+ //
1331
+ // So, for now, let's simplify: we only support FIXNUMs, and we won't break if we get a BIGNUM; we just won't
1332
+ // record the thread_id (but samples will still be collected).
1333
+ return FIXNUM_P(object_id) ? FIX2LONG(object_id) : -1;
1334
+ }
1335
+
1336
+ VALUE enforce_thread_context_collector_instance(VALUE object) {
1337
+ ENFORCE_TYPED_DATA(object, &thread_context_collector_typed_data);
1338
+ return object;
1339
+ }
1340
+
1341
+ // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
1342
+ // It SHOULD NOT be used for other purposes.
1343
+ static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
1344
+ thread_context_collector_state *state;
1345
+ TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1346
+
1347
+ return stats_as_ruby_hash(state);
1348
+ }
1349
+
1350
+ // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
1351
+ // It SHOULD NOT be used for other purposes.
1352
+ static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
1353
+ thread_context_collector_state *state;
1354
+ TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1355
+
1356
+ return gc_tracking_as_ruby_hash(state);
1357
+ }
1358
+
1359
+ // Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
1360
+ static void trace_identifiers_for(
1361
+ thread_context_collector_state *state,
1362
+ VALUE thread,
1363
+ trace_identifiers *trace_identifiers_result,
1364
+ bool is_safe_to_allocate_objects
1365
+ ) {
1366
+ if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
1367
+ if (state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY) return;
1368
+
1369
+ VALUE current_context = rb_thread_local_aref(thread, state->tracer_context_key);
1370
+ if (current_context == Qnil) return;
1371
+
1372
+ VALUE active_trace = rb_ivar_get(current_context, at_active_trace_id /* @active_trace */);
1373
+ if (active_trace == Qnil) return;
1374
+
1375
+ VALUE root_span = rb_ivar_get(active_trace, at_root_span_id /* @root_span */);
1376
+ VALUE active_span = rb_ivar_get(active_trace, at_active_span_id /* @active_span */);
1377
+ // Note: On Ruby 3.x `rb_attr_get` is exactly the same as `rb_ivar_get`. For Ruby 2.x, the difference is that
1378
+ // `rb_ivar_get` can trigger "warning: instance variable @otel_values not initialized" if warnings are enabled and
1379
+ // opentelemetry is not in use, whereas `rb_attr_get` does the lookup without generating the warning.
1380
+ VALUE otel_values = rb_attr_get(active_trace, at_otel_values_id /* @otel_values */);
1381
+
1382
+ VALUE numeric_span_id = Qnil;
1383
+
1384
+ if (otel_values != Qnil) {
1385
+ ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values, is_safe_to_allocate_objects);
1386
+ }
1387
+
1388
+ if (root_span == Qnil || (active_span == Qnil && numeric_span_id == Qnil)) return;
1389
+
1390
+ VALUE numeric_local_root_span_id = rb_ivar_get(root_span, at_id_id /* @id */);
1391
+ if (active_span != Qnil && numeric_span_id == Qnil) numeric_span_id = rb_ivar_get(active_span, at_id_id /* @id */);
1392
+ if (numeric_local_root_span_id == Qnil || numeric_span_id == Qnil) return;
1393
+
1394
+ trace_identifiers_result->local_root_span_id = NUM2ULL(numeric_local_root_span_id);
1395
+ trace_identifiers_result->span_id = NUM2ULL(numeric_span_id);
1396
+
1397
+ trace_identifiers_result->valid = true;
1398
+
1399
+ if (!state->endpoint_collection_enabled || !should_collect_resource(root_span)) return;
1400
+
1401
+ VALUE trace_resource = rb_ivar_get(active_trace, at_resource_id /* @resource */);
1402
+ if (RB_TYPE_P(trace_resource, T_STRING)) {
1403
+ trace_identifiers_result->trace_endpoint = trace_resource;
1404
+ } else if (trace_resource == Qnil) {
1405
+ // Fall back to resource from span, if any
1406
+ trace_identifiers_result->trace_endpoint = rb_ivar_get(root_span, at_resource_id /* @resource */);
1407
+ }
1408
+ }
1409
+
1410
+ // We opt-in to collecting the resource for spans of types:
1411
+ // * 'web', for web requests
1412
+ // * 'proxy', used by the rack integration with request_queuing: true (e.g. also represents a web request)
1413
+ // * 'worker', used for sidekiq and similar background job processors
1414
+ //
1415
+ // Over time, this list may be expanded.
1416
+ // Resources MUST NOT include personal identifiable information (PII); this should not be the case with
1417
+ // ddtrace integrations, but worth mentioning just in case :)
1418
+ static bool should_collect_resource(VALUE root_span) {
1419
+ VALUE root_span_type = rb_ivar_get(root_span, at_type_id /* @type */);
1420
+ if (root_span_type == Qnil) return false;
1421
+ ENFORCE_TYPE(root_span_type, T_STRING);
1422
+
1423
+ long root_span_type_length = RSTRING_LEN(root_span_type);
1424
+ const char *root_span_type_value = StringValuePtr(root_span_type);
1425
+
1426
+ bool is_web_request =
1427
+ (root_span_type_length == strlen("web") && (memcmp("web", root_span_type_value, strlen("web")) == 0)) ||
1428
+ (root_span_type_length == strlen("proxy") && (memcmp("proxy", root_span_type_value, strlen("proxy")) == 0));
1429
+
1430
+ if (is_web_request) return true;
1431
+
1432
+ bool is_worker_request =
1433
+ (root_span_type_length == strlen("worker") && (memcmp("worker", root_span_type_value, strlen("worker")) == 0));
1434
+
1435
+ return is_worker_request;
1436
+ }
1437
+
1438
+ // After the Ruby VM forks, this method gets called in the child process to clean up any leftover state from the parent.
1439
+ //
1440
+ // Assumption: This method gets called BEFORE restarting profiling -- e.g. there are no components attempting to
1441
+ // trigger samples at the same time.
1442
+ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
1443
+ thread_context_collector_state *state;
1444
+ TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1445
+
1446
+ // Release all context memory before clearing the existing context
1447
+ st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
1448
+
1449
+ st_clear(state->hash_map_per_thread_context);
1450
+
1451
+ state->stats = (struct stats) {}; // Resets all stats back to zero
1452
+
1453
+ rb_funcall(state->recorder_instance, rb_intern("reset_after_fork"), 0);
1454
+
1455
+ return Qtrue;
1456
+ }
1457
+
1458
+ static VALUE thread_list(thread_context_collector_state *state) {
1459
+ VALUE result = state->thread_list_buffer;
1460
+ rb_ary_clear(result);
1461
+ ddtrace_thread_list(result);
1462
+ return result;
1463
+ }
1464
+
1465
+ // Inside a signal handler, we don't want to do the whole work of recording a sample, but we only record the stack of
1466
+ // the current thread.
1467
+ //
1468
+ // Assumptions for this function are same as for `thread_context_collector_sample` except that this function is
1469
+ // expected to be called from a signal handler and to be async-signal-safe.
1470
+ //
1471
+ // Also, no allocation (Ruby or malloc) can happen.
1472
+ bool thread_context_collector_prepare_sample_inside_signal_handler(VALUE self_instance) {
1473
+ thread_context_collector_state *state;
1474
+ if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
1475
+ // This should never fail if the above check passes
1476
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1477
+
1478
+ VALUE current_thread = rb_thread_current();
1479
+ per_thread_context *thread_context = get_context_for(current_thread, state);
1480
+ if (thread_context == NULL) return false;
1481
+
1482
+ return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
1483
+ }
1484
+
1485
+ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned int sample_weight, VALUE new_object) {
1486
+ thread_context_collector_state *state;
1487
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1488
+
1489
+ VALUE current_thread = rb_thread_current();
1490
+
1491
+ enum ruby_value_type type = rb_type(new_object);
1492
+
1493
+ // Tag samples with the VM internal types
1494
+ ddog_CharSlice ruby_vm_type = ruby_value_type_to_char_slice(type);
1495
+
1496
+ // Since this is stack allocated, be careful about moving it
1497
+ ddog_CharSlice class_name;
1498
+ char imemo_type[100];
1499
+
1500
+ if (
1501
+ type == RUBY_T_OBJECT ||
1502
+ type == RUBY_T_CLASS ||
1503
+ type == RUBY_T_MODULE ||
1504
+ type == RUBY_T_FLOAT ||
1505
+ type == RUBY_T_STRING ||
1506
+ type == RUBY_T_REGEXP ||
1507
+ type == RUBY_T_ARRAY ||
1508
+ type == RUBY_T_HASH ||
1509
+ type == RUBY_T_STRUCT ||
1510
+ type == RUBY_T_BIGNUM ||
1511
+ type == RUBY_T_FILE ||
1512
+ type == RUBY_T_DATA ||
1513
+ type == RUBY_T_MATCH ||
1514
+ type == RUBY_T_COMPLEX ||
1515
+ type == RUBY_T_RATIONAL ||
1516
+ type == RUBY_T_NIL ||
1517
+ type == RUBY_T_TRUE ||
1518
+ type == RUBY_T_FALSE ||
1519
+ type == RUBY_T_SYMBOL ||
1520
+ type == RUBY_T_FIXNUM
1521
+ ) {
1522
+ VALUE klass = rb_class_of(new_object);
1523
+
1524
+ // Ruby sometimes plays a bit fast and loose with some of its internal objects, e.g.
1525
+ // `rb_str_tmp_frozen_acquire` allocates a string with no class (klass=0).
1526
+ // Thus, we need to make sure there's actually a class before getting its name.
1527
+
1528
+ if (klass != 0) {
1529
+ const char *name = rb_class2name(klass);
1530
+ size_t name_length = name != NULL ? strlen(name) : 0;
1531
+
1532
+ if (name_length > 0) {
1533
+ class_name = (ddog_CharSlice) {.ptr = name, .len = name_length};
1534
+ } else {
1535
+ // @ivoanjo: I'm not sure this can ever happen, but just-in-case
1536
+ class_name = ruby_value_type_to_class_name(type);
1537
+ }
1538
+ } else {
1539
+ // Fallback for objects with no class. Objects with no class are a way for the Ruby VM to mark them
1540
+ // as internal objects; see rb_objspace_internal_object_p for details.
1541
+ class_name = ruby_value_type_to_class_name(type);
1542
+ }
1543
+ } else if (type == RUBY_T_IMEMO) {
1544
+ const char *imemo_string = imemo_kind(new_object);
1545
+ if (imemo_string != NULL) {
1546
+ snprintf(imemo_type, 100, "(VM Internal, T_IMEMO, %s)", imemo_string);
1547
+ class_name = (ddog_CharSlice) {.ptr = imemo_type, .len = strlen(imemo_type)};
1548
+ } else { // Ruby < 3
1549
+ class_name = DDOG_CHARSLICE_C("(VM Internal, T_IMEMO)");
1550
+ }
1551
+ } else {
1552
+ class_name = ruby_vm_type; // For other weird internal things we just use the VM type
1553
+ }
1554
+
1555
+ track_object(state->recorder_instance, new_object, sample_weight, class_name);
1556
+
1557
+ per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
1558
+
1559
+ trigger_sample_for_thread(
1560
+ state,
1561
+ /* thread: */ current_thread,
1562
+ /* stack_from_thread: */ current_thread,
1563
+ thread_context,
1564
+ &thread_context->sampling_buffer,
1565
+ (sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
1566
+ INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
1567
+ &ruby_vm_type,
1568
+ &class_name,
1569
+ /* is_gvl_waiting_state: */ false,
1570
+ /* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
1571
+ );
1572
+ }
1573
+
1574
+ // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
1575
+ // It SHOULD NOT be used for other purposes.
1576
+ static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object) {
1577
+ debug_enter_unsafe_context();
1578
+
1579
+ thread_context_collector_sample_allocation(collector_instance, NUM2UINT(sample_weight), new_object);
1580
+
1581
+ debug_leave_unsafe_context();
1582
+
1583
+ return Qtrue;
1584
+ }
1585
+
1586
+ static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) { return Qnil; }
1587
+
1588
+ // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
1589
+ // It SHOULD NOT be used for other purposes.
1590
+ // (It creates an empty native thread, so we can test our native thread naming fallback)
1591
+ static VALUE _native_new_empty_thread(DDTRACE_UNUSED VALUE self) {
1592
+ return rb_thread_create(new_empty_thread_inner, NULL);
1593
+ }
1594
+
1595
+ static ddog_CharSlice ruby_value_type_to_class_name(enum ruby_value_type type) {
1596
+ switch (type) {
1597
+ case(RUBY_T_OBJECT ): return DDOG_CHARSLICE_C("Object");
1598
+ case(RUBY_T_CLASS ): return DDOG_CHARSLICE_C("Class");
1599
+ case(RUBY_T_MODULE ): return DDOG_CHARSLICE_C("Module");
1600
+ case(RUBY_T_FLOAT ): return DDOG_CHARSLICE_C("Float");
1601
+ case(RUBY_T_STRING ): return DDOG_CHARSLICE_C("String");
1602
+ case(RUBY_T_REGEXP ): return DDOG_CHARSLICE_C("Regexp");
1603
+ case(RUBY_T_ARRAY ): return DDOG_CHARSLICE_C("Array");
1604
+ case(RUBY_T_HASH ): return DDOG_CHARSLICE_C("Hash");
1605
+ case(RUBY_T_STRUCT ): return DDOG_CHARSLICE_C("Struct");
1606
+ case(RUBY_T_BIGNUM ): return DDOG_CHARSLICE_C("Integer");
1607
+ case(RUBY_T_FILE ): return DDOG_CHARSLICE_C("File");
1608
+ case(RUBY_T_DATA ): return DDOG_CHARSLICE_C("(VM Internal, T_DATA)");
1609
+ case(RUBY_T_MATCH ): return DDOG_CHARSLICE_C("MatchData");
1610
+ case(RUBY_T_COMPLEX ): return DDOG_CHARSLICE_C("Complex");
1611
+ case(RUBY_T_RATIONAL): return DDOG_CHARSLICE_C("Rational");
1612
+ case(RUBY_T_NIL ): return DDOG_CHARSLICE_C("NilClass");
1613
+ case(RUBY_T_TRUE ): return DDOG_CHARSLICE_C("TrueClass");
1614
+ case(RUBY_T_FALSE ): return DDOG_CHARSLICE_C("FalseClass");
1615
+ case(RUBY_T_SYMBOL ): return DDOG_CHARSLICE_C("Symbol");
1616
+ case(RUBY_T_FIXNUM ): return DDOG_CHARSLICE_C("Integer");
1617
+ default: return DDOG_CHARSLICE_C("(VM Internal, Missing class)");
1618
+ }
1619
+ }
1620
+
1621
+ // Used to access OpenTelemetry::Trace.const_get(:CURRENT_SPAN_KEY). Will raise exceptions if it fails.
1622
+ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
1623
+ VALUE opentelemetry_module = rb_const_get(rb_cObject, rb_intern("OpenTelemetry"));
1624
+ ENFORCE_TYPE(opentelemetry_module, T_MODULE);
1625
+ VALUE trace_module = rb_const_get(opentelemetry_module, rb_intern("Trace"));
1626
+ ENFORCE_TYPE(trace_module, T_MODULE);
1627
+ return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
1628
+ }
1629
+
1630
+ static VALUE get_otel_current_span_key(thread_context_collector_state *state, bool is_safe_to_allocate_objects) {
1631
+ if (state->otel_current_span_key == Qtrue) { // Qtrue means we haven't tried to extract it yet
1632
+ if (!is_safe_to_allocate_objects) {
1633
+ // Calling read_otel_current_span_key_const below can trigger exceptions and arbitrary Ruby code running (e.g.
1634
+ // `const_missing`, etc). Not safe to call in this situation, so we just skip otel info for this sample.
1635
+ return Qnil;
1636
+ }
1637
+
1638
+ // If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
1639
+ // gets refactored, we should not fall on our face)
1640
+ VALUE span_key = rb_protect(read_otel_current_span_key_const, Qnil, NULL);
1641
+ rb_set_errinfo(Qnil); // **Clear any pending exception after ignoring it**
1642
+
1643
+ // Note that this gets set to Qnil if we failed to extract the correct value, and thus we won't try to extract it again
1644
+ state->otel_current_span_key = span_key;
1645
+ }
1646
+
1647
+ return state->otel_current_span_key;
1648
+ }
1649
+
1650
+ // This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
1651
+ // differently, and this codepath handles it.
1652
+ static void ddtrace_otel_trace_identifiers_for(
1653
+ thread_context_collector_state *state,
1654
+ VALUE *active_trace,
1655
+ VALUE *root_span,
1656
+ VALUE *numeric_span_id,
1657
+ VALUE active_span,
1658
+ VALUE otel_values,
1659
+ bool is_safe_to_allocate_objects
1660
+ ) {
1661
+ VALUE resolved_numeric_span_id =
1662
+ active_span == Qnil ?
1663
+ // For traces started from otel spans, the span id will be empty, and the @parent_span_id has the right value
1664
+ rb_ivar_get(*active_trace, at_parent_span_id_id /* @parent_span_id */) :
1665
+ // Regular span created by ddtrace
1666
+ rb_ivar_get(active_span, at_id_id /* @id */);
1667
+
1668
+ if (resolved_numeric_span_id == Qnil) return;
1669
+
1670
+ VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
1671
+ if (otel_current_span_key == Qnil) return;
1672
+ VALUE current_trace = *active_trace;
1673
+
1674
+ // ddtrace uses a different structure when spans are created from otel, where each otel span will have a unique ddtrace
1675
+ // trace and span representing it. Each ddtrace trace is then connected to the previous otel span, forming a linked
1676
+ // list. The local root span is going to be the trace/span we find at the end of this linked list.
1677
+ while (otel_values != Qnil) {
1678
+ VALUE otel_span = safely_lookup_hash_without_going_into_ruby_code(otel_values, otel_current_span_key);
1679
+ if (otel_span == Qnil) break;
1680
+ VALUE next_trace = rb_ivar_get(otel_span, at_datadog_trace_id);
1681
+ if (next_trace == Qnil) break;
1682
+
1683
+ current_trace = next_trace;
1684
+ otel_values = rb_ivar_get(current_trace, at_otel_values_id /* @otel_values */);
1685
+ }
1686
+
1687
+ // We found the last trace in the linked list. This contains the local root span
1688
+ VALUE resolved_root_span = rb_ivar_get(current_trace, at_root_span_id /* @root_span */);
1689
+ if (resolved_root_span == Qnil) return;
1690
+
1691
+ *root_span = resolved_root_span;
1692
+ *active_trace = current_trace;
1693
+ *numeric_span_id = resolved_numeric_span_id;
1694
+ }
1695
+
1696
+ void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples) {
1697
+ thread_context_collector_state *state;
1698
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1699
+
1700
+ ddog_prof_Label labels[] = {
1701
+ // Providing .num = 0 should not be needed but the tracer-2.7 docker image ships a buggy gcc that complains about this
1702
+ {.key = DDOG_CHARSLICE_C("thread id"), .str = DDOG_CHARSLICE_C("SS"), .num = 0},
1703
+ {.key = DDOG_CHARSLICE_C("thread name"), .str = DDOG_CHARSLICE_C("Skipped Samples"), .num = 0},
1704
+ {.key = DDOG_CHARSLICE_C("allocation class"), .str = DDOG_CHARSLICE_C("(Skipped Samples)"), .num = 0},
1705
+ };
1706
+ ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = sizeof(labels) / sizeof(labels[0])};
1707
+
1708
+ record_placeholder_stack(
1709
+ state->recorder_instance,
1710
+ (sample_values) {.alloc_samples = skipped_samples},
1711
+ (sample_labels) {
1712
+ .labels = slice_labels,
1713
+ .state_label = NULL,
1714
+ .end_timestamp_ns = 0, // For now we're not collecting timestamps for allocation events
1715
+ },
1716
+ DDOG_CHARSLICE_C("Skipped Samples")
1717
+ );
1718
+ }
1719
+
1720
+ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples) {
1721
+ debug_enter_unsafe_context();
1722
+
1723
+ thread_context_collector_sample_skipped_allocation_samples(collector_instance, NUM2UINT(skipped_samples));
1724
+
1725
+ debug_leave_unsafe_context();
1726
+
1727
+ return Qtrue;
1728
+ }
1729
+
1730
+ #ifndef NO_CURRENT_FIBER_FOR // Ruby 3.1+
1731
+ static VALUE otel_context_storage_for(thread_context_collector_state *state, VALUE thread) {
1732
+ if (state->otel_context_source == OTEL_CONTEXT_SOURCE_FIBER_IVAR) { // otel-api 1.5+
1733
+ VALUE current_fiber = current_fiber_for(thread);
1734
+ return current_fiber == Qnil ? Qnil : rb_ivar_get(current_fiber, otel_fiber_context_storage_id /* @opentelemetry_context */);
1735
+ }
1736
+
1737
+ if (state->otel_context_source == OTEL_CONTEXT_SOURCE_FIBER_LOCAL) { // otel-api < 1.5
1738
+ return rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
1739
+ }
1740
+
1741
+ // If we got here, it means we never observed a context being set. Let's probe which one to use.
1742
+ VALUE current_fiber = current_fiber_for(thread);
1743
+ if (current_fiber != Qnil) {
1744
+ VALUE context_storage = rb_ivar_get(current_fiber, otel_fiber_context_storage_id /* @opentelemetry_context */);
1745
+ if (context_storage != Qnil) {
1746
+ state->otel_context_source = OTEL_CONTEXT_SOURCE_FIBER_IVAR; // Remember for next time
1747
+ return context_storage;
1748
+ }
1749
+ } else {
1750
+ VALUE context_storage = rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
1751
+ if (context_storage != Qnil) {
1752
+ state->otel_context_source = OTEL_CONTEXT_SOURCE_FIBER_LOCAL; // Remember for next time
1753
+ return context_storage;
1754
+ }
1755
+ }
1756
+
1757
+ // There's no context storage attached to the current thread
1758
+ return Qnil;
1759
+ }
1760
+ #else
1761
+ static inline VALUE otel_context_storage_for(DDTRACE_UNUSED thread_context_collector_state *state, VALUE thread) {
1762
+ return rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
1763
+ }
1764
+ #endif
1765
+
1766
+ // This method differs from trace_identifiers_for/ddtrace_otel_trace_identifiers_for to support the situation where
1767
+ // the opentelemetry ruby library is being used for tracing AND the ddtrace tracing bits are not involved at all.
1768
+ //
1769
+ // Thus, in this case, we're directly reading from the opentelemetry stuff, which is different to how ddtrace tracing
1770
+ // does it.
1771
+ //
1772
+ // This is somewhat brittle: we're coupling on internal details of the opentelemetry gem to get what we need. In the
1773
+ // future maybe the otel ruby folks would be open to having a nice public way of getting this data that suits the
1774
+ // usecase of profilers.
1775
+ // Until then, the strategy below is to be extremely defensive, and if anything is out of place, we immediately return
1776
+ // and give up on getting trace data from opentelemetry. (Thus, worst case would be -- you upgrade opentelemetry and
1777
+ // profiling features relying on reading this data stop working, but you'll still get profiles and the app will be
1778
+ // otherwise undisturbed).
1779
+ //
1780
+ // Specifically, the way this works is:
1781
+ // 1. The latest entry in the opentelemetry context storage represents the current span (if any). We take the span id
1782
+ // and trace id from this span.
1783
+ // 2. To find the local root span id, we walk the context storage backwards from the current span, and find the earliest
1784
+ // entry in the context storage that has the same trace id as the current span; we use the found span as the local
1785
+ // root span id.
1786
+ // This matches the semantics of how ddtrace tracing creates a TraceOperation and assigns a local root span to it.
1787
+ static void otel_without_ddtrace_trace_identifiers_for(
1788
+ thread_context_collector_state *state,
1789
+ VALUE thread,
1790
+ trace_identifiers *trace_identifiers_result,
1791
+ bool is_safe_to_allocate_objects
1792
+ ) {
1793
+ VALUE context_storage = otel_context_storage_for(state, thread);
1794
+
1795
+ // If it exists, context_storage is expected to be an Array[OpenTelemetry::Context]
1796
+ if (context_storage == Qnil || !RB_TYPE_P(context_storage, T_ARRAY)) return;
1797
+
1798
+ VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
1799
+ if (otel_current_span_key == Qnil) return;
1800
+
1801
+ int active_context_index = RARRAY_LEN(context_storage) - 1;
1802
+ if (active_context_index < 0) return;
1803
+
1804
+ otel_span active_span = otel_span_from(rb_ary_entry(context_storage, active_context_index), otel_current_span_key);
1805
+ if (active_span.span == Qnil) return;
1806
+
1807
+ otel_span local_root_span = active_span;
1808
+
1809
+ // Now find the oldest span starting from the active span that still has the same trace id as the active span
1810
+ for (int i = active_context_index - 1; i >= 0; i--) {
1811
+ otel_span checking_span = otel_span_from(rb_ary_entry(context_storage, i), otel_current_span_key);
1812
+ if (checking_span.span == Qnil) return;
1813
+
1814
+ if (rb_str_equal(active_span.trace_id, checking_span.trace_id) == Qfalse) break;
1815
+
1816
+ local_root_span = checking_span;
1817
+ }
1818
+
1819
+ // Convert the span ids into uint64_t to match what the Datadog tracer does
1820
+ trace_identifiers_result->span_id = otel_span_id_to_uint(active_span.span_id);
1821
+ trace_identifiers_result->local_root_span_id = otel_span_id_to_uint(local_root_span.span_id);
1822
+
1823
+ if (trace_identifiers_result->span_id == 0 || trace_identifiers_result->local_root_span_id == 0) return;
1824
+
1825
+ trace_identifiers_result->valid = true;
1826
+
1827
+ if (!state->endpoint_collection_enabled) return;
1828
+
1829
+ VALUE root_span_type = rb_ivar_get(local_root_span.span, at_kind_id /* @kind */);
1830
+ // We filter out spans that don't have `kind: :server`
1831
+ if (root_span_type == Qnil || !RB_TYPE_P(root_span_type, T_SYMBOL) || !RB_STATIC_SYM_P(root_span_type) || SYM2ID(root_span_type) != server_id) return;
1832
+
1833
+ VALUE trace_resource = rb_ivar_get(local_root_span.span, at_name_id /* @name */);
1834
+ if (!RB_TYPE_P(trace_resource, T_STRING)) return;
1835
+
1836
+ trace_identifiers_result->trace_endpoint = trace_resource;
1837
+ }
1838
+
1839
+ static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key) {
1840
+ otel_span failed = {.span = Qnil, .span_id = Qnil, .trace_id = Qnil};
1841
+
1842
+ if (otel_context == Qnil) return failed;
1843
+
1844
+ VALUE context_entries = rb_ivar_get(otel_context, at_entries_id /* @entries */);
1845
+ if (context_entries == Qnil || !RB_TYPE_P(context_entries, T_HASH)) return failed;
1846
+
1847
+ // If it exists, context_entries is expected to be a Hash[OpenTelemetry::Context::Key, OpenTelemetry::Trace::Span]
1848
+ VALUE span = safely_lookup_hash_without_going_into_ruby_code(context_entries, otel_current_span_key);
1849
+ if (span == Qnil) return failed;
1850
+
1851
+ // If it exists, span_context is expected to be a OpenTelemetry::Trace::SpanContext (don't confuse it with OpenTelemetry::Context)
1852
+ VALUE span_context = rb_ivar_get(span, at_context_id /* @context */);
1853
+ if (span_context == Qnil) return failed;
1854
+
1855
+ VALUE span_id = rb_ivar_get(span_context, at_span_id_id /* @span_id */);
1856
+ VALUE trace_id = rb_ivar_get(span_context, at_trace_id_id /* @trace_id */);
1857
+ if (span_id == Qnil || trace_id == Qnil || !RB_TYPE_P(span_id, T_STRING) || !RB_TYPE_P(trace_id, T_STRING)) return failed;
1858
+
1859
+ return (otel_span) {.span = span, .span_id = span_id, .trace_id = trace_id};
1860
+ }
1861
+
1862
+ // Otel span ids are represented as a big-endian 8-byte string
1863
+ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
1864
+ if (!RB_TYPE_P(otel_span_id, T_STRING) || RSTRING_LEN(otel_span_id) != 8) { return 0; }
1865
+
1866
+ unsigned char *span_bytes = (unsigned char*) StringValuePtr(otel_span_id);
1867
+
1868
+ return \
1869
+ ((uint64_t)span_bytes[0] << 56) |
1870
+ ((uint64_t)span_bytes[1] << 48) |
1871
+ ((uint64_t)span_bytes[2] << 40) |
1872
+ ((uint64_t)span_bytes[3] << 32) |
1873
+ ((uint64_t)span_bytes[4] << 24) |
1874
+ ((uint64_t)span_bytes[5] << 16) |
1875
+ ((uint64_t)span_bytes[6] << 8) |
1876
+ ((uint64_t)span_bytes[7]);
1877
+ }
1878
+
1879
+ #ifndef NO_GVL_INSTRUMENTATION
1880
+ // This function can get called from outside the GVL and even on non-main Ractors
1881
+ void thread_context_collector_on_gvl_waiting(gvl_profiling_thread thread) {
1882
+ // Because this function gets called from a thread that is NOT holding the GVL, we avoid touching the
1883
+ // per-thread context directly.
1884
+ //
1885
+ // Instead, we ask Ruby to hold the data we need in Ruby's own special per-thread context area
1886
+ // that's thread-safe and built for this kind of use
1887
+ //
1888
+ // Also, this function can get called on the non-main Ractor. We deal with this by checking if the value in the context
1889
+ // is non-zero, since only `initialize_context` ever sets the value from 0 to non-zero for threads it sees.
1890
+ intptr_t thread_being_profiled = gvl_profiling_state_get(thread);
1891
+ if (!thread_being_profiled) return;
1892
+
1893
+ long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
1894
+ if (current_monotonic_wall_time_ns <= 0 || current_monotonic_wall_time_ns > GVL_WAITING_ENABLED_EMPTY) return;
1895
+
1896
+ gvl_profiling_state_set(thread, current_monotonic_wall_time_ns);
1897
+ }
1898
+
1899
+ // This function can get called from outside the GVL and even on non-main Ractors
1900
+ __attribute__((warn_unused_result))
1901
+ on_gvl_running_result thread_context_collector_on_gvl_running_with_threshold(gvl_profiling_thread thread, uint32_t waiting_for_gvl_threshold_ns) {
1902
+ intptr_t gvl_waiting_at = gvl_profiling_state_get(thread);
1903
+
1904
+ // Thread was not being profiled / not waiting on gvl
1905
+ if (gvl_waiting_at == 0 || gvl_waiting_at == GVL_WAITING_ENABLED_EMPTY) return ON_GVL_RUNNING_UNKNOWN;
1906
+
1907
+ // @ivoanjo: I'm not sure if this can happen -- It means we should've sampled already but haven't gotten the chance yet?
1908
+ if (gvl_waiting_at < 0) return ON_GVL_RUNNING_SAMPLE;
1909
+
1910
+ long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - gvl_waiting_at;
1911
+
1912
+ bool should_sample = waiting_for_gvl_duration_ns >= waiting_for_gvl_threshold_ns;
1913
+
1914
+ if (should_sample) {
1915
+ // We flip the gvl_waiting_at to negative to mark that the thread is now running and no longer waiting
1916
+ intptr_t gvl_waiting_at_is_now_running = -gvl_waiting_at;
1917
+
1918
+ gvl_profiling_state_set(thread, gvl_waiting_at_is_now_running);
1919
+ } else {
1920
+ // We decided not to sample. Let's mark the thread back to the initial "enabled but empty" state
1921
+ gvl_profiling_state_set(thread, GVL_WAITING_ENABLED_EMPTY);
1922
+ }
1923
+
1924
+ return should_sample ? ON_GVL_RUNNING_SAMPLE : ON_GVL_RUNNING_DONT_SAMPLE;
1925
+ }
1926
+
1927
+ __attribute__((warn_unused_result))
1928
+ on_gvl_running_result thread_context_collector_on_gvl_running(gvl_profiling_thread thread) {
1929
+ return thread_context_collector_on_gvl_running_with_threshold(thread, global_waiting_for_gvl_threshold_ns);
1930
+ }
1931
+
1932
+ // Why does this method need to exist?
1933
+ //
1934
+ // You may be surprised to see that if we never call this function (from cpu_and_wall_time_worker), Waiting for GVL
1935
+ // samples will still show up.
1936
+ // This is because regular cpu/wall-time samples also use `update_metrics_and_sample` which will do the right thing
1937
+ // and push "Waiting for GVL" samples as needed.
1938
+ //
1939
+ // The reason this method needs to exist and be called very shortly after thread_context_collector_on_gvl_running
1940
+ // returning true is to ensure accuracy of both the timing and stack for the Waiting for GVL sample.
1941
+ //
1942
+ // Timing:
1943
+ // Because we currently only record the timestamp when the Waiting for GVL started and not when the Waiting for GVL ended,
1944
+ // we rely on pushing a sample as soon as possible when the Waiting for GVL ends so that the timestamp of the sample
1945
+ // actually matches when we stopped waiting.
1946
+ //
1947
+ // Stack:
1948
+ // If the thread starts working without the end of the Waiting for GVL sample, then by the time the thread is sampled
1949
+ // via the regular cpu/wall-time samples mechanism, the stack can be be inaccurate (e.g. does not correctly pinpoint
1950
+ // where the waiting happened).
1951
+ //
1952
+ // Arguably, the last sample after Waiting for GVL ended (when gvl_waiting_at < 0) should always come from this method
1953
+ // and not a regular cpu/wall-time sample BUT since all of these things are happening in parallel/concurrently I suspect
1954
+ // it's possible for a regular sample to kick in just before this one.
1955
+ //
1956
+ // ---
1957
+ //
1958
+ // NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
1959
+ // exists to enable testing.
1960
+ VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns) {
1961
+ thread_context_collector_state *state;
1962
+ TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
1963
+
1964
+ if (!state->timeline_enabled) rb_raise(rb_eRuntimeError, "GVL profiling requires timeline to be enabled");
1965
+
1966
+ intptr_t gvl_waiting_at = gvl_profiling_state_thread_object_get(current_thread);
1967
+
1968
+ if (gvl_waiting_at >= 0) {
1969
+ // @ivoanjo: I'm not sure if this can ever happen. This means that we're not on the same thread
1970
+ // that ran `thread_context_collector_on_gvl_running` and made the decision to sample OR a regular sample was
1971
+ // triggered ahead of us.
1972
+ // We do nothing in this case.
1973
+ return Qfalse;
1974
+ }
1975
+
1976
+ per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
1977
+
1978
+ // We don't actually account for cpu-time during Waiting for GVL. BUT, we may chose to push an
1979
+ // extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
1980
+ // cpu-time of the thread and let `update_metrics_and_sample` decide what to do with it.
1981
+ long cpu_time_for_thread = cpu_time_now_ns(thread_context);
1982
+
1983
+ // TODO: Should we update the dynamic sampling rate overhead tracking with this sample as well?
1984
+
1985
+ update_metrics_and_sample(
1986
+ state,
1987
+ /* thread_being_sampled: */ current_thread,
1988
+ /* stack_from_thread: */ current_thread,
1989
+ thread_context,
1990
+ &thread_context->sampling_buffer,
1991
+ cpu_time_for_thread,
1992
+ current_monotonic_wall_time_ns
1993
+ );
1994
+
1995
+ return Qtrue;
1996
+ }
1997
+
1998
+ // This method is intended to be called from update_metrics_and_sample. It exists to handle extra sampling steps we
1999
+ // need to take when sampling cpu/wall-time for a thread that's in the "Waiting for GVL" state.
2000
+ __attribute__((warn_unused_result))
2001
+ static bool handle_gvl_waiting(
2002
+ thread_context_collector_state *state,
2003
+ VALUE thread_being_sampled,
2004
+ VALUE stack_from_thread,
2005
+ per_thread_context *thread_context,
2006
+ sampling_buffer* sampling_buffer,
2007
+ long current_cpu_time_ns
2008
+ ) {
2009
+ intptr_t gvl_waiting_at = gvl_profiling_state_thread_object_get(thread_being_sampled);
2010
+
2011
+ bool is_gvl_waiting_state = gvl_waiting_at != 0 && gvl_waiting_at != GVL_WAITING_ENABLED_EMPTY;
2012
+
2013
+ if (!is_gvl_waiting_state) return false;
2014
+
2015
+ // We can be in one of 2 situations here:
2016
+ //
2017
+ // 1. The current sample is the first one after we entered the "Waiting for GVL" state
2018
+ // (wall_time_at_previous_sample_ns < abs(gvl_waiting_at))
2019
+ //
2020
+ // time ─────►
2021
+ // ...──────────────┬───────────────────...
2022
+ // Other state │ Waiting for GVL
2023
+ // ...──────────────┴───────────────────...
2024
+ // ▲ ▲
2025
+ // └─ Previous sample └─ Regular sample (caller)
2026
+ //
2027
+ // In this case, we'll want to push two samples: a) one for the current time (handled by the caller), b) an extra sample
2028
+ // to represent the remaining cpu/wall time before the "Waiting for GVL" started:
2029
+ //
2030
+ // time ─────►
2031
+ // ...──────────────┬───────────────────...
2032
+ // Other state │ Waiting for GVL
2033
+ // ...──────────────┴───────────────────...
2034
+ // ▲ ▲ ▲
2035
+ // └─ Prev... └─ Extra sample └─ Regular sample (caller)
2036
+ //
2037
+ // 2. The current sample is the n-th one after we entered the "Waiting for GVL" state
2038
+ // (wall_time_at_previous_sample_ns > abs(gvl_waiting_at))
2039
+ //
2040
+ // time ─────►
2041
+ // ...──────────────┬───────────────────────────────────────────────...
2042
+ // Other state │ Waiting for GVL
2043
+ // ...──────────────┴───────────────────────────────────────────────...
2044
+ // ▲ ▲ ▲
2045
+ // └─ Previous sample └─ Previous sample └─ Regular sample (caller)
2046
+ //
2047
+ // In this case, we just report back to the caller that the thread is in the "Waiting for GVL" state.
2048
+ //
2049
+ // ---
2050
+ //
2051
+ // Overall, gvl_waiting_at will be > 0 if still in the "Waiting for GVL" state and < 0 if we actually reached the end of
2052
+ // the wait.
2053
+ //
2054
+ // It doesn't really matter if the thread is still waiting or just reached the end of the wait: each sample represents
2055
+ // a snapshot at time ending now, so if the state finished, it just means the next sample will be a regular one.
2056
+
2057
+ if (gvl_waiting_at < 0) {
2058
+ // Negative means the waiting for GVL just ended, so we clear the state, so next samples no longer represent waiting
2059
+ gvl_profiling_state_thread_object_set(thread_being_sampled, GVL_WAITING_ENABLED_EMPTY);
2060
+ }
2061
+
2062
+ long gvl_waiting_started_wall_time_ns = labs(gvl_waiting_at);
2063
+
2064
+ if (thread_context->wall_time_at_previous_sample_ns < gvl_waiting_started_wall_time_ns) { // situation 1 above
2065
+ long cpu_time_elapsed_ns = update_time_since_previous_sample(
2066
+ &thread_context->cpu_time_at_previous_sample_ns,
2067
+ current_cpu_time_ns,
2068
+ thread_context->gc_tracking.cpu_time_at_start_ns,
2069
+ IS_NOT_WALL_TIME
2070
+ );
2071
+
2072
+ long duration_until_start_of_gvl_waiting_ns = update_time_since_previous_sample(
2073
+ &thread_context->wall_time_at_previous_sample_ns,
2074
+ gvl_waiting_started_wall_time_ns,
2075
+ INVALID_TIME,
2076
+ IS_WALL_TIME
2077
+ );
2078
+
2079
+ // Push extra sample
2080
+ trigger_sample_for_thread(
2081
+ state,
2082
+ thread_being_sampled,
2083
+ stack_from_thread,
2084
+ thread_context,
2085
+ sampling_buffer,
2086
+ (sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = duration_until_start_of_gvl_waiting_ns},
2087
+ gvl_waiting_started_wall_time_ns,
2088
+ NULL,
2089
+ NULL,
2090
+ /* is_gvl_waiting_state: */ false, // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
2091
+ /* is_safe_to_allocate_objects: */ true // This is similar to a regular cpu/wall sample, so it's also safe
2092
+ );
2093
+ }
2094
+
2095
+ return true;
2096
+ }
2097
+
2098
+ static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
2099
+ ENFORCE_THREAD(thread);
2100
+
2101
+ debug_enter_unsafe_context();
2102
+
2103
+ thread_context_collector_on_gvl_waiting(thread_from_thread_object(thread));
2104
+
2105
+ debug_leave_unsafe_context();
2106
+
2107
+ return Qnil;
2108
+ }
2109
+
2110
+ static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
2111
+ ENFORCE_THREAD(thread);
2112
+
2113
+ debug_enter_unsafe_context();
2114
+
2115
+ intptr_t gvl_waiting_at = gvl_profiling_state_thread_object_get(thread);
2116
+
2117
+ debug_leave_unsafe_context();
2118
+
2119
+ return LONG2NUM(gvl_waiting_at);
2120
+ }
2121
+
2122
+ static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread) {
2123
+ ENFORCE_THREAD(thread);
2124
+
2125
+ debug_enter_unsafe_context();
2126
+
2127
+ VALUE result = thread_context_collector_on_gvl_running(thread_from_thread_object(thread)) == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
2128
+
2129
+ debug_leave_unsafe_context();
2130
+
2131
+ return result;
2132
+ }
2133
+
2134
+ static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
2135
+ ENFORCE_THREAD(thread);
2136
+
2137
+ debug_enter_unsafe_context();
2138
+
2139
+ VALUE result = thread_context_collector_sample_after_gvl_running(
2140
+ collector_instance,
2141
+ thread,
2142
+ monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
2143
+ );
2144
+
2145
+ debug_leave_unsafe_context();
2146
+
2147
+ return result;
2148
+ }
2149
+
2150
+ static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns) {
2151
+ ENFORCE_THREAD(thread);
2152
+
2153
+ thread_context_collector_state *state;
2154
+ TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
2155
+
2156
+ per_thread_context *thread_context = get_context_for(thread, state);
2157
+ if (thread_context == NULL) rb_raise(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
2158
+
2159
+ thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
2160
+
2161
+ return Qtrue;
2162
+ }
2163
+
2164
+ #else
2165
+ static bool handle_gvl_waiting(
2166
+ DDTRACE_UNUSED thread_context_collector_state *state,
2167
+ DDTRACE_UNUSED VALUE thread_being_sampled,
2168
+ DDTRACE_UNUSED VALUE stack_from_thread,
2169
+ DDTRACE_UNUSED per_thread_context *thread_context,
2170
+ DDTRACE_UNUSED sampling_buffer* sampling_buffer,
2171
+ DDTRACE_UNUSED long current_cpu_time_ns
2172
+ ) { return false; }
2173
+ #endif // NO_GVL_INSTRUMENTATION
2174
+
2175
+ #define MAX_SAFE_LOOKUP_SIZE 16
2176
+
2177
+ typedef struct { VALUE lookup_key; VALUE result; } safe_lookup_hash_state;
2178
+
2179
+ static int safe_lookup_hash_iterate(VALUE key, VALUE value, VALUE state_ptr) {
2180
+ safe_lookup_hash_state *state = (safe_lookup_hash_state *) state_ptr;
2181
+
2182
+ if (key == state->lookup_key) {
2183
+ state->result = value;
2184
+ return ST_STOP;
2185
+ }
2186
+
2187
+ return ST_CONTINUE;
2188
+ }
2189
+
2190
+ // This method exists because we need to look up a hash during sampling, but we don't want to invoke any
2191
+ // Ruby code as a side effect. To be able to look up by hash, `rb_hash_lookup` calls `#hash` on the key,
2192
+ // which we want to avoid.
2193
+ // Thus, instead, we opt to just iterate through the hash and check if we can find what we're looking for.
2194
+ //
2195
+ // To avoid having too much overhead here we only iterate in hashes up to MAX_SAFE_LOOKUP_SIZE.
2196
+ // Note that we don't even try to iterate if the hash is bigger -- this is to avoid flaky behavior where
2197
+ // depending on the internal storage order of the hash we may or not find the key, and instead we always
2198
+ // enforce the size.
2199
+ static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key) {
2200
+ if (!RB_TYPE_P(hash, T_HASH) || RHASH_SIZE(hash) > MAX_SAFE_LOOKUP_SIZE) return Qnil;
2201
+
2202
+ safe_lookup_hash_state state = {.lookup_key = key, .result = Qnil};
2203
+
2204
+ rb_hash_foreach(hash, safe_lookup_hash_iterate, (VALUE) &state);
2205
+
2206
+ return state.result;
2207
+ }
2208
+
2209
+ static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
2210
+ thread_context_collector_state *state;
2211
+ TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
2212
+
2213
+ long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
2214
+ long system_epoch_time_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, current_monotonic_wall_time_ns);
2215
+
2216
+ return LONG2NUM(system_epoch_time_ns);
2217
+ }
2218
+
2219
+ static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
2220
+ return thread_context_collector_prepare_sample_inside_signal_handler(collector_instance) ? Qtrue : Qfalse;
2221
+ }