datadog 2.8.0 → 2.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1009 -1
- data/README.md +0 -1
- data/bin/ddprofrb +4 -4
- data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
- data/ext/datadog_profiling_native_extension/clock_id.h +11 -3
- data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +66 -0
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +3 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +500 -188
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +5 -5
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +34 -20
- data/ext/datadog_profiling_native_extension/collectors_stack.c +319 -101
- data/ext/datadog_profiling_native_extension/collectors_stack.h +27 -5
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +985 -504
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +19 -5
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +66 -5
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +50 -1
- data/ext/datadog_profiling_native_extension/encoded_profile.c +80 -0
- data/ext/datadog_profiling_native_extension/encoded_profile.h +8 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +53 -35
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +6 -43
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -55
- data/ext/datadog_profiling_native_extension/heap_recorder.c +487 -497
- data/ext/datadog_profiling_native_extension/heap_recorder.h +17 -8
- data/ext/datadog_profiling_native_extension/http_transport.c +108 -144
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +18 -15
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -10
- data/ext/datadog_profiling_native_extension/macos_sampler_thread.h +55 -0
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -13
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +136 -60
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +22 -5
- data/ext/datadog_profiling_native_extension/profiling.c +84 -27
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +67 -62
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +20 -16
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +34 -10
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +2 -1
- data/ext/datadog_profiling_native_extension/stack_recorder.c +326 -217
- data/ext/datadog_profiling_native_extension/stack_recorder.h +4 -2
- data/ext/datadog_profiling_native_extension/time_helpers.h +14 -8
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +48 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +36 -0
- data/ext/libdatadog_api/crashtracker.c +27 -24
- data/ext/libdatadog_api/crashtracker.h +5 -0
- data/ext/libdatadog_api/crashtracker_report_exception.c +126 -0
- data/ext/libdatadog_api/datadog_ruby_common.c +66 -5
- data/ext/libdatadog_api/datadog_ruby_common.h +50 -1
- data/ext/libdatadog_api/ddsketch.c +102 -0
- data/ext/libdatadog_api/di.c +203 -0
- data/ext/libdatadog_api/extconf.rb +41 -45
- data/ext/libdatadog_api/feature_flags.c +580 -0
- data/ext/libdatadog_api/feature_flags.h +5 -0
- data/ext/libdatadog_api/helpers.h +27 -0
- data/ext/libdatadog_api/init.c +27 -0
- data/ext/libdatadog_api/library_config.c +173 -0
- data/ext/libdatadog_api/library_config.h +25 -0
- data/ext/libdatadog_api/process_discovery.c +118 -0
- data/ext/libdatadog_api/process_discovery.h +5 -0
- data/ext/libdatadog_extconf_helpers.rb +111 -18
- data/lib/datadog/ai_guard/api_client.rb +84 -0
- data/lib/datadog/ai_guard/autoload.rb +10 -0
- data/lib/datadog/ai_guard/component.rb +44 -0
- data/lib/datadog/ai_guard/configuration/ext.rb +17 -0
- data/lib/datadog/ai_guard/configuration.rb +115 -0
- data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -0
- data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
- data/lib/datadog/ai_guard/contrib/rack/integration.rb +42 -0
- data/lib/datadog/ai_guard/contrib/rack/patcher.rb +26 -0
- data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +97 -0
- data/lib/datadog/ai_guard/contrib/rails/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/rails/patcher.rb +97 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +80 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +30 -0
- data/lib/datadog/ai_guard/evaluation/content_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/content_part.rb +36 -0
- data/lib/datadog/ai_guard/evaluation/message.rb +25 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +36 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +86 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +45 -0
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +18 -0
- data/lib/datadog/ai_guard/evaluation.rb +108 -0
- data/lib/datadog/ai_guard/ext.rb +29 -0
- data/lib/datadog/ai_guard.rb +181 -0
- data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +89 -0
- data/lib/datadog/appsec/actions_handler.rb +49 -0
- data/lib/datadog/appsec/anonymizer.rb +16 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +66 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +36 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
- data/lib/datadog/appsec/api_security/route_extractor.rb +104 -0
- data/lib/datadog/appsec/api_security/sampler.rb +62 -0
- data/lib/datadog/appsec/api_security.rb +23 -0
- data/lib/datadog/appsec/assets/blocked.html +10 -1
- data/lib/datadog/appsec/assets/blocked.json +1 -1
- data/lib/datadog/appsec/assets/blocked.text +3 -1
- data/lib/datadog/appsec/assets/waf_rules/README.md +44 -5
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +954 -229
- data/lib/datadog/appsec/assets/waf_rules/strict.json +109 -106
- data/lib/datadog/appsec/assets.rb +8 -8
- data/lib/datadog/appsec/autoload.rb +5 -5
- data/lib/datadog/appsec/component.rb +56 -68
- data/lib/datadog/appsec/compressed_json.rb +40 -0
- data/lib/datadog/appsec/configuration.rb +427 -1
- data/lib/datadog/appsec/context.rb +135 -0
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +45 -24
- data/lib/datadog/appsec/contrib/active_record/integration.rb +6 -6
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +67 -16
- data/lib/datadog/appsec/contrib/auto_instrument.rb +1 -1
- data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +75 -0
- data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +39 -0
- data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +146 -0
- data/lib/datadog/appsec/contrib/devise/configuration.rb +52 -0
- data/lib/datadog/appsec/contrib/devise/data_extractor.rb +78 -0
- data/lib/datadog/appsec/contrib/devise/ext.rb +22 -0
- data/lib/datadog/appsec/contrib/devise/integration.rb +5 -6
- data/lib/datadog/appsec/contrib/devise/patcher.rb +33 -25
- data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +104 -0
- data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +70 -0
- data/lib/datadog/appsec/contrib/devise/{patcher/rememberable_patch.rb → patches/skip_signin_tracking_patch.rb} +3 -3
- data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +121 -0
- data/lib/datadog/appsec/contrib/excon/integration.rb +41 -0
- data/lib/datadog/appsec/contrib/excon/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +181 -0
- data/lib/datadog/appsec/contrib/faraday/connection_patch.rb +22 -0
- data/lib/datadog/appsec/contrib/faraday/integration.rb +42 -0
- data/lib/datadog/appsec/contrib/faraday/patcher.rb +53 -0
- data/lib/datadog/appsec/contrib/faraday/rack_builder_patch.rb +22 -0
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +166 -0
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +4 -10
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +76 -26
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +22 -33
- data/lib/datadog/appsec/contrib/graphql/integration.rb +10 -9
- data/lib/datadog/appsec/contrib/graphql/patcher.rb +3 -6
- data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
- data/lib/datadog/appsec/contrib/rack/ext.rb +34 -0
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +56 -21
- data/lib/datadog/appsec/contrib/rack/gateway/response.rb +4 -4
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +101 -100
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +79 -0
- data/lib/datadog/appsec/contrib/rack/integration.rb +7 -7
- data/lib/datadog/appsec/contrib/rack/patcher.rb +2 -5
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +13 -14
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +117 -115
- data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +43 -4
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +58 -33
- data/lib/datadog/appsec/contrib/rails/integration.rb +6 -6
- data/lib/datadog/appsec/contrib/rails/patcher.rb +82 -69
- data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +29 -0
- data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +33 -0
- data/lib/datadog/appsec/contrib/rails/request.rb +3 -3
- data/lib/datadog/appsec/contrib/rest_client/integration.rb +45 -0
- data/lib/datadog/appsec/contrib/rest_client/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +193 -0
- data/lib/datadog/appsec/contrib/sinatra/gateway/request.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/route_params.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +83 -67
- data/lib/datadog/appsec/contrib/sinatra/integration.rb +6 -6
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +25 -61
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +31 -0
- data/lib/datadog/appsec/counter_sampler.rb +25 -0
- data/lib/datadog/appsec/default_header_tags.rb +52 -0
- data/lib/datadog/appsec/event.rb +80 -152
- data/lib/datadog/appsec/ext.rb +16 -4
- data/lib/datadog/appsec/extensions.rb +2 -2
- data/lib/datadog/appsec/instrumentation/gateway/argument.rb +23 -2
- data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +23 -0
- data/lib/datadog/appsec/instrumentation/gateway.rb +6 -24
- data/lib/datadog/appsec/metrics/collector.rb +80 -0
- data/lib/datadog/appsec/metrics/exporter.rb +48 -0
- data/lib/datadog/appsec/metrics/telemetry.rb +40 -0
- data/lib/datadog/appsec/metrics/telemetry_exporter.rb +47 -0
- data/lib/datadog/appsec/metrics.rb +14 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +60 -34
- data/lib/datadog/appsec/monitor.rb +1 -1
- data/lib/datadog/appsec/processor/rule_loader.rb +48 -54
- data/lib/datadog/appsec/rate_limiter.rb +1 -1
- data/lib/datadog/appsec/remote.rb +59 -68
- data/lib/datadog/appsec/response.rb +42 -90
- data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
- data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
- data/lib/datadog/appsec/route_normalizer.rb +80 -0
- data/lib/datadog/appsec/security_engine/engine.rb +197 -0
- data/lib/datadog/appsec/security_engine/result.rb +103 -0
- data/lib/datadog/appsec/security_engine/runner.rb +111 -0
- data/lib/datadog/appsec/security_engine.rb +9 -0
- data/lib/datadog/appsec/security_event.rb +37 -0
- data/lib/datadog/appsec/thread_safe_ref.rb +61 -0
- data/lib/datadog/appsec/trace_keeper.rb +36 -0
- data/lib/datadog/appsec/utils/hash_coercion.rb +23 -0
- data/lib/datadog/appsec/utils/http/body.rb +41 -0
- data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
- data/lib/datadog/appsec/utils/http/media_range.rb +17 -16
- data/lib/datadog/appsec/utils/http/media_type.rb +33 -27
- data/lib/datadog/appsec/utils/http/url_encoded.rb +107 -0
- data/lib/datadog/appsec/utils.rb +0 -2
- data/lib/datadog/appsec.rb +35 -30
- data/lib/datadog/auto_instrument.rb +5 -2
- data/lib/datadog/auto_instrument_base.rb +2 -1
- data/lib/datadog/core/buffer/cruby.rb +1 -1
- data/lib/datadog/core/buffer/random.rb +18 -2
- data/lib/datadog/core/buffer/thread_safe.rb +1 -1
- data/lib/datadog/core/configuration/agent_settings.rb +52 -0
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +22 -36
- data/lib/datadog/core/configuration/agentless_settings_resolver.rb +176 -0
- data/lib/datadog/core/configuration/base.rb +19 -7
- data/lib/datadog/core/configuration/components.rb +261 -57
- data/lib/datadog/core/configuration/components_state.rb +28 -0
- data/lib/datadog/core/configuration/config_helper.rb +109 -0
- data/lib/datadog/core/configuration/deprecations.rb +36 -0
- data/lib/datadog/core/configuration/ext.rb +15 -12
- data/lib/datadog/core/configuration/option.rb +150 -84
- data/lib/datadog/core/configuration/option_definition.rb +50 -29
- data/lib/datadog/core/configuration/options.rb +58 -19
- data/lib/datadog/core/configuration/settings.rb +287 -103
- data/lib/datadog/core/configuration/stable_config.rb +32 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +415 -0
- data/lib/datadog/core/configuration.rb +47 -23
- data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
- data/lib/datadog/core/contrib/rails/utils.rb +28 -0
- data/lib/datadog/core/crashtracking/component.rb +81 -38
- data/lib/datadog/core/crashtracking/tag_builder.rb +11 -23
- data/lib/datadog/core/ddsketch.rb +19 -0
- data/lib/datadog/core/deprecations.rb +3 -3
- data/lib/datadog/core/diagnostics/environment_logger.rb +10 -8
- data/lib/datadog/core/diagnostics/health.rb +2 -2
- data/lib/datadog/core/encoding.rb +21 -5
- data/lib/datadog/core/environment/agent_info.rb +142 -0
- data/lib/datadog/core/environment/cgroup.rb +53 -28
- data/lib/datadog/core/environment/container.rb +143 -51
- data/lib/datadog/core/environment/execution.rb +3 -3
- data/lib/datadog/core/environment/ext.rb +32 -21
- data/lib/datadog/core/environment/git.rb +5 -4
- data/lib/datadog/core/environment/identity.rb +50 -14
- data/lib/datadog/core/environment/platform.rb +8 -8
- data/lib/datadog/core/environment/process.rb +122 -0
- data/lib/datadog/core/environment/socket.rb +15 -2
- data/lib/datadog/core/environment/variable_helpers.rb +4 -4
- data/lib/datadog/core/environment/yjit.rb +2 -1
- data/lib/datadog/core/error.rb +15 -13
- data/lib/datadog/core/evp.rb +11 -0
- data/lib/datadog/core/extensions.rb +1 -1
- data/lib/datadog/core/feature_flags.rb +61 -0
- data/lib/datadog/core/git/ext.rb +4 -4
- data/lib/datadog/core/knuth_sampler.rb +59 -0
- data/lib/datadog/core/logger.rb +5 -5
- data/lib/datadog/core/logging/ext.rb +1 -1
- data/lib/datadog/core/metrics/client.rb +49 -49
- data/lib/datadog/core/metrics/ext.rb +5 -5
- data/lib/datadog/core/metrics/logging.rb +8 -8
- data/lib/datadog/core/metrics/options.rb +3 -3
- data/lib/datadog/core/pin.rb +9 -9
- data/lib/datadog/core/process_discovery/tracer_memfd.rb +13 -0
- data/lib/datadog/core/process_discovery.rb +62 -0
- data/lib/datadog/core/rate_limiter.rb +16 -4
- data/lib/datadog/core/remote/client/capabilities.rb +72 -13
- data/lib/datadog/core/remote/client.rb +68 -51
- data/lib/datadog/core/remote/component.rb +90 -55
- data/lib/datadog/core/remote/configuration/content.rb +17 -4
- data/lib/datadog/core/remote/configuration/digest.rb +22 -15
- data/lib/datadog/core/remote/configuration/path.rb +9 -9
- data/lib/datadog/core/remote/configuration/repository.rb +17 -4
- data/lib/datadog/core/remote/configuration/target.rb +15 -8
- data/lib/datadog/core/remote/configuration.rb +4 -4
- data/lib/datadog/core/remote/ext.rb +3 -3
- data/lib/datadog/core/remote/negotiation.rb +10 -10
- data/lib/datadog/core/remote/tie/tracing.rb +7 -7
- data/lib/datadog/core/remote/transport/config.rb +11 -37
- data/lib/datadog/core/remote/transport/http/config.rb +39 -107
- data/lib/datadog/core/remote/transport/http/negotiation.rb +19 -92
- data/lib/datadog/core/remote/transport/http.rb +33 -111
- data/lib/datadog/core/remote/transport/negotiation.rb +9 -21
- data/lib/datadog/core/remote/worker.rb +33 -42
- data/lib/datadog/core/runtime/ext.rb +22 -23
- data/lib/datadog/core/runtime/metrics.rb +30 -14
- data/lib/datadog/core/semaphore.rb +1 -4
- data/lib/datadog/core/tag_builder.rb +52 -0
- data/lib/datadog/core/tag_normalizer.rb +84 -0
- data/lib/datadog/core/telemetry/component.rb +142 -65
- data/lib/datadog/core/telemetry/emitter.rb +26 -14
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +65 -0
- data/lib/datadog/core/telemetry/event/app_closing.rb +18 -0
- data/lib/datadog/core/telemetry/event/app_dependencies_loaded.rb +33 -0
- data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
- data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
- data/lib/datadog/core/telemetry/event/app_heartbeat.rb +18 -0
- data/lib/datadog/core/telemetry/event/app_integrations_change.rb +58 -0
- data/lib/datadog/core/telemetry/event/app_started.rb +293 -0
- data/lib/datadog/core/telemetry/event/base.rb +40 -0
- data/lib/datadog/core/telemetry/event/distributions.rb +18 -0
- data/lib/datadog/core/telemetry/event/generate_metrics.rb +43 -0
- data/lib/datadog/core/telemetry/event/log.rb +76 -0
- data/lib/datadog/core/telemetry/event/message_batch.rb +42 -0
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +66 -0
- data/lib/datadog/core/telemetry/event.rb +21 -392
- data/lib/datadog/core/telemetry/ext.rb +11 -10
- data/lib/datadog/core/telemetry/http/adapters/net.rb +12 -97
- data/lib/datadog/core/telemetry/logger.rb +6 -3
- data/lib/datadog/core/telemetry/logging.rb +27 -9
- data/lib/datadog/core/telemetry/metric.rb +35 -13
- data/lib/datadog/core/telemetry/metrics_collection.rb +2 -2
- data/lib/datadog/core/telemetry/metrics_manager.rb +10 -1
- data/lib/datadog/core/telemetry/request.rb +21 -9
- data/lib/datadog/core/telemetry/transport/http/telemetry.rb +66 -0
- data/lib/datadog/core/telemetry/transport/http.rb +68 -0
- data/lib/datadog/core/telemetry/transport/telemetry.rb +42 -0
- data/lib/datadog/core/telemetry/worker.rb +214 -35
- data/lib/datadog/core/transport/ext.rb +14 -11
- data/lib/datadog/core/transport/http/adapters/net.rb +19 -4
- data/lib/datadog/core/transport/http/adapters/test.rb +3 -2
- data/lib/datadog/core/transport/http/adapters/unix_socket.rb +5 -5
- data/lib/datadog/core/transport/http/api/endpoint.rb +10 -5
- data/lib/datadog/{tracing → core}/transport/http/api/instance.rb +5 -5
- data/lib/datadog/core/transport/http/api/map.rb +1 -1
- data/lib/datadog/{tracing → core}/transport/http/builder.rb +62 -36
- data/lib/datadog/core/transport/http/client.rb +80 -0
- data/lib/datadog/core/transport/http/env.rb +8 -0
- data/lib/datadog/core/transport/http/response.rb +5 -1
- data/lib/datadog/core/transport/http.rb +80 -0
- data/lib/datadog/core/transport/parcel.rb +61 -9
- data/lib/datadog/core/transport/response.rb +19 -1
- data/lib/datadog/core/transport/transport.rb +90 -0
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +9 -9
- data/lib/datadog/core/utils/base64_codec.rb +23 -0
- data/lib/datadog/core/utils/duration.rb +33 -33
- data/lib/datadog/core/utils/enumerable_compat.rb +29 -0
- data/lib/datadog/core/utils/fnv.rb +26 -0
- data/lib/datadog/core/utils/forking.rb +5 -3
- data/lib/datadog/core/utils/hash.rb +0 -23
- data/lib/datadog/core/utils/lru_cache.rb +45 -0
- data/lib/datadog/core/utils/network.rb +34 -13
- data/lib/datadog/core/utils/only_once.rb +1 -1
- data/lib/datadog/core/utils/only_once_successful.rb +25 -8
- data/lib/datadog/core/utils/safe_dup.rb +4 -3
- data/lib/datadog/core/utils/sequence.rb +2 -0
- data/lib/datadog/core/utils/spawn_monkey_patch.rb +68 -0
- data/lib/datadog/core/utils/time.rb +26 -2
- data/lib/datadog/core/utils/truncation.rb +21 -0
- data/lib/datadog/core/utils/url.rb +1 -1
- data/lib/datadog/core/utils.rb +14 -5
- data/lib/datadog/core/vendor/multipart-post/multipart/post/composite_read_io.rb +1 -1
- data/lib/datadog/core/vendor/multipart-post/multipart/post/multipartable.rb +8 -8
- data/lib/datadog/core/vendor/multipart-post/multipart/post/parts.rb +7 -7
- data/lib/datadog/core/worker.rb +1 -1
- data/lib/datadog/core/workers/async.rb +40 -14
- data/lib/datadog/core/workers/interval_loop.rb +63 -4
- data/lib/datadog/core/workers/polling.rb +4 -2
- data/lib/datadog/core/workers/queue.rb +96 -1
- data/lib/datadog/core/workers/runtime_metrics.rb +14 -6
- data/lib/datadog/core.rb +20 -3
- data/lib/datadog/data_streams/configuration.rb +50 -0
- data/lib/datadog/data_streams/ext.rb +11 -0
- data/lib/datadog/data_streams/extensions.rb +16 -0
- data/lib/datadog/data_streams/pathway_context.rb +169 -0
- data/lib/datadog/data_streams/processor.rb +511 -0
- data/lib/datadog/data_streams/transport/http/stats.rb +58 -0
- data/lib/datadog/data_streams/transport/http.rb +36 -0
- data/lib/datadog/data_streams/transport/stats.rb +39 -0
- data/lib/datadog/data_streams.rb +100 -0
- data/lib/datadog/di/base.rb +123 -0
- data/lib/datadog/di/boot.rb +47 -0
- data/lib/datadog/di/capture_expression.rb +21 -0
- data/lib/datadog/di/capture_expression_evaluator.rb +71 -0
- data/lib/datadog/di/capture_limits.rb +41 -0
- data/lib/datadog/di/code_tracker.rb +224 -12
- data/lib/datadog/di/component.rb +157 -49
- data/lib/datadog/di/configuration.rb +241 -2
- data/lib/datadog/di/context.rb +82 -0
- data/lib/datadog/di/contrib/active_record.rb +34 -5
- data/lib/datadog/di/contrib/railtie.rb +15 -0
- data/lib/datadog/di/contrib.rb +28 -0
- data/lib/datadog/di/el/compiler.rb +168 -0
- data/lib/datadog/di/el/evaluator.rb +159 -0
- data/lib/datadog/di/el/expression.rb +42 -0
- data/lib/datadog/di/el.rb +5 -0
- data/lib/datadog/di/error.rb +49 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +672 -122
- data/lib/datadog/di/logger.rb +30 -0
- data/lib/datadog/di/{init.rb → preload.rb} +2 -4
- data/lib/datadog/di/probe.rb +125 -20
- data/lib/datadog/di/probe_builder.rb +126 -5
- data/lib/datadog/di/probe_file_loader/railtie.rb +15 -0
- data/lib/datadog/di/probe_file_loader.rb +87 -0
- data/lib/datadog/di/probe_manager.rb +198 -100
- data/lib/datadog/di/probe_notification_builder.rb +349 -103
- data/lib/datadog/di/probe_notifier_worker.rb +161 -51
- data/lib/datadog/di/probe_repository.rb +198 -0
- data/lib/datadog/di/proc_responder.rb +36 -0
- data/lib/datadog/di/redactor.rb +24 -3
- data/lib/datadog/di/remote.rb +259 -73
- data/lib/datadog/di/serializer.rb +319 -30
- data/lib/datadog/di/transport/diagnostics.rb +32 -0
- data/lib/datadog/di/transport/http/diagnostics.rb +35 -0
- data/lib/datadog/di/transport/http/input.rb +45 -0
- data/lib/datadog/di/transport/http.rb +81 -0
- data/lib/datadog/di/transport/input.rb +144 -0
- data/lib/datadog/di/utils.rb +133 -9
- data/lib/datadog/di.rb +188 -121
- data/lib/datadog/error_tracking/collector.rb +88 -0
- data/lib/datadog/error_tracking/component.rb +167 -0
- data/lib/datadog/error_tracking/configuration.rb +64 -0
- data/lib/datadog/error_tracking/ext.rb +18 -0
- data/lib/datadog/error_tracking/extensions.rb +16 -0
- data/lib/datadog/error_tracking/filters.rb +77 -0
- data/lib/datadog/error_tracking.rb +18 -0
- data/lib/datadog/kit/appsec/events/v2.rb +253 -0
- data/lib/datadog/kit/appsec/events.rb +39 -26
- data/lib/datadog/kit/enable_core_dumps.rb +3 -3
- data/lib/datadog/kit/identity.rb +34 -24
- data/lib/datadog/kit/tracing/method_tracer.rb +135 -0
- data/lib/datadog/kit.rb +1 -1
- data/lib/datadog/open_feature/component.rb +109 -0
- data/lib/datadog/open_feature/configuration.rb +50 -0
- data/lib/datadog/open_feature/evaluation_engine.rb +70 -0
- data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
- data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
- data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
- data/lib/datadog/open_feature/exposures/event.rb +60 -0
- data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
- data/lib/datadog/open_feature/exposures/worker.rb +116 -0
- data/lib/datadog/open_feature/ext.rb +21 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +102 -0
- data/lib/datadog/open_feature/hooks/flag_eval_metrics_hook.rb +51 -0
- data/lib/datadog/open_feature/hooks/span_enrichment_hook/codec.rb +58 -0
- data/lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state.rb +96 -0
- data/lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state_store.rb +49 -0
- data/lib/datadog/open_feature/hooks/span_enrichment_hook.rb +184 -0
- data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
- data/lib/datadog/open_feature/native_evaluator.rb +60 -0
- data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
- data/lib/datadog/open_feature/provider.rb +226 -0
- data/lib/datadog/open_feature/remote.rb +67 -0
- data/lib/datadog/open_feature/resolution_details.rb +37 -0
- data/lib/datadog/open_feature/transport.rb +121 -0
- data/lib/datadog/open_feature.rb +19 -0
- data/lib/datadog/opentelemetry/api/baggage.rb +90 -0
- data/lib/datadog/opentelemetry/api/context.rb +25 -10
- data/lib/datadog/opentelemetry/configuration/settings.rb +226 -0
- data/lib/datadog/opentelemetry/ext.rb +9 -0
- data/lib/datadog/opentelemetry/logs.rb +98 -0
- data/lib/datadog/opentelemetry/metrics.rb +90 -0
- data/lib/datadog/opentelemetry/sdk/configurator.rb +69 -5
- data/lib/datadog/opentelemetry/sdk/id_generator.rb +16 -10
- data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +30 -0
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +28 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +20 -14
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +24 -21
- data/lib/datadog/opentelemetry/sdk/trace/span.rb +43 -39
- data/lib/datadog/opentelemetry/sdk.rb +19 -0
- data/lib/datadog/opentelemetry/signal_configuration.rb +53 -0
- data/lib/datadog/opentelemetry/trace.rb +5 -5
- data/lib/datadog/opentelemetry.rb +11 -6
- data/lib/datadog/profiling/collectors/code_provenance.rb +84 -16
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +57 -3
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +13 -4
- data/lib/datadog/profiling/collectors/info.rb +62 -4
- data/lib/datadog/profiling/collectors/thread_context.rb +27 -7
- data/lib/datadog/profiling/component.rb +125 -88
- data/lib/datadog/profiling/encoded_profile.rb +11 -0
- data/lib/datadog/profiling/exporter.rb +51 -17
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +24 -5
- data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
- data/lib/datadog/profiling/ext.rb +2 -17
- data/lib/datadog/profiling/flush.rb +26 -17
- data/lib/datadog/profiling/http_transport.rb +30 -96
- data/lib/datadog/profiling/load_native_extension.rb +3 -35
- data/lib/datadog/profiling/profiler.rb +31 -12
- data/lib/datadog/profiling/scheduler.rb +19 -10
- data/lib/datadog/profiling/sequence_tracker.rb +44 -0
- data/lib/datadog/profiling/stack_recorder.rb +9 -17
- data/lib/datadog/profiling/tag_builder.rb +41 -41
- data/lib/datadog/profiling/tasks/exec.rb +10 -5
- data/lib/datadog/profiling/tasks/help.rb +1 -0
- data/lib/datadog/profiling/tasks/setup.rb +4 -2
- data/lib/datadog/profiling.rb +38 -37
- data/lib/datadog/ruby_version.rb +25 -0
- data/lib/datadog/single_step_instrument.rb +11 -2
- data/lib/datadog/symbol_database/component.rb +734 -0
- data/lib/datadog/symbol_database/configuration.rb +73 -0
- data/lib/datadog/symbol_database/extensions.rb +19 -0
- data/lib/datadog/symbol_database/extractor.rb +1110 -0
- data/lib/datadog/symbol_database/file_hash.rb +48 -0
- data/lib/datadog/symbol_database/logger.rb +43 -0
- data/lib/datadog/symbol_database/remote.rb +195 -0
- data/lib/datadog/symbol_database/scope.rb +102 -0
- data/lib/datadog/symbol_database/scope_batcher.rb +292 -0
- data/lib/datadog/symbol_database/service_version.rb +66 -0
- data/lib/datadog/symbol_database/symbol.rb +69 -0
- data/lib/datadog/symbol_database/transport/http/endpoint.rb +28 -0
- data/lib/datadog/symbol_database/transport/http.rb +45 -0
- data/lib/datadog/symbol_database/transport.rb +54 -0
- data/lib/datadog/symbol_database/uploader.rb +230 -0
- data/lib/datadog/symbol_database.rb +71 -0
- data/lib/datadog/tracing/analytics.rb +2 -2
- data/lib/datadog/tracing/buffer.rb +15 -15
- data/lib/datadog/tracing/client_ip.rb +4 -4
- data/lib/datadog/tracing/component.rb +39 -35
- data/lib/datadog/tracing/configuration/dynamic.rb +14 -16
- data/lib/datadog/tracing/configuration/ext.rb +60 -33
- data/lib/datadog/tracing/configuration/http.rb +2 -2
- data/lib/datadog/tracing/configuration/settings.rb +156 -26
- data/lib/datadog/tracing/context.rb +3 -3
- data/lib/datadog/tracing/context_provider.rb +3 -3
- data/lib/datadog/tracing/contrib/action_cable/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/action_cable/event.rb +5 -5
- data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +9 -6
- data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +8 -5
- data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +9 -6
- data/lib/datadog/tracing/contrib/action_cable/events.rb +4 -4
- data/lib/datadog/tracing/contrib/action_cable/ext.rb +18 -18
- data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -5
- data/lib/datadog/tracing/contrib/action_cable/integration.rb +10 -7
- data/lib/datadog/tracing/contrib/action_cable/patcher.rb +4 -4
- data/lib/datadog/tracing/contrib/action_mailer/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/action_mailer/event.rb +7 -4
- data/lib/datadog/tracing/contrib/action_mailer/events/deliver.rb +8 -8
- data/lib/datadog/tracing/contrib/action_mailer/events/process.rb +4 -4
- data/lib/datadog/tracing/contrib/action_mailer/events.rb +2 -2
- data/lib/datadog/tracing/contrib/action_mailer/ext.rb +18 -18
- data/lib/datadog/tracing/contrib/action_mailer/integration.rb +11 -7
- data/lib/datadog/tracing/contrib/action_mailer/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +37 -19
- data/lib/datadog/tracing/contrib/action_pack/action_controller/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +48 -13
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +5 -3
- data/lib/datadog/tracing/contrib/action_pack/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/action_pack/ext.rb +10 -8
- data/lib/datadog/tracing/contrib/action_pack/integration.rb +10 -7
- data/lib/datadog/tracing/contrib/action_pack/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/action_pack/utils.rb +2 -3
- data/lib/datadog/tracing/contrib/action_view/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/action_view/event.rb +1 -1
- data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +10 -7
- data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +12 -9
- data/lib/datadog/tracing/contrib/action_view/events.rb +2 -2
- data/lib/datadog/tracing/contrib/action_view/ext.rb +10 -10
- data/lib/datadog/tracing/contrib/action_view/integration.rb +14 -11
- data/lib/datadog/tracing/contrib/action_view/patcher.rb +5 -5
- data/lib/datadog/tracing/contrib/action_view/utils.rb +2 -2
- data/lib/datadog/tracing/contrib/active_job/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/active_job/data_streams.rb +62 -0
- data/lib/datadog/tracing/contrib/active_job/event.rb +11 -11
- data/lib/datadog/tracing/contrib/active_job/events/discard.rb +11 -8
- data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +11 -8
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +11 -8
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +11 -8
- data/lib/datadog/tracing/contrib/active_job/events/perform.rb +11 -8
- data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +11 -8
- data/lib/datadog/tracing/contrib/active_job/events.rb +6 -6
- data/lib/datadog/tracing/contrib/active_job/ext.rb +23 -23
- data/lib/datadog/tracing/contrib/active_job/integration.rb +10 -7
- data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
- data/lib/datadog/tracing/contrib/active_job/patcher.rb +21 -5
- data/lib/datadog/tracing/contrib/active_model_serializers/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/active_model_serializers/event.rb +4 -4
- data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +8 -8
- data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +9 -10
- data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +2 -2
- data/lib/datadog/tracing/contrib/active_model_serializers/ext.rb +10 -10
- data/lib/datadog/tracing/contrib/active_model_serializers/integration.rb +5 -6
- data/lib/datadog/tracing/contrib/active_model_serializers/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/active_record/configuration/makara_resolver.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +8 -8
- data/lib/datadog/tracing/contrib/active_record/configuration/settings.rb +4 -4
- data/lib/datadog/tracing/contrib/active_record/event.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +10 -10
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +16 -19
- data/lib/datadog/tracing/contrib/active_record/events.rb +2 -2
- data/lib/datadog/tracing/contrib/active_record/ext.rb +15 -15
- data/lib/datadog/tracing/contrib/active_record/integration.rb +16 -12
- data/lib/datadog/tracing/contrib/active_record/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/active_record/utils.rb +19 -19
- data/lib/datadog/tracing/contrib/active_support/cache/event.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +28 -20
- data/lib/datadog/tracing/contrib/active_support/cache/events.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +40 -8
- data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +8 -4
- data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +5 -7
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +26 -3
- data/lib/datadog/tracing/contrib/active_support/ext.rb +16 -16
- data/lib/datadog/tracing/contrib/active_support/integration.rb +11 -8
- data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +3 -2
- data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +2 -2
- data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +9 -11
- data/lib/datadog/tracing/contrib/active_support/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/analytics.rb +1 -1
- data/lib/datadog/tracing/contrib/auto_instrument.rb +5 -5
- data/lib/datadog/tracing/contrib/aws/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/aws/ext.rb +26 -26
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +21 -13
- data/lib/datadog/tracing/contrib/aws/integration.rb +11 -8
- data/lib/datadog/tracing/contrib/aws/parsed_context.rb +9 -3
- data/lib/datadog/tracing/contrib/aws/patcher.rb +10 -6
- data/lib/datadog/tracing/contrib/aws/service/base.rb +2 -1
- data/lib/datadog/tracing/contrib/aws/service/dynamodb.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/service/eventbridge.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/service/kinesis.rb +4 -4
- data/lib/datadog/tracing/contrib/aws/service/s3.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/service/sns.rb +3 -3
- data/lib/datadog/tracing/contrib/aws/service/sqs.rb +3 -3
- data/lib/datadog/tracing/contrib/aws/service/states.rb +5 -5
- data/lib/datadog/tracing/contrib/aws/services.rb +14 -14
- data/lib/datadog/tracing/contrib/component.rb +3 -3
- data/lib/datadog/tracing/contrib/concurrent_ruby/async_patch.rb +2 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +2 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/ext.rb +2 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/future_patch.rb +2 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +8 -5
- data/lib/datadog/tracing/contrib/concurrent_ruby/patcher.rb +4 -4
- data/lib/datadog/tracing/contrib/concurrent_ruby/promises_future_patch.rb +2 -2
- data/lib/datadog/tracing/contrib/configurable.rb +24 -9
- data/lib/datadog/tracing/contrib/configuration/resolver.rb +14 -4
- data/lib/datadog/tracing/contrib/configuration/resolvers/pattern_resolver.rb +5 -5
- data/lib/datadog/tracing/contrib/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/dalli/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/dalli/ext.rb +16 -15
- data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +6 -10
- data/lib/datadog/tracing/contrib/dalli/integration.rb +10 -7
- data/lib/datadog/tracing/contrib/dalli/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/dalli/quantize.rb +3 -3
- data/lib/datadog/tracing/contrib/delayed_job/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/delayed_job/ext.rb +14 -14
- data/lib/datadog/tracing/contrib/delayed_job/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/delayed_job/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +7 -5
- data/lib/datadog/tracing/contrib/delayed_job/server_internal_tracer/worker.rb +1 -0
- data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +18 -17
- data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +8 -8
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +57 -63
- data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +8 -8
- data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +7 -3
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +19 -21
- data/lib/datadog/tracing/contrib/ethon/ext.rb +16 -14
- data/lib/datadog/tracing/contrib/ethon/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +4 -11
- data/lib/datadog/tracing/contrib/ethon/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +21 -10
- data/lib/datadog/tracing/contrib/excon/ext.rb +14 -12
- data/lib/datadog/tracing/contrib/excon/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/excon/middleware.rb +17 -19
- data/lib/datadog/tracing/contrib/excon/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/ext.rb +20 -17
- data/lib/datadog/tracing/contrib/extensions.rb +57 -14
- data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +20 -13
- data/lib/datadog/tracing/contrib/faraday/ext.rb +14 -12
- data/lib/datadog/tracing/contrib/faraday/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +19 -19
- data/lib/datadog/tracing/contrib/faraday/patcher.rb +6 -6
- data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +11 -7
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +37 -34
- data/lib/datadog/tracing/contrib/grape/ext.rb +15 -15
- data/lib/datadog/tracing/contrib/grape/instrumentation.rb +15 -10
- data/lib/datadog/tracing/contrib/grape/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/grape/patcher.rb +10 -5
- data/lib/datadog/tracing/contrib/graphql/configuration/error_extension_env_parser.rb +21 -0
- data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +20 -2
- data/lib/datadog/tracing/contrib/graphql/ext.rb +11 -5
- data/lib/datadog/tracing/contrib/graphql/integration.rb +9 -9
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +8 -8
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +174 -41
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +7 -4
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +25 -23
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +13 -17
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor.rb +6 -6
- data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +2 -2
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +12 -7
- data/lib/datadog/tracing/contrib/grpc/ext.rb +14 -13
- data/lib/datadog/tracing/contrib/grpc/formatting.rb +7 -7
- data/lib/datadog/tracing/contrib/grpc/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/grpc/intercept_with_datadog.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/grpc.rb +7 -6
- data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +4 -3
- data/lib/datadog/tracing/contrib/hanami/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/hanami/ext.rb +8 -8
- data/lib/datadog/tracing/contrib/hanami/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/hanami/patcher.rb +5 -5
- data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +5 -4
- data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +12 -13
- data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +1 -16
- data/lib/datadog/tracing/contrib/http/configuration/settings.rb +20 -9
- data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +6 -6
- data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +13 -8
- data/lib/datadog/tracing/contrib/http/ext.rb +14 -12
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +20 -28
- data/lib/datadog/tracing/contrib/http/integration.rb +9 -8
- data/lib/datadog/tracing/contrib/http/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/http.rb +7 -6
- data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +20 -9
- data/lib/datadog/tracing/contrib/httpclient/ext.rb +14 -12
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +21 -31
- data/lib/datadog/tracing/contrib/httpclient/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/httpclient/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +20 -9
- data/lib/datadog/tracing/contrib/httprb/ext.rb +14 -12
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +20 -32
- data/lib/datadog/tracing/contrib/httprb/integration.rb +9 -6
- data/lib/datadog/tracing/contrib/httprb/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/integration.rb +3 -3
- data/lib/datadog/tracing/contrib/kafka/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/kafka/event.rb +5 -4
- data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +4 -4
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +5 -5
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +5 -5
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +6 -6
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/join_group.rb +6 -6
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/leave_group.rb +6 -6
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/sync_group.rb +6 -6
- data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +4 -4
- data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +4 -4
- data/lib/datadog/tracing/contrib/kafka/events.rb +9 -9
- data/lib/datadog/tracing/contrib/kafka/ext.rb +40 -40
- data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
- data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
- data/lib/datadog/tracing/contrib/kafka/integration.rb +8 -5
- data/lib/datadog/tracing/contrib/kafka/patcher.rb +17 -3
- data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +31 -0
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +50 -0
- data/lib/datadog/tracing/contrib/karafka/ext.rb +28 -0
- data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
- data/lib/datadog/tracing/contrib/karafka/integration.rb +45 -0
- data/lib/datadog/tracing/contrib/karafka/monitor.rb +77 -0
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +102 -0
- data/lib/datadog/tracing/contrib/karafka.rb +38 -0
- data/lib/datadog/tracing/contrib/lograge/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/lograge/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +1 -1
- data/lib/datadog/tracing/contrib/lograge/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +13 -11
- data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +11 -3
- data/lib/datadog/tracing/contrib/mongodb/ext.rb +19 -18
- data/lib/datadog/tracing/contrib/mongodb/instrumentation.rb +3 -3
- data/lib/datadog/tracing/contrib/mongodb/integration.rb +9 -6
- data/lib/datadog/tracing/contrib/mongodb/parsers.rb +10 -10
- data/lib/datadog/tracing/contrib/mongodb/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +38 -24
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +9 -3
- data/lib/datadog/tracing/contrib/mysql2/ext.rb +12 -12
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +25 -18
- data/lib/datadog/tracing/contrib/mysql2/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/mysql2/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +19 -2
- data/lib/datadog/tracing/contrib/opensearch/ext.rb +31 -21
- data/lib/datadog/tracing/contrib/opensearch/integration.rb +8 -6
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +75 -77
- data/lib/datadog/tracing/contrib/opensearch/quantize.rb +9 -9
- data/lib/datadog/tracing/contrib/patcher.rb +14 -13
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +9 -3
- data/lib/datadog/tracing/contrib/pg/ext.rb +19 -19
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +21 -24
- data/lib/datadog/tracing/contrib/pg/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/pg/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/presto/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/presto/ext.rb +22 -22
- data/lib/datadog/tracing/contrib/presto/instrumentation.rb +11 -13
- data/lib/datadog/tracing/contrib/presto/integration.rb +8 -5
- data/lib/datadog/tracing/contrib/presto/patcher.rb +4 -4
- data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +2 -2
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +26 -13
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +7 -3
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +29 -5
- data/lib/datadog/tracing/contrib/que/configuration/settings.rb +7 -4
- data/lib/datadog/tracing/contrib/que/ext.rb +19 -18
- data/lib/datadog/tracing/contrib/que/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/que/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/que/tracer.rb +2 -1
- data/lib/datadog/tracing/contrib/racecar/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/racecar/event.rb +6 -10
- data/lib/datadog/tracing/contrib/racecar/events/batch.rb +5 -5
- data/lib/datadog/tracing/contrib/racecar/events/consume.rb +4 -4
- data/lib/datadog/tracing/contrib/racecar/events/message.rb +5 -5
- data/lib/datadog/tracing/contrib/racecar/events.rb +3 -3
- data/lib/datadog/tracing/contrib/racecar/ext.rb +18 -18
- data/lib/datadog/tracing/contrib/racecar/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/racecar/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +13 -3
- data/lib/datadog/tracing/contrib/rack/ext.rb +43 -15
- data/lib/datadog/tracing/contrib/rack/header_collection.rb +13 -3
- data/lib/datadog/tracing/contrib/rack/header_tagging.rb +55 -32
- data/lib/datadog/tracing/contrib/rack/integration.rb +7 -7
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +102 -63
- data/lib/datadog/tracing/contrib/rack/patcher.rb +5 -5
- data/lib/datadog/tracing/contrib/rack/request_queue.rb +8 -7
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +67 -0
- data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +130 -6
- data/lib/datadog/tracing/contrib/rails/auto_instrument_railtie.rb +1 -1
- data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +9 -6
- data/lib/datadog/tracing/contrib/rails/ext.rb +16 -14
- data/lib/datadog/tracing/contrib/rails/framework.rb +12 -12
- data/lib/datadog/tracing/contrib/rails/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/rails/log_injection.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/middlewares.rb +4 -4
- data/lib/datadog/tracing/contrib/rails/patcher.rb +11 -9
- data/lib/datadog/tracing/contrib/rails/railtie.rb +4 -4
- data/lib/datadog/tracing/contrib/rails/runner.rb +64 -40
- data/lib/datadog/tracing/contrib/rake/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/rake/ext.rb +12 -12
- data/lib/datadog/tracing/contrib/rake/instrumentation.rb +13 -11
- data/lib/datadog/tracing/contrib/rake/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/rake/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +4 -4
- data/lib/datadog/tracing/contrib/redis/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/redis/ext.rb +19 -18
- data/lib/datadog/tracing/contrib/redis/instrumentation.rb +6 -6
- data/lib/datadog/tracing/contrib/redis/integration.rb +7 -7
- data/lib/datadog/tracing/contrib/redis/patcher.rb +12 -12
- data/lib/datadog/tracing/contrib/redis/quantize.rb +6 -6
- data/lib/datadog/tracing/contrib/redis/tags.rb +7 -12
- data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +11 -9
- data/lib/datadog/tracing/contrib/registerable.rb +11 -0
- data/lib/datadog/tracing/contrib/registry.rb +1 -1
- data/lib/datadog/tracing/contrib/resque/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/resque/ext.rb +7 -7
- data/lib/datadog/tracing/contrib/resque/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/resque/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/resque/resque_job.rb +7 -6
- data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +7 -4
- data/lib/datadog/tracing/contrib/rest_client/ext.rb +13 -11
- data/lib/datadog/tracing/contrib/rest_client/integration.rb +8 -5
- data/lib/datadog/tracing/contrib/rest_client/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +18 -17
- data/lib/datadog/tracing/contrib/roda/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/roda/ext.rb +6 -5
- data/lib/datadog/tracing/contrib/roda/instrumentation.rb +12 -7
- data/lib/datadog/tracing/contrib/roda/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/roda/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/semantic_logger/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/semantic_logger/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +1 -1
- data/lib/datadog/tracing/contrib/semantic_logger/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/semantic_logger/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/sequel/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/sequel/database.rb +12 -11
- data/lib/datadog/tracing/contrib/sequel/dataset.rb +7 -6
- data/lib/datadog/tracing/contrib/sequel/ext.rb +12 -8
- data/lib/datadog/tracing/contrib/sequel/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/sequel/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/sequel/utils.rb +115 -15
- data/lib/datadog/tracing/contrib/shoryuken/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/shoryuken/ext.rb +12 -12
- data/lib/datadog/tracing/contrib/shoryuken/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/shoryuken/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +3 -2
- data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +15 -9
- data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +7 -3
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +13 -8
- data/lib/datadog/tracing/contrib/sidekiq/ext.rb +31 -29
- data/lib/datadog/tracing/contrib/sidekiq/integration.rb +7 -7
- data/lib/datadog/tracing/contrib/sidekiq/patcher.rb +8 -8
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/heartbeat.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/job_fetch.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/redis_info.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/scheduled_poller.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/stop.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +19 -15
- data/lib/datadog/tracing/contrib/sidekiq/utils.rb +10 -10
- data/lib/datadog/tracing/contrib/sidekiq.rb +7 -6
- data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +7 -3
- data/lib/datadog/tracing/contrib/sinatra/env.rb +6 -6
- data/lib/datadog/tracing/contrib/sinatra/ext.rb +17 -16
- data/lib/datadog/tracing/contrib/sinatra/framework.rb +2 -2
- data/lib/datadog/tracing/contrib/sinatra/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/sinatra/patcher.rb +5 -5
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +11 -10
- data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +46 -45
- data/lib/datadog/tracing/contrib/sneakers/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/sneakers/ext.rb +12 -12
- data/lib/datadog/tracing/contrib/sneakers/integration.rb +19 -8
- data/lib/datadog/tracing/contrib/sneakers/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/sneakers/tracer.rb +4 -3
- data/lib/datadog/tracing/contrib/span_attribute_schema.rb +9 -4
- data/lib/datadog/tracing/contrib/status_range_env_parser.rb +2 -2
- data/lib/datadog/tracing/contrib/status_range_matcher.rb +13 -1
- data/lib/datadog/tracing/contrib/stripe/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/stripe/ext.rb +12 -12
- data/lib/datadog/tracing/contrib/stripe/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/stripe/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/stripe/request.rb +5 -5
- data/lib/datadog/tracing/contrib/sucker_punch/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/sucker_punch/exception_handler.rb +1 -1
- data/lib/datadog/tracing/contrib/sucker_punch/ext.rb +13 -13
- data/lib/datadog/tracing/contrib/sucker_punch/instrumentation.rb +5 -4
- data/lib/datadog/tracing/contrib/sucker_punch/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/sucker_punch/patcher.rb +4 -4
- data/lib/datadog/tracing/contrib/support.rb +28 -0
- data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +9 -3
- data/lib/datadog/tracing/contrib/trilogy/ext.rb +12 -12
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +10 -12
- data/lib/datadog/tracing/contrib/trilogy/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/trilogy/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/utils/database.rb +5 -5
- data/lib/datadog/tracing/contrib/utils/quantization/{hash.rb → hash_formatter.rb} +16 -14
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +16 -16
- data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +31 -0
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +50 -0
- data/lib/datadog/tracing/contrib/waterdrop/ext.rb +18 -0
- data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
- data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
- data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +49 -0
- data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
- data/lib/datadog/tracing/contrib/waterdrop.rb +42 -0
- data/lib/datadog/tracing/contrib.rb +62 -52
- data/lib/datadog/tracing/correlation.rb +19 -12
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +21 -13
- data/lib/datadog/tracing/diagnostics/ext.rb +19 -19
- data/lib/datadog/tracing/diagnostics/health.rb +1 -1
- data/lib/datadog/tracing/distributed/b3_multi.rb +9 -9
- data/lib/datadog/tracing/distributed/b3_single.rb +6 -6
- data/lib/datadog/tracing/distributed/baggage.rb +252 -0
- data/lib/datadog/tracing/distributed/datadog.rb +39 -36
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +12 -27
- data/lib/datadog/tracing/distributed/fetcher.rb +1 -1
- data/lib/datadog/tracing/distributed/helpers.rb +4 -4
- data/lib/datadog/tracing/distributed/none.rb +4 -2
- data/lib/datadog/tracing/distributed/propagation.rb +70 -14
- data/lib/datadog/tracing/distributed/propagation_policy.rb +42 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +126 -97
- data/lib/datadog/tracing/event.rb +6 -8
- data/lib/datadog/tracing/ext.rb +9 -0
- data/lib/datadog/tracing/flush.rb +1 -1
- data/lib/datadog/tracing/metadata/analytics.rb +3 -3
- data/lib/datadog/tracing/metadata/errors.rb +8 -8
- data/lib/datadog/tracing/metadata/ext.rb +86 -59
- data/lib/datadog/tracing/metadata/metastruct.rb +36 -0
- data/lib/datadog/tracing/metadata/metastruct_tagging.rb +42 -0
- data/lib/datadog/tracing/metadata/tagging.rb +8 -8
- data/lib/datadog/tracing/metadata.rb +5 -3
- data/lib/datadog/tracing/pipeline/span_filter.rb +5 -3
- data/lib/datadog/tracing/pipeline/span_processor.rb +3 -1
- data/lib/datadog/tracing/pipeline.rb +4 -4
- data/lib/datadog/tracing/remote.rb +47 -17
- data/lib/datadog/tracing/sampling/all_sampler.rb +1 -1
- data/lib/datadog/tracing/sampling/ext.rb +9 -9
- data/lib/datadog/tracing/sampling/matcher.rb +4 -4
- data/lib/datadog/tracing/sampling/priority_sampler.rb +20 -5
- data/lib/datadog/tracing/sampling/rate_by_key_sampler.rb +3 -3
- data/lib/datadog/tracing/sampling/rate_by_service_sampler.rb +2 -2
- data/lib/datadog/tracing/sampling/rate_sampler.rb +9 -19
- data/lib/datadog/tracing/sampling/rule.rb +5 -5
- data/lib/datadog/tracing/sampling/rule_sampler.rb +88 -59
- data/lib/datadog/tracing/sampling/sampler.rb +2 -2
- data/lib/datadog/tracing/sampling/span/ext.rb +3 -3
- data/lib/datadog/tracing/sampling/span/matcher.rb +2 -2
- data/lib/datadog/tracing/sampling/span/rule.rb +1 -2
- data/lib/datadog/tracing/sampling/span/rule_parser.rb +11 -11
- data/lib/datadog/tracing/sampling/span/sampler.rb +0 -7
- data/lib/datadog/tracing/span.rb +28 -11
- data/lib/datadog/tracing/span_event.rb +125 -5
- data/lib/datadog/tracing/span_link.rb +12 -12
- data/lib/datadog/tracing/span_operation.rb +119 -39
- data/lib/datadog/tracing/sync_writer.rb +14 -11
- data/lib/datadog/tracing/trace_digest.rb +36 -24
- data/lib/datadog/tracing/trace_operation.rb +243 -112
- data/lib/datadog/tracing/trace_segment.rb +18 -16
- data/lib/datadog/tracing/tracer.rb +181 -72
- data/lib/datadog/tracing/transport/http/client.rb +17 -30
- data/lib/datadog/tracing/transport/http/statistics.rb +2 -2
- data/lib/datadog/tracing/transport/http/traces.rb +11 -90
- data/lib/datadog/tracing/transport/http.rb +26 -77
- data/lib/datadog/tracing/transport/io/client.rb +13 -16
- data/lib/datadog/tracing/transport/io/response.rb +1 -1
- data/lib/datadog/tracing/transport/io/traces.rb +32 -38
- data/lib/datadog/tracing/transport/io.rb +3 -3
- data/lib/datadog/tracing/transport/serializable_trace.rb +47 -23
- data/lib/datadog/tracing/transport/statistics.rb +3 -3
- data/lib/datadog/tracing/transport/trace_formatter.rb +36 -7
- data/lib/datadog/tracing/transport/traces.rb +56 -90
- data/lib/datadog/tracing/utils.rb +3 -3
- data/lib/datadog/tracing/workers.rb +11 -9
- data/lib/datadog/tracing/writer.rb +15 -11
- data/lib/datadog/tracing.rb +19 -6
- data/lib/datadog/version.rb +4 -4
- data/lib/datadog.rb +20 -7
- metadata +327 -69
- data/ext/datadog_profiling_loader/datadog_profiling_loader.c +0 -142
- data/ext/datadog_profiling_loader/extconf.rb +0 -60
- data/ext/datadog_profiling_native_extension/clock_id_noop.c +0 -21
- data/ext/libdatadog_api/macos_development.md +0 -26
- data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -92
- data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -114
- data/lib/datadog/appsec/configuration/settings.rb +0 -215
- data/lib/datadog/appsec/contrib/devise/event.rb +0 -57
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +0 -77
- data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +0 -54
- data/lib/datadog/appsec/contrib/devise/resource.rb +0 -35
- data/lib/datadog/appsec/contrib/devise/tracking.rb +0 -57
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +0 -46
- data/lib/datadog/appsec/contrib/patcher.rb +0 -12
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +0 -69
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +0 -47
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +0 -53
- data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +0 -53
- data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +0 -48
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +0 -45
- data/lib/datadog/appsec/processor/context.rb +0 -107
- data/lib/datadog/appsec/processor/rule_merger.rb +0 -170
- data/lib/datadog/appsec/processor.rb +0 -106
- data/lib/datadog/appsec/reactive/address_hash.rb +0 -22
- data/lib/datadog/appsec/reactive/engine.rb +0 -47
- data/lib/datadog/appsec/reactive/operation.rb +0 -68
- data/lib/datadog/appsec/reactive/subscriber.rb +0 -19
- data/lib/datadog/appsec/scope.rb +0 -58
- data/lib/datadog/appsec/utils/trace_operation.rb +0 -15
- data/lib/datadog/core/crashtracking/agent_base_url.rb +0 -21
- data/lib/datadog/core/remote/transport/http/api/instance.rb +0 -39
- data/lib/datadog/core/remote/transport/http/api/spec.rb +0 -21
- data/lib/datadog/core/remote/transport/http/api.rb +0 -58
- data/lib/datadog/core/remote/transport/http/builder.rb +0 -219
- data/lib/datadog/core/remote/transport/http/client.rb +0 -48
- data/lib/datadog/core/telemetry/http/env.rb +0 -20
- data/lib/datadog/core/telemetry/http/ext.rb +0 -28
- data/lib/datadog/core/telemetry/http/response.rb +0 -70
- data/lib/datadog/core/telemetry/http/transport.rb +0 -90
- data/lib/datadog/core/utils/base64.rb +0 -22
- data/lib/datadog/di/configuration/settings.rb +0 -188
- data/lib/datadog/di/transport.rb +0 -81
- data/lib/datadog/tracing/contrib/rails/utils.rb +0 -26
- data/lib/datadog/tracing/transport/http/api/spec.rb +0 -19
- data/lib/datadog/tracing/transport/http/api.rb +0 -43
- data/lib/datadog/tracing/workers/trace_writer.rb +0 -196
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#include <ruby.h>
|
|
2
|
+
#include <ruby/debug.h>
|
|
2
3
|
|
|
4
|
+
#include "datadog_ruby_common.h"
|
|
3
5
|
#include "collectors_thread_context.h"
|
|
4
6
|
#include "clock_id.h"
|
|
5
7
|
#include "collectors_stack.h"
|
|
@@ -7,8 +9,11 @@
|
|
|
7
9
|
#include "helpers.h"
|
|
8
10
|
#include "libdatadog_helpers.h"
|
|
9
11
|
#include "private_vm_api_access.h"
|
|
12
|
+
#include "ruby_helpers.h"
|
|
10
13
|
#include "stack_recorder.h"
|
|
11
14
|
#include "time_helpers.h"
|
|
15
|
+
#include "unsafe_api_calls_check.h"
|
|
16
|
+
#include "extconf.h"
|
|
12
17
|
|
|
13
18
|
// Used to trigger sampling of threads, based on external "events", such as:
|
|
14
19
|
// * periodic timer for cpu-time and wall-time
|
|
@@ -38,11 +43,8 @@
|
|
|
38
43
|
// When `thread_context_collector_on_gc_start` gets called, the current cpu and wall-time get recorded to the thread
|
|
39
44
|
// context: `cpu_time_at_gc_start_ns` and `wall_time_at_gc_start_ns`.
|
|
40
45
|
//
|
|
41
|
-
// While `cpu_time_at_gc_start_ns` is set,
|
|
42
|
-
//
|
|
43
|
-
// Wall-time accounting is not affected by this (e.g. we still record 60 seconds every 60 seconds).
|
|
44
|
-
//
|
|
45
|
-
// (Regular samples can still account for the cpu-time between the previous sample and the start of GC.)
|
|
46
|
+
// While `cpu_time_at_gc_start_ns` is set, we don't expect the thread to be sampled: the VM is doing GC
|
|
47
|
+
// on the thread holding the GVL so no other samples can/will be triggered until GC finishes.
|
|
46
48
|
//
|
|
47
49
|
// When `thread_context_collector_on_gc_finish` gets called, the cpu-time and wall-time spent during GC gets recorded
|
|
48
50
|
// into the global gc_tracking structure, and further samples are not affected. (The `cpu_time_at_previous_sample_ns`
|
|
@@ -71,15 +73,14 @@
|
|
|
71
73
|
|
|
72
74
|
#define THREAD_ID_LIMIT_CHARS 44 // Why 44? "#{2**64} (#{2**64})".size + 1 for \0
|
|
73
75
|
#define THREAD_INVOKE_LOCATION_LIMIT_CHARS 512
|
|
74
|
-
#define IS_WALL_TIME true
|
|
75
|
-
#define IS_NOT_WALL_TIME false
|
|
76
76
|
#define MISSING_TRACER_CONTEXT_KEY 0
|
|
77
77
|
#define TIME_BETWEEN_GC_EVENTS_NS MILLIS_AS_NS(10)
|
|
78
|
+
#define GVL_SUSPENDED ((uint64_t)1)
|
|
79
|
+
#define GVL_RUNNING ((uint64_t)0)
|
|
80
|
+
|
|
81
|
+
#define MAX(a, b) ((a) < (b) ? (b) : (a))
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
// (e.g. to avoid trying to gvl profile threads that are not from the main Ractor)
|
|
81
|
-
// and for which there's no data yet
|
|
82
|
-
#define GVL_WAITING_ENABLED_EMPTY RUBY_FIXNUM_MAX
|
|
83
|
+
static ID dd_per_thread_context_id; // Hidden ivar (no @ prefix, inaccessible from Ruby)
|
|
83
84
|
|
|
84
85
|
static ID at_active_span_id; // id of :@active_span in Ruby
|
|
85
86
|
static ID at_active_trace_id; // id of :@active_trace in Ruby
|
|
@@ -101,44 +102,47 @@ static ID at_kind_id; // id of :@kind in Ruby
|
|
|
101
102
|
static ID at_name_id; // id of :@name in Ruby
|
|
102
103
|
static ID server_id; // id of :server in Ruby
|
|
103
104
|
static ID otel_context_storage_id; // id of :__opentelemetry_context_storage__ in Ruby
|
|
105
|
+
static ID otel_fiber_context_storage_id; // id of :@opentelemetry_context in Ruby
|
|
106
|
+
|
|
107
|
+
// This is mutable and gets set last-writer-wins style by
|
|
108
|
+
// `thread_context_collector_reset_all_per_thread_contexts`, which is called whenever
|
|
109
|
+
// profiling is starting or restating.
|
|
110
|
+
//
|
|
111
|
+
// Note: We must be careful to not change this value while the profiler is still running,
|
|
112
|
+
// otherwise new threads can get the new value and cause profiling to stop with an
|
|
113
|
+
// exception due to the mismatched size.
|
|
114
|
+
//
|
|
115
|
+
// The initial value should be kept in sync with the default for DD_PROFILING_MAX_FRAMES
|
|
116
|
+
// in settings.rb. See `initialize_context` for details on why this is needed/used.
|
|
117
|
+
static uint16_t latest_max_frames = 400;
|
|
104
118
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
// mean this setting is shared among all thread context collectors, and thus it's "last writer wins".
|
|
108
|
-
// In production this should not be a problem: there should only be one profiler, which is the last one created,
|
|
109
|
-
// and that'll be the one that last wrote this setting.
|
|
110
|
-
static uint32_t global_waiting_for_gvl_threshold_ns = MILLIS_AS_NS(10);
|
|
119
|
+
// Global tracepoint for RUBY_EVENT_THREAD_BEGIN. Created and enabled once when the first ThreadContext collector is initialized.
|
|
120
|
+
static VALUE thread_begin_tracepoint = Qnil;
|
|
111
121
|
|
|
112
122
|
typedef enum { OTEL_CONTEXT_ENABLED_FALSE, OTEL_CONTEXT_ENABLED_ONLY, OTEL_CONTEXT_ENABLED_BOTH } otel_context_enabled;
|
|
123
|
+
typedef enum { OTEL_CONTEXT_SOURCE_UNKNOWN, OTEL_CONTEXT_SOURCE_FIBER_IVAR, OTEL_CONTEXT_SOURCE_FIBER_LOCAL } otel_context_source;
|
|
113
124
|
|
|
114
125
|
// Contains state for a single ThreadContext instance
|
|
115
|
-
struct
|
|
126
|
+
typedef struct {
|
|
116
127
|
// Note: Places in this file that usually need to be changed when this struct is changed are tagged with
|
|
117
128
|
// "Update this when modifying state struct"
|
|
118
129
|
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
uint16_t max_frames;
|
|
122
|
-
// Hashmap <Thread Object, struct per_thread_context>
|
|
123
|
-
st_table *hash_map_per_thread_context;
|
|
130
|
+
// Output buffer for stack traces, passed to sample_thread()
|
|
131
|
+
sample_locations locations;
|
|
124
132
|
// Datadog::Profiling::StackRecorder instance
|
|
125
133
|
VALUE recorder_instance;
|
|
126
134
|
// If the tracer is available and enabled, this will be the fiber-local symbol for accessing its running context,
|
|
127
135
|
// to enable code hotspots and endpoint aggregation.
|
|
128
136
|
// When not available, this is set to MISSING_TRACER_CONTEXT_KEY.
|
|
129
137
|
ID tracer_context_key;
|
|
130
|
-
// Track how many regular samples we've taken. Does not include garbage collection samples.
|
|
131
|
-
// Currently **outside** of stats struct because we also use it to decide when to clean the contexts, and thus this
|
|
132
|
-
// is not (just) a stat.
|
|
133
|
-
unsigned int sample_count;
|
|
134
138
|
// Reusable array to get list of threads
|
|
135
139
|
VALUE thread_list_buffer;
|
|
136
140
|
// Used to omit endpoint names (retrieved from tracer) from collected data
|
|
137
141
|
bool endpoint_collection_enabled;
|
|
138
|
-
// Used to omit timestamps / timeline events from collected data
|
|
139
|
-
bool timeline_enabled;
|
|
140
142
|
// Used to control context collection
|
|
141
143
|
otel_context_enabled otel_context_enabled;
|
|
144
|
+
// Used to remember where otel context is being stored after we observe it the first time
|
|
145
|
+
otel_context_source otel_context_source;
|
|
142
146
|
// Used when calling monotonic_to_system_epoch_ns
|
|
143
147
|
monotonic_to_system_epoch_state time_converter_state;
|
|
144
148
|
// Used to identify the main thread, to give it a fallback name
|
|
@@ -147,12 +151,26 @@ struct thread_context_collector_state {
|
|
|
147
151
|
// Qtrue serves as a marker we've not yet extracted it; when we try to extract it, we set it to an object if
|
|
148
152
|
// successful and Qnil if not.
|
|
149
153
|
VALUE otel_current_span_key;
|
|
154
|
+
// Used to enable native filenames in stack traces
|
|
155
|
+
bool native_filenames_enabled;
|
|
156
|
+
// Used to cache native filename lookup results (Map[void *function_pointer, char *filename])
|
|
157
|
+
st_table *native_filenames_cache;
|
|
158
|
+
// Used to attribute overhead during sampling to this component
|
|
159
|
+
VALUE overhead_filename;
|
|
160
|
+
// Minimum duration of a "Waiting for GVL" period to trigger a sample
|
|
161
|
+
uint32_t waiting_for_gvl_threshold_ns;
|
|
150
162
|
|
|
151
163
|
struct stats {
|
|
164
|
+
// Track how many regular samples we've taken. Does not include garbage collection samples.
|
|
165
|
+
unsigned int sample_count;
|
|
152
166
|
// Track how many garbage collection samples we've taken.
|
|
153
167
|
unsigned int gc_samples;
|
|
154
168
|
// See thread_context_collector_on_gc_start for details
|
|
155
169
|
unsigned int gc_samples_missed_due_to_missing_context;
|
|
170
|
+
// How many per-thread samples were skipped because the thread has been continuously suspended
|
|
171
|
+
// (no GVL) since its previous sample, so its Ruby stack cannot have changed.
|
|
172
|
+
unsigned int inactive_thread_samples_skipped;
|
|
173
|
+
unsigned int profiler_thread_samples_skipped;
|
|
156
174
|
} stats;
|
|
157
175
|
|
|
158
176
|
struct {
|
|
@@ -162,18 +180,92 @@ struct thread_context_collector_state {
|
|
|
162
180
|
long wall_time_at_previous_gc_ns; // Will be INVALID_TIME unless there's accumulated time above
|
|
163
181
|
long wall_time_at_last_flushed_gc_event_ns; // Starts at 0 and then will always be valid
|
|
164
182
|
} gc_tracking;
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
// Tracks per-thread state
|
|
183
|
+
} thread_context_collector_state;
|
|
184
|
+
|
|
185
|
+
// Tracks per-thread state.
|
|
186
|
+
// This state is global and lives forever on the Ruby Thread (until the Thread is GC'd).
|
|
187
|
+
// The state is created early on for all threads on the main Ractor
|
|
188
|
+
// (enabling a TracePoint only enables it for the current Ractor).
|
|
189
|
+
// The state is either created when the Thread starts running (via the RUBY_EVENT_THREAD_BEGIN TracePoint),
|
|
190
|
+
// or via `thread_context_collector_reset_all_per_thread_contexts` when the profiler starts.
|
|
191
|
+
//
|
|
192
|
+
// Unfortunately that RUBY_EVENT_THREAD_BEGIN TracePoint still fires after some other events:
|
|
193
|
+
// * RUBY_INTERNAL_THREAD_EVENT_RESUMED for the Thread acquiring the GVL for the first time
|
|
194
|
+
// * an early SIGPROF calling handle_sampling_signal()
|
|
195
|
+
// * Ruby might check for interrupts and run postponed jobs (e.g. thread_context_collector_sample)
|
|
196
|
+
// * another RUBY_EVENT_THREAD_BEGIN TracePoint which might run before ours
|
|
197
|
+
// * etc
|
|
198
|
+
// For those cases we have to ignore those events and we cannot assume the state is always set,
|
|
199
|
+
// however this only matters for a very short period when a thread starts.
|
|
168
200
|
struct per_thread_context {
|
|
169
|
-
sampling_buffer
|
|
201
|
+
sampling_buffer sampling_buffer;
|
|
170
202
|
char thread_id[THREAD_ID_LIMIT_CHARS];
|
|
171
203
|
ddog_CharSlice thread_id_char_slice;
|
|
172
204
|
char thread_invoke_location[THREAD_INVOKE_LOCATION_LIMIT_CHARS];
|
|
173
205
|
ddog_CharSlice thread_invoke_location_char_slice;
|
|
174
206
|
thread_cpu_time_id thread_cpu_time_id;
|
|
175
|
-
long cpu_time_at_previous_sample_ns;
|
|
176
|
-
long wall_time_at_previous_sample_ns;
|
|
207
|
+
long cpu_time_at_previous_sample_ns;
|
|
208
|
+
long wall_time_at_previous_sample_ns;
|
|
209
|
+
|
|
210
|
+
// There are 3 possible states for the GVL (per thread), and 3 transitions for which we receive GVL events:
|
|
211
|
+
// Thread holds the GVL
|
|
212
|
+
// on_gvl_released() the thread releases the GVL (RUBY_INTERNAL_THREAD_EVENT_SUSPENDED)
|
|
213
|
+
// Thread runs without the GVL
|
|
214
|
+
// on_gvl_waiting() the thread wants the GVL (RUBY_INTERNAL_THREAD_EVENT_READY)
|
|
215
|
+
// Thread is "Waiting for GVL"
|
|
216
|
+
// on_gvl_running() the thread now got the GVL (RUBY_INTERNAL_THREAD_EVENT_RESUMED)
|
|
217
|
+
// ... and the cycle restarts
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
// --- GVL waiting tracking state machine ---
|
|
221
|
+
//
|
|
222
|
+
// gvl_waiting_at tracks the GVL wait state for each profiled thread:
|
|
223
|
+
//
|
|
224
|
+
// ┌───────────────────────────────────┐
|
|
225
|
+
// │ on_gvl_waiting │
|
|
226
|
+
// │ ▼
|
|
227
|
+
// Not Waiting (0) ◀────────────────── Waiting (> 0)
|
|
228
|
+
// ▲ on_gvl_running │
|
|
229
|
+
// │ (below threshold) │ on_gvl_running (above threshold)
|
|
230
|
+
// │ ▼
|
|
231
|
+
// └─────────────────────────── Sample Pending (< 0)
|
|
232
|
+
// sample / sample_after_gvl_running
|
|
233
|
+
//
|
|
234
|
+
// Not Waiting (0): thread is running or not waiting for the GVL
|
|
235
|
+
// Waiting (> 0): monotonic wall time (ns) when the thread started waiting
|
|
236
|
+
// Sample Pending (< 0): negated timestamp; the wait ended and a sample is pending
|
|
237
|
+
//
|
|
238
|
+
// The field is accessed under the GVL for most functions EXCEPT on_gvl_waiting() which writes to it without the GVL.
|
|
239
|
+
// So we need to pack the above state in a single long to ensure atomicity.
|
|
240
|
+
long gvl_waiting_at;
|
|
241
|
+
|
|
242
|
+
// Per-thread "state + version" word, updated on every GVL state transition. The encoding is:
|
|
243
|
+
// - low bit: current state (1 = currently suspended, 0 = currently running)
|
|
244
|
+
// - bits 1+: monotonic event counter (incremented on every RESUMED)
|
|
245
|
+
// The hooks set the state bit explicitly rather than relying on parity, so the encoding stays
|
|
246
|
+
// correct even when events are not paired properly (as in tests).
|
|
247
|
+
//
|
|
248
|
+
// Note that SUSPENDED can happen multiple times in a row on Ruby 3.2,
|
|
249
|
+
// see https://github.com/DataDog/dd-trace-rb/pull/5777#discussion_r3388560254,
|
|
250
|
+
// the encoding is designed to naturally not change the field in such a case.
|
|
251
|
+
uint64_t gvl_state_change_count;
|
|
252
|
+
// Snapshot of the thread's gvl_state_change_count at the moment we last sampled it.
|
|
253
|
+
// Equality with this snapshot means no GVL transition since the last sample.
|
|
254
|
+
uint64_t gvl_state_change_count_at_previous_sample;
|
|
255
|
+
// True when the previous per-tick sample was skipped by the SUSPENDED-skip optimization, so the
|
|
256
|
+
// flush-before-serialize pass knows it needs to report this thread.
|
|
257
|
+
// As a result, we will accumulate all wall & CPU time as a single batch per reporting period,
|
|
258
|
+
// but this is deemed worth it for this optimization. In any case we don't know exactly
|
|
259
|
+
// at what time a thread was doing CPU work (unless it's on CPU 100% of the time).
|
|
260
|
+
bool was_skipped_at_last_sample;
|
|
261
|
+
// Set as true for CpuAndWallTimeWorker and IdleSamplingHelper threads.
|
|
262
|
+
// When true, per-tick samples are skipped entirely; the thread is sampled only once per
|
|
263
|
+
// reporting period during the on_serialize flush.
|
|
264
|
+
//
|
|
265
|
+
// These threads are always in native code so their stacks aren't interesting;
|
|
266
|
+
// the Profiling::Scheduler thread on the other hand does a lot of different
|
|
267
|
+
// things using a mix of Ruby and native code, so that one isn't considered internal.
|
|
268
|
+
bool is_profiler_internal_thread;
|
|
177
269
|
|
|
178
270
|
struct {
|
|
179
271
|
// Both of these fields are set by on_gc_start and kept until on_gc_finish is called.
|
|
@@ -184,44 +276,41 @@ struct per_thread_context {
|
|
|
184
276
|
};
|
|
185
277
|
|
|
186
278
|
// Used to correlate profiles with traces
|
|
187
|
-
struct
|
|
279
|
+
typedef struct {
|
|
188
280
|
bool valid;
|
|
189
281
|
uint64_t local_root_span_id;
|
|
190
282
|
uint64_t span_id;
|
|
191
283
|
VALUE trace_endpoint;
|
|
192
|
-
};
|
|
284
|
+
} trace_identifiers;
|
|
193
285
|
|
|
194
|
-
struct
|
|
286
|
+
typedef struct {
|
|
195
287
|
VALUE span;
|
|
196
288
|
VALUE span_id;
|
|
197
289
|
VALUE trace_id;
|
|
198
|
-
};
|
|
290
|
+
} otel_span;
|
|
199
291
|
|
|
200
292
|
static void thread_context_collector_typed_data_mark(void *state_ptr);
|
|
201
293
|
static void thread_context_collector_typed_data_free(void *state_ptr);
|
|
202
|
-
static
|
|
203
|
-
static
|
|
294
|
+
static void per_thread_context_typed_data_mark(void *ctx_ptr);
|
|
295
|
+
static void per_thread_context_typed_data_free(void *ctx_ptr);
|
|
204
296
|
static VALUE _native_new(VALUE klass);
|
|
205
297
|
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
|
206
|
-
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE
|
|
298
|
+
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE allow_exception);
|
|
207
299
|
static VALUE _native_on_gc_start(VALUE self, VALUE collector_instance);
|
|
208
300
|
static VALUE _native_on_gc_finish(VALUE self, VALUE collector_instance);
|
|
209
|
-
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE
|
|
301
|
+
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE allow_exception);
|
|
210
302
|
static void update_metrics_and_sample(
|
|
211
|
-
|
|
303
|
+
thread_context_collector_state *state,
|
|
212
304
|
VALUE thread_being_sampled,
|
|
213
|
-
|
|
214
|
-
struct per_thread_context *thread_context,
|
|
215
|
-
sampling_buffer* sampling_buffer,
|
|
305
|
+
per_thread_context *thread_context,
|
|
216
306
|
long current_cpu_time_ns,
|
|
217
|
-
long current_monotonic_wall_time_ns
|
|
307
|
+
long current_monotonic_wall_time_ns,
|
|
308
|
+
bool force_sample
|
|
218
309
|
);
|
|
219
310
|
static void trigger_sample_for_thread(
|
|
220
|
-
|
|
221
|
-
VALUE
|
|
222
|
-
|
|
223
|
-
struct per_thread_context *thread_context,
|
|
224
|
-
sampling_buffer* sampling_buffer,
|
|
311
|
+
thread_context_collector_state *state,
|
|
312
|
+
VALUE thread_being_sampled,
|
|
313
|
+
per_thread_context *thread_context,
|
|
225
314
|
sample_values values,
|
|
226
315
|
long current_monotonic_wall_time_ns,
|
|
227
316
|
ddog_CharSlice *ruby_vm_type,
|
|
@@ -230,37 +319,34 @@ static void trigger_sample_for_thread(
|
|
|
230
319
|
bool is_safe_to_allocate_objects
|
|
231
320
|
);
|
|
232
321
|
static VALUE _native_thread_list(VALUE self);
|
|
233
|
-
static
|
|
234
|
-
static
|
|
235
|
-
static void initialize_context(VALUE thread,
|
|
236
|
-
static void free_context(struct per_thread_context* thread_context);
|
|
322
|
+
static void check_frozen_thread(VALUE thread);
|
|
323
|
+
static per_thread_context *get_or_create_context_for(VALUE thread);
|
|
324
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context);
|
|
237
325
|
static VALUE _native_inspect(VALUE self, VALUE collector_instance);
|
|
238
|
-
static VALUE
|
|
239
|
-
static
|
|
240
|
-
static VALUE
|
|
241
|
-
static VALUE gc_tracking_as_ruby_hash(struct thread_context_collector_state *state);
|
|
242
|
-
static void remove_context_for_dead_threads(struct thread_context_collector_state *state);
|
|
243
|
-
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, st_data_t _argument);
|
|
326
|
+
static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context);
|
|
327
|
+
static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash);
|
|
328
|
+
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state);
|
|
244
329
|
static VALUE _native_per_thread_context(VALUE self, VALUE collector_instance);
|
|
245
|
-
static long
|
|
246
|
-
static long
|
|
330
|
+
static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns);
|
|
331
|
+
static long update_wall_time_since_previous_sample(per_thread_context *thread_context, long current_wall_time_ns);
|
|
332
|
+
static long cpu_time_now_ns(per_thread_context *thread_context);
|
|
247
333
|
static long thread_id_for(VALUE thread);
|
|
248
334
|
static VALUE _native_stats(VALUE self, VALUE collector_instance);
|
|
249
335
|
static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
|
|
250
336
|
static void trace_identifiers_for(
|
|
251
|
-
|
|
337
|
+
thread_context_collector_state *state,
|
|
252
338
|
VALUE thread,
|
|
253
|
-
|
|
339
|
+
trace_identifiers *trace_identifiers_result,
|
|
254
340
|
bool is_safe_to_allocate_objects
|
|
255
341
|
);
|
|
256
342
|
static bool should_collect_resource(VALUE root_span);
|
|
257
343
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
258
|
-
static VALUE thread_list(
|
|
344
|
+
static VALUE thread_list(thread_context_collector_state *state);
|
|
259
345
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object);
|
|
260
346
|
static VALUE _native_new_empty_thread(VALUE self);
|
|
261
347
|
static ddog_CharSlice ruby_value_type_to_class_name(enum ruby_value_type type);
|
|
262
348
|
static void ddtrace_otel_trace_identifiers_for(
|
|
263
|
-
|
|
349
|
+
thread_context_collector_state *state,
|
|
264
350
|
VALUE *active_trace,
|
|
265
351
|
VALUE *root_span,
|
|
266
352
|
VALUE *numeric_span_id,
|
|
@@ -270,30 +356,40 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
270
356
|
);
|
|
271
357
|
static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples);
|
|
272
358
|
static bool handle_gvl_waiting(
|
|
273
|
-
|
|
359
|
+
thread_context_collector_state *state,
|
|
274
360
|
VALUE thread_being_sampled,
|
|
275
|
-
|
|
276
|
-
struct per_thread_context *thread_context,
|
|
277
|
-
sampling_buffer* sampling_buffer,
|
|
361
|
+
per_thread_context *thread_context,
|
|
278
362
|
long current_cpu_time_ns
|
|
279
363
|
);
|
|
280
364
|
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
281
|
-
static VALUE
|
|
282
|
-
|
|
283
|
-
static VALUE
|
|
284
|
-
static VALUE
|
|
365
|
+
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
366
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
|
367
|
+
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
368
|
+
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
|
|
369
|
+
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE allow_exception);
|
|
370
|
+
#endif
|
|
371
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns);
|
|
285
372
|
static void otel_without_ddtrace_trace_identifiers_for(
|
|
286
|
-
|
|
373
|
+
thread_context_collector_state *state,
|
|
287
374
|
VALUE thread,
|
|
288
|
-
|
|
375
|
+
trace_identifiers *trace_identifiers_result,
|
|
289
376
|
bool is_safe_to_allocate_objects
|
|
290
377
|
);
|
|
291
|
-
static
|
|
378
|
+
static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
|
|
292
379
|
static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
|
|
380
|
+
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
|
|
381
|
+
static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
382
|
+
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
383
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
384
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
385
|
+
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
386
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample);
|
|
387
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused);
|
|
293
388
|
|
|
294
389
|
void collectors_thread_context_init(VALUE profiling_module) {
|
|
295
390
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
|
296
391
|
VALUE collectors_thread_context_class = rb_define_class_under(collectors_module, "ThreadContext", rb_cObject);
|
|
392
|
+
|
|
297
393
|
// Hosts methods used for testing the native code using RSpec
|
|
298
394
|
VALUE testing_module = rb_define_module_under(collectors_thread_context_class, "Testing");
|
|
299
395
|
|
|
@@ -321,13 +417,19 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
321
417
|
rb_define_singleton_method(testing_module, "_native_gc_tracking", _native_gc_tracking, 1);
|
|
322
418
|
rb_define_singleton_method(testing_module, "_native_new_empty_thread", _native_new_empty_thread, 0);
|
|
323
419
|
rb_define_singleton_method(testing_module, "_native_sample_skipped_allocation_samples", _native_sample_skipped_allocation_samples, 2);
|
|
420
|
+
rb_define_singleton_method(testing_module, "_native_system_epoch_time_now_ns", _native_system_epoch_time_now_ns, 1);
|
|
421
|
+
rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler, 0);
|
|
422
|
+
rb_define_singleton_method(testing_module, "_native_remove_per_thread_context_for", _native_remove_per_thread_context_for, 1);
|
|
423
|
+
rb_define_singleton_method(testing_module, "_native_global_reset_per_thread_context", _native_global_reset_per_thread_context, 1);
|
|
424
|
+
rb_define_singleton_method(testing_module, "_native_mark_thread_as_profiler_internal", _native_mark_thread_as_profiler_internal, 1);
|
|
425
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
426
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_released", _native_on_gvl_released, 1);
|
|
324
427
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
325
|
-
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
326
428
|
rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
|
|
327
|
-
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running,
|
|
328
|
-
rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running,
|
|
329
|
-
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);
|
|
429
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 2);
|
|
430
|
+
rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 3);
|
|
330
431
|
#endif
|
|
432
|
+
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, 2);
|
|
331
433
|
|
|
332
434
|
at_active_span_id = rb_intern_const("@active_span");
|
|
333
435
|
at_active_trace_id = rb_intern_const("@active_trace");
|
|
@@ -346,16 +448,19 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
346
448
|
at_name_id = rb_intern_const("@name");
|
|
347
449
|
server_id = rb_intern_const("server");
|
|
348
450
|
otel_context_storage_id = rb_intern_const("__opentelemetry_context_storage__");
|
|
451
|
+
otel_fiber_context_storage_id = rb_intern_const("@opentelemetry_context");
|
|
349
452
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
453
|
+
dd_per_thread_context_id = rb_intern_const("dd_per_thread_context");
|
|
454
|
+
|
|
455
|
+
// This will raise if Ruby already ran out of thread-local keys
|
|
456
|
+
per_thread_context_tls_init();
|
|
457
|
+
|
|
458
|
+
rb_global_variable(&thread_begin_tracepoint);
|
|
354
459
|
|
|
355
460
|
gc_profiling_init();
|
|
356
461
|
}
|
|
357
462
|
|
|
358
|
-
// This structure is used to define a Ruby object that stores a pointer to a
|
|
463
|
+
// This structure is used to define a Ruby object that stores a pointer to a thread_context_collector_state
|
|
359
464
|
// See also https://github.com/ruby/ruby/blob/master/doc/extension.rdoc for how this works
|
|
360
465
|
static const rb_data_type_t thread_context_collector_typed_data = {
|
|
361
466
|
.wrap_struct_name = "Datadog::Profiling::Collectors::ThreadContext",
|
|
@@ -371,66 +476,91 @@ static const rb_data_type_t thread_context_collector_typed_data = {
|
|
|
371
476
|
// This function is called by the Ruby GC to give us a chance to mark any Ruby objects that we're holding on to,
|
|
372
477
|
// so that they don't get garbage collected
|
|
373
478
|
static void thread_context_collector_typed_data_mark(void *state_ptr) {
|
|
374
|
-
|
|
479
|
+
thread_context_collector_state *state = (thread_context_collector_state *) state_ptr;
|
|
375
480
|
|
|
376
481
|
// Update this when modifying state struct
|
|
377
482
|
rb_gc_mark(state->recorder_instance);
|
|
378
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_mark, 0 /* unused */);
|
|
379
483
|
rb_gc_mark(state->thread_list_buffer);
|
|
380
484
|
rb_gc_mark(state->main_thread);
|
|
381
485
|
rb_gc_mark(state->otel_current_span_key);
|
|
486
|
+
rb_gc_mark(state->overhead_filename);
|
|
382
487
|
}
|
|
383
488
|
|
|
384
489
|
static void thread_context_collector_typed_data_free(void *state_ptr) {
|
|
385
|
-
|
|
490
|
+
thread_context_collector_state *state = (thread_context_collector_state *) state_ptr;
|
|
386
491
|
|
|
387
492
|
// Update this when modifying state struct
|
|
388
493
|
|
|
389
494
|
// Important: Remember that we're only guaranteed to see here what's been set in _native_new, aka
|
|
390
495
|
// pointers that have been set NULL there may still be NULL here.
|
|
391
|
-
if (state->locations != NULL) ruby_xfree(state->locations);
|
|
496
|
+
if (state->locations.ptr != NULL) ruby_xfree(state->locations.ptr);
|
|
392
497
|
|
|
393
|
-
|
|
394
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
|
395
|
-
// ...and then the map
|
|
396
|
-
st_free_table(state->hash_map_per_thread_context);
|
|
498
|
+
st_free_table(state->native_filenames_cache);
|
|
397
499
|
|
|
398
500
|
ruby_xfree(state);
|
|
399
501
|
}
|
|
400
502
|
|
|
401
|
-
//
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
503
|
+
// per_thread_context is wrapped in a TypedData Ruby object stored as an ivar on each Ruby Thread.
|
|
504
|
+
// This gives us automatic GC marking (for sampling_buffer iseq VALUEs) and lifecycle management.
|
|
505
|
+
static const rb_data_type_t per_thread_context_typed_data = {
|
|
506
|
+
.wrap_struct_name = "Datadog::Profiling::PerThreadContext",
|
|
507
|
+
.function = {
|
|
508
|
+
.dmark = per_thread_context_typed_data_mark,
|
|
509
|
+
.dfree = per_thread_context_typed_data_free,
|
|
510
|
+
.dsize = NULL,
|
|
511
|
+
},
|
|
512
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
static void per_thread_context_typed_data_mark(void *ctx_ptr) {
|
|
516
|
+
per_thread_context *ctx = (per_thread_context *) ctx_ptr;
|
|
517
|
+
if (sampling_buffer_needs_marking(&ctx->sampling_buffer)) {
|
|
518
|
+
sampling_buffer_mark(&ctx->sampling_buffer);
|
|
519
|
+
}
|
|
406
520
|
}
|
|
407
521
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
522
|
+
static void per_thread_context_typed_data_free(void *ctx_ptr) {
|
|
523
|
+
per_thread_context *ctx = (per_thread_context *) ctx_ptr;
|
|
524
|
+
sampling_buffer_free(&ctx->sampling_buffer);
|
|
525
|
+
free(ctx);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Only for testing: removes the per-thread context without recreating it, so the thread has no context.
|
|
529
|
+
// This simulates situations where we observe a thread running before our RUBY_EVENT_THREAD_BEGIN tracepoint fires,
|
|
530
|
+
// so that we can test our `get_per_thread_context(...) => null` code paths.
|
|
531
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
532
|
+
check_frozen_thread(thread);
|
|
533
|
+
per_thread_context *ctx = get_per_thread_context(thread);
|
|
534
|
+
if (ctx != NULL) {
|
|
535
|
+
set_per_thread_context(thread, NULL);
|
|
536
|
+
rb_ivar_set(thread, dd_per_thread_context_id, Qnil);
|
|
537
|
+
}
|
|
538
|
+
return Qnil;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
542
|
+
thread_context_collector_reset_all_per_thread_contexts(collector_instance);
|
|
543
|
+
return Qnil;
|
|
413
544
|
}
|
|
414
545
|
|
|
415
546
|
static VALUE _native_new(VALUE klass) {
|
|
416
|
-
|
|
547
|
+
thread_context_collector_state *state = ruby_xcalloc(1, sizeof(thread_context_collector_state));
|
|
417
548
|
|
|
418
549
|
// Note: Any exceptions raised from this note until the TypedData_Wrap_Struct call will lead to the state memory
|
|
419
550
|
// being leaked.
|
|
420
551
|
|
|
421
552
|
// Update this when modifying state struct
|
|
422
|
-
state->locations = NULL;
|
|
423
|
-
state->
|
|
424
|
-
state->hash_map_per_thread_context =
|
|
425
|
-
// "numtable" is an awful name, but TL;DR it's what should be used when keys are `VALUE`s.
|
|
426
|
-
st_init_numtable();
|
|
553
|
+
state->locations.ptr = NULL;
|
|
554
|
+
state->locations.len = 0;
|
|
427
555
|
state->recorder_instance = Qnil;
|
|
428
556
|
state->tracer_context_key = MISSING_TRACER_CONTEXT_KEY;
|
|
429
557
|
VALUE thread_list_buffer = rb_ary_new();
|
|
430
558
|
state->thread_list_buffer = thread_list_buffer;
|
|
431
559
|
state->endpoint_collection_enabled = true;
|
|
432
|
-
state->
|
|
560
|
+
state->native_filenames_enabled = false;
|
|
561
|
+
state->native_filenames_cache = st_init_numtable();
|
|
433
562
|
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
|
|
563
|
+
state->otel_context_source = OTEL_CONTEXT_SOURCE_UNKNOWN;
|
|
434
564
|
state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
|
|
435
565
|
VALUE main_thread = rb_thread_main();
|
|
436
566
|
state->main_thread = main_thread;
|
|
@@ -460,25 +590,30 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
460
590
|
VALUE max_frames = rb_hash_fetch(options, ID2SYM(rb_intern("max_frames")));
|
|
461
591
|
VALUE tracer_context_key = rb_hash_fetch(options, ID2SYM(rb_intern("tracer_context_key")));
|
|
462
592
|
VALUE endpoint_collection_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("endpoint_collection_enabled")));
|
|
463
|
-
VALUE timeline_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("timeline_enabled")));
|
|
464
593
|
VALUE waiting_for_gvl_threshold_ns = rb_hash_fetch(options, ID2SYM(rb_intern("waiting_for_gvl_threshold_ns")));
|
|
465
594
|
VALUE otel_context_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("otel_context_enabled")));
|
|
595
|
+
VALUE native_filenames_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("native_filenames_enabled")));
|
|
596
|
+
VALUE overhead_filename = rb_hash_fetch(options, ID2SYM(rb_intern("overhead_filename")));
|
|
466
597
|
|
|
467
598
|
ENFORCE_TYPE(max_frames, T_FIXNUM);
|
|
468
599
|
ENFORCE_BOOLEAN(endpoint_collection_enabled);
|
|
469
|
-
ENFORCE_BOOLEAN(timeline_enabled);
|
|
470
600
|
ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
|
|
601
|
+
ENFORCE_BOOLEAN(native_filenames_enabled);
|
|
602
|
+
ENFORCE_TYPE(overhead_filename, T_STRING);
|
|
603
|
+
|
|
604
|
+
uint16_t max_frame_int = sampling_buffer_check_max_frames(NUM2INT(max_frames));
|
|
471
605
|
|
|
472
|
-
|
|
473
|
-
TypedData_Get_Struct(self_instance,
|
|
606
|
+
thread_context_collector_state *state;
|
|
607
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
474
608
|
|
|
475
609
|
// Update this when modifying state struct
|
|
476
|
-
state->
|
|
477
|
-
state->locations = ruby_xcalloc(
|
|
478
|
-
// hash_map_per_thread_context is already initialized, nothing to do here
|
|
610
|
+
state->locations.len = max_frame_int;
|
|
611
|
+
state->locations.ptr = ruby_xcalloc(max_frame_int, sizeof(ddog_prof_Location));
|
|
479
612
|
state->recorder_instance = enforce_recorder_instance(recorder_instance);
|
|
613
|
+
recorder_install_on_serialize(recorder_instance, self_instance);
|
|
480
614
|
state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
|
|
481
|
-
state->
|
|
615
|
+
state->native_filenames_enabled = (native_filenames_enabled == Qtrue);
|
|
616
|
+
state->overhead_filename = overhead_filename;
|
|
482
617
|
if (otel_context_enabled == Qfalse || otel_context_enabled == Qnil) {
|
|
483
618
|
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
|
|
484
619
|
} else if (otel_context_enabled == ID2SYM(rb_intern("only"))) {
|
|
@@ -486,10 +621,10 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
486
621
|
} else if (otel_context_enabled == ID2SYM(rb_intern("both"))) {
|
|
487
622
|
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_BOTH;
|
|
488
623
|
} else {
|
|
489
|
-
|
|
624
|
+
raise_error(rb_eArgError, "Unexpected value for otel_context_enabled: %+" PRIsVALUE, otel_context_enabled);
|
|
490
625
|
}
|
|
491
626
|
|
|
492
|
-
|
|
627
|
+
state->waiting_for_gvl_threshold_ns = NUM2UINT(waiting_for_gvl_threshold_ns);
|
|
493
628
|
|
|
494
629
|
if (RTEST(tracer_context_key)) {
|
|
495
630
|
ENFORCE_TYPE(tracer_context_key, T_SYMBOL);
|
|
@@ -499,48 +634,102 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
499
634
|
state->tracer_context_key = rb_to_id(tracer_context_key);
|
|
500
635
|
}
|
|
501
636
|
|
|
637
|
+
if (thread_begin_tracepoint == Qnil) {
|
|
638
|
+
thread_begin_tracepoint = rb_tracepoint_new(Qnil, RUBY_EVENT_THREAD_BEGIN, on_thread_begin_event, NULL);
|
|
639
|
+
rb_tracepoint_enable(thread_begin_tracepoint);
|
|
640
|
+
}
|
|
641
|
+
|
|
502
642
|
return Qtrue;
|
|
503
643
|
}
|
|
504
644
|
|
|
505
645
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
506
646
|
// It SHOULD NOT be used for other purposes.
|
|
507
|
-
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE
|
|
508
|
-
|
|
647
|
+
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE allow_exception) {
|
|
648
|
+
ENFORCE_BOOLEAN(allow_exception);
|
|
649
|
+
|
|
650
|
+
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
651
|
+
|
|
652
|
+
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE));
|
|
653
|
+
|
|
654
|
+
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
|
509
655
|
|
|
510
|
-
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE), profiler_overhead_stack_thread);
|
|
511
656
|
return Qtrue;
|
|
512
657
|
}
|
|
513
658
|
|
|
514
659
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
515
660
|
// It SHOULD NOT be used for other purposes.
|
|
516
661
|
static VALUE _native_on_gc_start(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
662
|
+
debug_enter_unsafe_context();
|
|
663
|
+
|
|
517
664
|
thread_context_collector_on_gc_start(collector_instance);
|
|
665
|
+
|
|
666
|
+
debug_leave_unsafe_context();
|
|
667
|
+
|
|
518
668
|
return Qtrue;
|
|
519
669
|
}
|
|
520
670
|
|
|
521
671
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
522
672
|
// It SHOULD NOT be used for other purposes.
|
|
523
673
|
static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
674
|
+
debug_enter_unsafe_context();
|
|
675
|
+
|
|
524
676
|
(void) !thread_context_collector_on_gc_finish(collector_instance);
|
|
677
|
+
|
|
678
|
+
debug_leave_unsafe_context();
|
|
679
|
+
|
|
525
680
|
return Qtrue;
|
|
526
681
|
}
|
|
527
682
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state) {
|
|
531
|
-
ENFORCE_BOOLEAN(reset_monotonic_to_system_state);
|
|
532
|
-
|
|
533
|
-
struct thread_context_collector_state *state;
|
|
534
|
-
TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
683
|
+
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE allow_exception) {
|
|
684
|
+
ENFORCE_BOOLEAN(allow_exception);
|
|
535
685
|
|
|
536
|
-
if (
|
|
537
|
-
state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
|
|
538
|
-
}
|
|
686
|
+
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
539
687
|
|
|
540
688
|
thread_context_collector_sample_after_gc(collector_instance);
|
|
689
|
+
|
|
690
|
+
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
|
691
|
+
|
|
541
692
|
return Qtrue;
|
|
542
693
|
}
|
|
543
694
|
|
|
695
|
+
// Record profiler sampling overhead as a placeholder stack
|
|
696
|
+
static void record_sampling_overhead(thread_context_collector_state *state, per_thread_context *current_thread_context) {
|
|
697
|
+
long wall_time_after_sampling = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
698
|
+
long cpu_time_after_sampling = cpu_time_now_ns(current_thread_context);
|
|
699
|
+
|
|
700
|
+
long overhead_cpu_time_ns = update_cpu_time_since_previous_sample(current_thread_context, cpu_time_after_sampling);
|
|
701
|
+
long overhead_wall_time_ns = update_wall_time_since_previous_sample(current_thread_context, wall_time_after_sampling);
|
|
702
|
+
|
|
703
|
+
ddog_prof_Label overhead_labels[] = {
|
|
704
|
+
{.key = DDOG_CHARSLICE_C("thread id"), .str = DDOG_CHARSLICE_C("0"), .num = 0},
|
|
705
|
+
{.key = DDOG_CHARSLICE_C("thread name"), .str = DDOG_CHARSLICE_C("Datadog::Profiling::Sampling"), .num = 0},
|
|
706
|
+
{.key = DDOG_CHARSLICE_C("state"), .str = DDOG_CHARSLICE_C("had cpu"), .num = 0},
|
|
707
|
+
{.key = DDOG_CHARSLICE_C("profiler overhead"), .num = 1},
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
int64_t end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, wall_time_after_sampling);
|
|
711
|
+
|
|
712
|
+
ddog_prof_Location overhead_location = {
|
|
713
|
+
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
|
714
|
+
.function = {
|
|
715
|
+
.name = DDOG_CHARSLICE_C("sampling"),
|
|
716
|
+
.filename = char_slice_from_ruby_string(state->overhead_filename),
|
|
717
|
+
},
|
|
718
|
+
.line = 0,
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
record_sample(
|
|
722
|
+
state->recorder_instance,
|
|
723
|
+
(ddog_prof_Slice_Location) {.ptr = &overhead_location, .len = 1},
|
|
724
|
+
(sample_values) {.cpu_time_ns = overhead_cpu_time_ns, .cpu_or_wall_samples = 1, .wall_time_ns = overhead_wall_time_ns},
|
|
725
|
+
(sample_labels) {
|
|
726
|
+
.labels = (ddog_prof_Slice_Label) {.ptr = overhead_labels, .len = sizeof(overhead_labels) / sizeof(overhead_labels[0])},
|
|
727
|
+
.state_label = NULL,
|
|
728
|
+
.end_timestamp_ns = end_timestamp_ns,
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
|
|
544
733
|
// This function gets called from the Collectors::CpuAndWallTimeWorker to trigger the actual sampling.
|
|
545
734
|
//
|
|
546
735
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
@@ -549,14 +738,12 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_
|
|
|
549
738
|
// Assumption 4: This function IS NOT called in a reentrant way.
|
|
550
739
|
// Assumption 5: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
551
740
|
//
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
struct thread_context_collector_state *state;
|
|
556
|
-
TypedData_Get_Struct(self_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
741
|
+
void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns) {
|
|
742
|
+
thread_context_collector_state *state;
|
|
743
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
557
744
|
|
|
558
745
|
VALUE current_thread = rb_thread_current();
|
|
559
|
-
|
|
746
|
+
per_thread_context *current_thread_context = get_or_create_context_for(current_thread);
|
|
560
747
|
long cpu_time_at_sample_start_for_current_thread = cpu_time_now_ns(current_thread_context);
|
|
561
748
|
|
|
562
749
|
VALUE threads = thread_list(state);
|
|
@@ -564,76 +751,57 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
|
564
751
|
const long thread_count = RARRAY_LEN(threads);
|
|
565
752
|
for (long i = 0; i < thread_count; i++) {
|
|
566
753
|
VALUE thread = RARRAY_AREF(threads, i);
|
|
567
|
-
|
|
754
|
+
per_thread_context *thread_context = get_or_create_context_for(thread);
|
|
568
755
|
|
|
569
|
-
// We account for cpu-time for the current thread in a different way
|
|
570
|
-
// blaming the time the profiler took on whatever
|
|
571
|
-
|
|
756
|
+
// We account for cpu-time for the current thread in a different way: we use the cpu-time at sampling start,
|
|
757
|
+
// to avoid blaming the time the profiler took on whatever is currently running on the thread,
|
|
758
|
+
// and instead we report that time the profiler took as sampling overhead below.
|
|
759
|
+
long current_cpu_time_ns = (thread == current_thread) ? cpu_time_at_sample_start_for_current_thread : cpu_time_now_ns(thread_context);
|
|
572
760
|
|
|
573
761
|
update_metrics_and_sample(
|
|
574
762
|
state,
|
|
575
|
-
|
|
576
|
-
/* stack_from_thread: */ thread,
|
|
763
|
+
thread,
|
|
577
764
|
thread_context,
|
|
578
|
-
thread_context->sampling_buffer,
|
|
579
765
|
current_cpu_time_ns,
|
|
580
|
-
current_monotonic_wall_time_ns
|
|
581
|
-
|
|
766
|
+
current_monotonic_wall_time_ns,
|
|
767
|
+
false);
|
|
582
768
|
}
|
|
583
769
|
|
|
584
|
-
state->sample_count++;
|
|
585
|
-
|
|
586
|
-
// TODO: This seems somewhat overkill and inefficient to do often; right now we just do it every few samples
|
|
587
|
-
// but there's probably a better way to do this if we actually track when threads finish
|
|
588
|
-
if (state->sample_count % 100 == 0) remove_context_for_dead_threads(state);
|
|
770
|
+
state->stats.sample_count++;
|
|
589
771
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
);
|
|
772
|
+
// If the current thread is a profiler-internal thread, we don't use record_sampling_overhead()
|
|
773
|
+
// and accept the sampling overhead is attributed to the profiler-internal thread
|
|
774
|
+
// (both are under the same datadog group in the flamegraph).
|
|
775
|
+
// The reason we need to do this is profiler-internal threads are skipped during per-tick sampling
|
|
776
|
+
// so their timestamps are not updated. record_sampling_overhead() would see the stale previous-sample timestamps and
|
|
777
|
+
// attribute the entire sleep interval incorrectly as overhead instead of just the sampling work.
|
|
778
|
+
if (!current_thread_context->is_profiler_internal_thread) {
|
|
779
|
+
record_sampling_overhead(state, current_thread_context);
|
|
780
|
+
}
|
|
600
781
|
}
|
|
601
782
|
|
|
602
783
|
static void update_metrics_and_sample(
|
|
603
|
-
|
|
784
|
+
thread_context_collector_state *state,
|
|
604
785
|
VALUE thread_being_sampled,
|
|
605
|
-
|
|
606
|
-
struct per_thread_context *thread_context,
|
|
607
|
-
sampling_buffer* sampling_buffer,
|
|
786
|
+
per_thread_context *thread_context,
|
|
608
787
|
long current_cpu_time_ns,
|
|
609
|
-
long current_monotonic_wall_time_ns
|
|
788
|
+
long current_monotonic_wall_time_ns,
|
|
789
|
+
bool force_sample
|
|
610
790
|
) {
|
|
611
791
|
bool is_gvl_waiting_state =
|
|
612
|
-
handle_gvl_waiting(state, thread_being_sampled,
|
|
792
|
+
handle_gvl_waiting(state, thread_being_sampled, thread_context, current_cpu_time_ns);
|
|
793
|
+
|
|
794
|
+
if (skip_sample(state, thread_context, is_gvl_waiting_state, force_sample)) return;
|
|
613
795
|
|
|
614
796
|
// Don't assign/update cpu during "Waiting for GVL"
|
|
615
|
-
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 :
|
|
616
|
-
&thread_context->cpu_time_at_previous_sample_ns,
|
|
617
|
-
current_cpu_time_ns,
|
|
618
|
-
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
619
|
-
IS_NOT_WALL_TIME
|
|
620
|
-
);
|
|
797
|
+
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
621
798
|
|
|
622
|
-
long wall_time_elapsed_ns =
|
|
623
|
-
&thread_context->wall_time_at_previous_sample_ns,
|
|
624
|
-
current_monotonic_wall_time_ns,
|
|
625
|
-
// We explicitly pass in `INVALID_TIME` as an argument for `gc_start_time_ns` here because we don't want wall-time
|
|
626
|
-
// accounting to change during GC.
|
|
627
|
-
// E.g. if 60 seconds pass in the real world, 60 seconds of wall-time are recorded, regardless of the thread doing
|
|
628
|
-
// GC or not.
|
|
629
|
-
INVALID_TIME,
|
|
630
|
-
IS_WALL_TIME
|
|
631
|
-
);
|
|
799
|
+
long wall_time_elapsed_ns = update_wall_time_since_previous_sample(thread_context, current_monotonic_wall_time_ns);
|
|
632
800
|
|
|
633
801
|
// A thread enters "Waiting for GVL", well, as the name implies, without the GVL.
|
|
634
802
|
//
|
|
635
803
|
// As a consequence, it's possible that a thread enters "Waiting for GVL" in parallel with the current thread working
|
|
636
|
-
// on sampling, and thus for the
|
|
804
|
+
// on sampling, and thus for the `current_monotonic_wall_time_ns` (which is recorded at the start of sampling)
|
|
637
805
|
// to be < the time at which we started Waiting for GVL.
|
|
638
806
|
//
|
|
639
807
|
// All together, this means that when `handle_gvl_waiting` creates an extra sample (see comments on that function for
|
|
@@ -648,9 +816,7 @@ static void update_metrics_and_sample(
|
|
|
648
816
|
trigger_sample_for_thread(
|
|
649
817
|
state,
|
|
650
818
|
thread_being_sampled,
|
|
651
|
-
stack_from_thread,
|
|
652
819
|
thread_context,
|
|
653
|
-
sampling_buffer,
|
|
654
820
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = wall_time_elapsed_ns},
|
|
655
821
|
current_monotonic_wall_time_ns,
|
|
656
822
|
NULL,
|
|
@@ -660,6 +826,37 @@ static void update_metrics_and_sample(
|
|
|
660
826
|
);
|
|
661
827
|
}
|
|
662
828
|
|
|
829
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample) {
|
|
830
|
+
if (!force_sample && thread_context->is_profiler_internal_thread) {
|
|
831
|
+
state->stats.profiler_thread_samples_skipped++;
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Racy read but harmless, can only cause an extra sample
|
|
836
|
+
uint64_t gvl_state_change_count = thread_context->gvl_state_change_count;
|
|
837
|
+
|
|
838
|
+
// Skip this per-tick sample entirely when the thread does not have the GVL and did not acquire
|
|
839
|
+
// it since the previous sample: its Ruby-level stack has not changed. The skipped wall-time will
|
|
840
|
+
// be picked up by either by an extra sample when the thread acquires the GVL, or by
|
|
841
|
+
// the on-serialize flush in the stack recorder (using was_skipped_at_last_sample).
|
|
842
|
+
// The check is gated by `!is_gvl_waiting_state` so the existing "Waiting for GVL" machinery
|
|
843
|
+
// in handle_gvl_waiting (situation 1 extra sample, situation 2 regular sample) keeps running.
|
|
844
|
+
// TODO: we could probably also skip while "Waiting for GVL"
|
|
845
|
+
if (!is_gvl_waiting_state &&
|
|
846
|
+
!force_sample &&
|
|
847
|
+
(gvl_state_change_count & GVL_SUSPENDED) &&
|
|
848
|
+
gvl_state_change_count == thread_context->gvl_state_change_count_at_previous_sample) {
|
|
849
|
+
state->stats.inactive_thread_samples_skipped++;
|
|
850
|
+
thread_context->was_skipped_at_last_sample = true;
|
|
851
|
+
return true; // Do NOT update wall_time_at_previous_sample_ns or cpu_time_at_previous_sample_ns
|
|
852
|
+
} else {
|
|
853
|
+
// We are going to sample, update the state accordingly:
|
|
854
|
+
thread_context->gvl_state_change_count_at_previous_sample = gvl_state_change_count;
|
|
855
|
+
thread_context->was_skipped_at_last_sample = false;
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
663
860
|
// This function gets called when Ruby is about to start running the Garbage Collector on the current thread.
|
|
664
861
|
// It updates the per_thread_context of the current thread to include the current cpu/wall times, to be used to later
|
|
665
862
|
// create an event including the cpu/wall time spent in garbage collector work.
|
|
@@ -671,17 +868,15 @@ static void update_metrics_and_sample(
|
|
|
671
868
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
672
869
|
// Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
673
870
|
void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
674
|
-
|
|
871
|
+
thread_context_collector_state *state;
|
|
675
872
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return;
|
|
676
|
-
// This should never fail
|
|
677
|
-
TypedData_Get_Struct(self_instance,
|
|
873
|
+
// This should never fail when the above check passes
|
|
874
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
678
875
|
|
|
679
|
-
|
|
876
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
680
877
|
|
|
681
|
-
//
|
|
682
|
-
//
|
|
683
|
-
// GC" is a rare enough case that we can just ignore it.
|
|
684
|
-
// We can always improve this later if we find that this happens often (and we have the counter to help us figure that out)!
|
|
878
|
+
// Context is created eagerly via on_thread_begin_event, so this should not normally be NULL.
|
|
879
|
+
// We can't get_or_create_context_for() here since we can't allocate (GC context).
|
|
685
880
|
if (thread_context == NULL) {
|
|
686
881
|
state->stats.gc_samples_missed_due_to_missing_context++;
|
|
687
882
|
return;
|
|
@@ -704,15 +899,14 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
|
704
899
|
// Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
705
900
|
__attribute__((warn_unused_result))
|
|
706
901
|
bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
707
|
-
|
|
902
|
+
thread_context_collector_state *state;
|
|
708
903
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
|
709
|
-
// This should never fail
|
|
710
|
-
TypedData_Get_Struct(self_instance,
|
|
904
|
+
// This should never fail when the above check passes
|
|
905
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
711
906
|
|
|
712
|
-
|
|
907
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
713
908
|
|
|
714
|
-
//
|
|
715
|
-
// how often this happens -- see on_gc_start.
|
|
909
|
+
// Context is created eagerly, so this should not normally be NULL (see on_gc_start).
|
|
716
910
|
if (thread_context == NULL) return false;
|
|
717
911
|
|
|
718
912
|
long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
|
|
@@ -753,9 +947,9 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
753
947
|
state->gc_tracking.accumulated_wall_time_ns += gc_wall_time_elapsed_ns;
|
|
754
948
|
state->gc_tracking.wall_time_at_previous_gc_ns = wall_time_at_finish_ns;
|
|
755
949
|
|
|
756
|
-
// Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample
|
|
757
|
-
// We don't
|
|
758
|
-
//
|
|
950
|
+
// Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample.
|
|
951
|
+
// We don't do the same for wall-time, because GC is just like any other reason a thread didn't make
|
|
952
|
+
// progress -- time always goes forward regardless of the thread making progress on what it wanted.
|
|
759
953
|
if (thread_context->cpu_time_at_previous_sample_ns != INVALID_TIME) {
|
|
760
954
|
thread_context->cpu_time_at_previous_sample_ns += gc_cpu_time_elapsed_ns;
|
|
761
955
|
}
|
|
@@ -782,11 +976,11 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
782
976
|
// Assumption 3: Unlike `on_gc_start` and `on_gc_finish`, this method is allowed to allocate memory as needed.
|
|
783
977
|
// Assumption 4: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
784
978
|
VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
785
|
-
|
|
786
|
-
TypedData_Get_Struct(self_instance,
|
|
979
|
+
thread_context_collector_state *state;
|
|
980
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
787
981
|
|
|
788
982
|
if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
|
|
789
|
-
|
|
983
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected call to sample_after_gc without valid GC information available");
|
|
790
984
|
}
|
|
791
985
|
|
|
792
986
|
int max_labels_needed_for_gc = 7; // Magic number gets validated inside gc_profiling_set_metadata
|
|
@@ -796,11 +990,7 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
796
990
|
ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = label_pos};
|
|
797
991
|
|
|
798
992
|
// The end_timestamp_ns is treated specially by libdatadog and that's why it's not added as a ddog_prof_Label
|
|
799
|
-
int64_t end_timestamp_ns =
|
|
800
|
-
|
|
801
|
-
if (state->timeline_enabled) {
|
|
802
|
-
end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, state->gc_tracking.wall_time_at_previous_gc_ns);
|
|
803
|
-
}
|
|
993
|
+
int64_t end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, state->gc_tracking.wall_time_at_previous_gc_ns);
|
|
804
994
|
|
|
805
995
|
record_placeholder_stack(
|
|
806
996
|
state->recorder_instance,
|
|
@@ -832,11 +1022,9 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
832
1022
|
}
|
|
833
1023
|
|
|
834
1024
|
static void trigger_sample_for_thread(
|
|
835
|
-
|
|
836
|
-
VALUE
|
|
837
|
-
|
|
838
|
-
struct per_thread_context *thread_context,
|
|
839
|
-
sampling_buffer* sampling_buffer,
|
|
1025
|
+
thread_context_collector_state *state,
|
|
1026
|
+
VALUE thread_being_sampled,
|
|
1027
|
+
per_thread_context *thread_context,
|
|
840
1028
|
sample_values values,
|
|
841
1029
|
long current_monotonic_wall_time_ns,
|
|
842
1030
|
// These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
|
|
@@ -850,7 +1038,6 @@ static void trigger_sample_for_thread(
|
|
|
850
1038
|
int max_label_count =
|
|
851
1039
|
1 + // thread id
|
|
852
1040
|
1 + // thread name
|
|
853
|
-
1 + // profiler overhead
|
|
854
1041
|
2 + // ruby vm type and allocation class
|
|
855
1042
|
1 + // state (only set for cpu/wall-time samples)
|
|
856
1043
|
2; // local root span id and span id
|
|
@@ -862,13 +1049,13 @@ static void trigger_sample_for_thread(
|
|
|
862
1049
|
.str = thread_context->thread_id_char_slice
|
|
863
1050
|
};
|
|
864
1051
|
|
|
865
|
-
VALUE thread_name = thread_name_for(
|
|
1052
|
+
VALUE thread_name = thread_name_for(thread_being_sampled);
|
|
866
1053
|
if (thread_name != Qnil) {
|
|
867
1054
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
868
1055
|
.key = DDOG_CHARSLICE_C("thread name"),
|
|
869
1056
|
.str = char_slice_from_ruby_string(thread_name)
|
|
870
1057
|
};
|
|
871
|
-
} else if (
|
|
1058
|
+
} else if (thread_being_sampled == state->main_thread) { // Threads are often not named, but we can have a nice fallback for this special thread
|
|
872
1059
|
ddog_CharSlice main_thread_name = DDOG_CHARSLICE_C("main");
|
|
873
1060
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
874
1061
|
.key = DDOG_CHARSLICE_C("thread name"),
|
|
@@ -883,12 +1070,12 @@ static void trigger_sample_for_thread(
|
|
|
883
1070
|
};
|
|
884
1071
|
}
|
|
885
1072
|
|
|
886
|
-
|
|
887
|
-
trace_identifiers_for(state,
|
|
1073
|
+
trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
|
|
1074
|
+
trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result, is_safe_to_allocate_objects);
|
|
888
1075
|
|
|
889
1076
|
if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
|
|
890
1077
|
// If we couldn't get something with ddtrace, let's see if we can get some trace identifiers from opentelemetry directly
|
|
891
|
-
otel_without_ddtrace_trace_identifiers_for(state,
|
|
1078
|
+
otel_without_ddtrace_trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result, is_safe_to_allocate_objects);
|
|
892
1079
|
}
|
|
893
1080
|
|
|
894
1081
|
if (trace_identifiers_result.valid) {
|
|
@@ -913,13 +1100,6 @@ static void trigger_sample_for_thread(
|
|
|
913
1100
|
}
|
|
914
1101
|
}
|
|
915
1102
|
|
|
916
|
-
if (thread != stack_from_thread) {
|
|
917
|
-
labels[label_pos++] = (ddog_prof_Label) {
|
|
918
|
-
.key = DDOG_CHARSLICE_C("profiler overhead"),
|
|
919
|
-
.num = 1
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
|
|
923
1103
|
if (ruby_vm_type != NULL) {
|
|
924
1104
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
925
1105
|
.key = DDOG_CHARSLICE_C("ruby vm type"),
|
|
@@ -953,20 +1133,21 @@ static void trigger_sample_for_thread(
|
|
|
953
1133
|
// @ivoanjo: I wonder if C compilers are smart enough to statically prove this check never triggers unless someone
|
|
954
1134
|
// changes the code erroneously and remove it entirely?
|
|
955
1135
|
if (label_pos > max_label_count) {
|
|
956
|
-
|
|
1136
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected label_pos (%d) > max_label_count (%d)", label_pos, max_label_count);
|
|
957
1137
|
}
|
|
958
1138
|
|
|
959
1139
|
ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = label_pos};
|
|
960
1140
|
|
|
961
1141
|
// The end_timestamp_ns is treated specially by libdatadog and that's why it's not added as a ddog_prof_Label
|
|
962
1142
|
int64_t end_timestamp_ns = 0;
|
|
963
|
-
if (
|
|
1143
|
+
if (current_monotonic_wall_time_ns != INVALID_TIME) {
|
|
964
1144
|
end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, current_monotonic_wall_time_ns);
|
|
965
1145
|
}
|
|
966
1146
|
|
|
967
1147
|
sample_thread(
|
|
968
|
-
|
|
969
|
-
sampling_buffer,
|
|
1148
|
+
thread_being_sampled,
|
|
1149
|
+
&thread_context->sampling_buffer,
|
|
1150
|
+
state->locations,
|
|
970
1151
|
state->recorder_instance,
|
|
971
1152
|
values,
|
|
972
1153
|
(sample_labels) {
|
|
@@ -974,7 +1155,9 @@ static void trigger_sample_for_thread(
|
|
|
974
1155
|
.state_label = state_label,
|
|
975
1156
|
.end_timestamp_ns = end_timestamp_ns,
|
|
976
1157
|
.is_gvl_waiting_state = is_gvl_waiting_state,
|
|
977
|
-
}
|
|
1158
|
+
},
|
|
1159
|
+
state->native_filenames_enabled,
|
|
1160
|
+
state->native_filenames_cache
|
|
978
1161
|
);
|
|
979
1162
|
}
|
|
980
1163
|
|
|
@@ -982,36 +1165,48 @@ static void trigger_sample_for_thread(
|
|
|
982
1165
|
// It SHOULD NOT be used for other purposes.
|
|
983
1166
|
static VALUE _native_thread_list(DDTRACE_UNUSED VALUE _self) {
|
|
984
1167
|
VALUE result = rb_ary_new();
|
|
1168
|
+
|
|
1169
|
+
debug_enter_unsafe_context();
|
|
1170
|
+
|
|
985
1171
|
ddtrace_thread_list(result);
|
|
1172
|
+
|
|
1173
|
+
debug_leave_unsafe_context();
|
|
1174
|
+
|
|
986
1175
|
return result;
|
|
987
1176
|
}
|
|
988
1177
|
|
|
989
|
-
static
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
|
|
994
|
-
thread_context = (struct per_thread_context*) value_context;
|
|
995
|
-
} else {
|
|
996
|
-
thread_context = ruby_xcalloc(1, sizeof(struct per_thread_context));
|
|
997
|
-
initialize_context(thread, thread_context, state);
|
|
998
|
-
st_insert(state->hash_map_per_thread_context, (st_data_t) thread, (st_data_t) thread_context);
|
|
1178
|
+
static void check_frozen_thread(VALUE thread) {
|
|
1179
|
+
if (RB_OBJ_FROZEN(thread)) {
|
|
1180
|
+
raise_error(rb_eFrozenError, "Cannot setup profiler state for Thread %"PRIsVALUE" because it is frozen. Please avoid freezing Thread instances and/or report the issue to dd-trace-rb", thread);
|
|
999
1181
|
}
|
|
1000
|
-
|
|
1001
|
-
return thread_context;
|
|
1002
1182
|
}
|
|
1003
1183
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1184
|
+
// See the docs on struct per_thread_context.
|
|
1185
|
+
// This allocates a Ruby object and therefore needs the GVL and is not safe to call from RUBY_INTERNAL_EVENT_* hooks.
|
|
1186
|
+
static per_thread_context *get_or_create_context_for(VALUE thread) {
|
|
1187
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1188
|
+
if (thread_context != NULL) return thread_context;
|
|
1007
1189
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1190
|
+
check_frozen_thread(thread);
|
|
1191
|
+
|
|
1192
|
+
thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
|
|
1193
|
+
initialize_context(thread, thread_context);
|
|
1194
|
+
|
|
1195
|
+
VALUE wrapper = TypedData_Wrap_Struct(rb_cObject, &per_thread_context_typed_data, thread_context);
|
|
1196
|
+
rb_ivar_set(thread, dd_per_thread_context_id, wrapper);
|
|
1011
1197
|
|
|
1198
|
+
set_per_thread_context(thread, thread_context);
|
|
1012
1199
|
return thread_context;
|
|
1013
1200
|
}
|
|
1014
1201
|
|
|
1202
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
|
|
1203
|
+
if (!ddtrace_rb_ractor_main_p()) return;
|
|
1204
|
+
|
|
1205
|
+
VALUE thread = rb_tracearg_self(rb_tracearg_from_tracepoint(tracepoint_data));
|
|
1206
|
+
ENFORCE_THREAD(thread);
|
|
1207
|
+
get_or_create_context_for(thread);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1015
1210
|
#define LOGGING_GEM_PATH "/lib/logging/diagnostic_context.rb"
|
|
1016
1211
|
|
|
1017
1212
|
// The `logging` gem monkey patches thread creation, which makes the `invoke_location_for` useless, since every thread
|
|
@@ -1033,8 +1228,11 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
|
|
|
1033
1228
|
return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
|
|
1034
1229
|
}
|
|
1035
1230
|
|
|
1036
|
-
static void initialize_context(VALUE thread,
|
|
1037
|
-
|
|
1231
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context) {
|
|
1232
|
+
// We always create per_thread_context's with latest_max_frames; that value is kept in sync with the
|
|
1233
|
+
// active profiler's max_frames by the global reset that runs when profiling starts
|
|
1234
|
+
// so we expect to always see here the latest correct value to be used.
|
|
1235
|
+
sampling_buffer_initialize(&thread_context->sampling_buffer, latest_max_frames);
|
|
1038
1236
|
|
|
1039
1237
|
snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
|
|
1040
1238
|
thread_context->thread_id_char_slice = (ddog_CharSlice) {.ptr = thread_context->thread_id, .len = strlen(thread_context->thread_id)};
|
|
@@ -1053,7 +1251,7 @@ static void initialize_context(VALUE thread, struct per_thread_context *thread_c
|
|
|
1053
1251
|
} else {
|
|
1054
1252
|
snprintf(thread_context->thread_invoke_location, THREAD_INVOKE_LOCATION_LIMIT_CHARS, "%s", "(Unnamed thread)");
|
|
1055
1253
|
}
|
|
1056
|
-
} else if (thread !=
|
|
1254
|
+
} else if (thread != rb_thread_main()) {
|
|
1057
1255
|
// If the first function of a thread is native code, there won't be an invoke location, so we use this fallback.
|
|
1058
1256
|
// 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
|
|
1059
1257
|
// symbol attached to it.
|
|
@@ -1067,50 +1265,72 @@ static void initialize_context(VALUE thread, struct per_thread_context *thread_c
|
|
|
1067
1265
|
|
|
1068
1266
|
thread_context->thread_cpu_time_id = thread_cpu_time_id_for(thread);
|
|
1069
1267
|
|
|
1070
|
-
|
|
1071
|
-
thread_context->cpu_time_at_previous_sample_ns =
|
|
1072
|
-
thread_context->wall_time_at_previous_sample_ns = INVALID_TIME;
|
|
1268
|
+
thread_context->wall_time_at_previous_sample_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
1269
|
+
thread_context->cpu_time_at_previous_sample_ns = cpu_time_now_ns(thread_context);
|
|
1073
1270
|
|
|
1074
1271
|
// These will only be used during a GC operation
|
|
1075
1272
|
thread_context->gc_tracking.cpu_time_at_start_ns = INVALID_TIME;
|
|
1076
1273
|
thread_context->gc_tracking.wall_time_at_start_ns = INVALID_TIME;
|
|
1077
1274
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
// kind of synchronization (e.g. by threads without the GVL).
|
|
1081
|
-
//
|
|
1082
|
-
// We set this marker here for two purposes:
|
|
1083
|
-
// * To make sure there's no stale data from a previous execution of the profiler.
|
|
1084
|
-
// * To mark threads that are actually being profiled
|
|
1085
|
-
//
|
|
1086
|
-
// (Setting this is potentially a race, but what we want is to avoid _stale_ data, so
|
|
1087
|
-
// if this gets set concurrently with context initialization, then such a value will belong
|
|
1088
|
-
// to the current profiler instance, so that's OK)
|
|
1089
|
-
gvl_profiling_state_thread_object_set(thread, GVL_WAITING_ENABLED_EMPTY);
|
|
1090
|
-
#endif
|
|
1275
|
+
thread_context->gvl_waiting_at = 0;
|
|
1276
|
+
thread_context->gvl_state_change_count = 0;
|
|
1091
1277
|
}
|
|
1092
1278
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1279
|
+
// This MUST be called before profiling starts, so that a new profiler session starts from a fresh state and never
|
|
1280
|
+
// observes or includes any leftover stale state from a previous session.
|
|
1281
|
+
// Such a call MUST happen while the CpuAndWallTimeWorker is stopped (e.g. no tracepoints active, no signals
|
|
1282
|
+
// triggering samples, no gvl hooks, etc).
|
|
1283
|
+
//
|
|
1284
|
+
// It updates the global `latest_max_frames` from the given (latest) ThreadContext and (re)creates every per-thread
|
|
1285
|
+
// context's sampling buffer sized accordingly, so the buffers always match the collector that's about to start
|
|
1286
|
+
// sampling -- even if a previous session used a different max_frames.
|
|
1287
|
+
void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance) {
|
|
1288
|
+
thread_context_collector_state *state;
|
|
1289
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1290
|
+
|
|
1291
|
+
// Update global max frames to be used when allocating sampling buffers
|
|
1292
|
+
if (state->locations.len == 0) {
|
|
1293
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected locations.len == 0. Is this ThreadContext not initialized?");
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
latest_max_frames = state->locations.len;
|
|
1297
|
+
|
|
1298
|
+
VALUE threads = thread_list(state);
|
|
1299
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
1300
|
+
for (long i = 0; i < thread_count; i++) {
|
|
1301
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
1302
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1303
|
+
if (thread_context != NULL) {
|
|
1304
|
+
bool is_profiler_internal_thread = thread_context->is_profiler_internal_thread;
|
|
1305
|
+
|
|
1306
|
+
sampling_buffer_free(&thread_context->sampling_buffer);
|
|
1307
|
+
memset(thread_context, 0, sizeof(per_thread_context));
|
|
1308
|
+
initialize_context(thread, thread_context);
|
|
1309
|
+
|
|
1310
|
+
thread_context->is_profiler_internal_thread = is_profiler_internal_thread;
|
|
1311
|
+
} else {
|
|
1312
|
+
// If thread didn't have a context, let's trigger its creation
|
|
1313
|
+
get_or_create_context_for(thread);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1096
1316
|
}
|
|
1097
1317
|
|
|
1098
1318
|
static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1099
|
-
|
|
1100
|
-
TypedData_Get_Struct(collector_instance,
|
|
1319
|
+
thread_context_collector_state *state;
|
|
1320
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1101
1321
|
|
|
1102
1322
|
VALUE result = rb_str_new2(" (native state)");
|
|
1103
1323
|
|
|
1104
1324
|
// Update this when modifying state struct
|
|
1105
|
-
rb_str_concat(result, rb_sprintf(" max_frames=%d", state->
|
|
1106
|
-
rb_str_concat(result, rb_sprintf(" hash_map_per_thread_context=%"PRIsVALUE, per_thread_context_st_table_as_ruby_hash(state)));
|
|
1325
|
+
rb_str_concat(result, rb_sprintf(" max_frames=%d", state->locations.len));
|
|
1107
1326
|
rb_str_concat(result, rb_sprintf(" recorder_instance=%"PRIsVALUE, state->recorder_instance));
|
|
1108
1327
|
VALUE tracer_context_key = state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY ? Qnil : ID2SYM(state->tracer_context_key);
|
|
1109
1328
|
rb_str_concat(result, rb_sprintf(" tracer_context_key=%+"PRIsVALUE, tracer_context_key));
|
|
1110
|
-
rb_str_concat(result, rb_sprintf("
|
|
1111
|
-
rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_as_ruby_hash(state)));
|
|
1329
|
+
rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_to_ruby_hash(state, rb_hash_new())));
|
|
1112
1330
|
rb_str_concat(result, rb_sprintf(" endpoint_collection_enabled=%"PRIsVALUE, state->endpoint_collection_enabled ? Qtrue : Qfalse));
|
|
1113
|
-
rb_str_concat(result, rb_sprintf("
|
|
1331
|
+
rb_str_concat(result, rb_sprintf(" native_filenames_enabled=%"PRIsVALUE, state->native_filenames_enabled ? Qtrue : Qfalse));
|
|
1332
|
+
// Note: `st_table_size()` is available from Ruby 3.2+ but not before
|
|
1333
|
+
rb_str_concat(result, rb_sprintf(" native_filenames_cache_size=%zu", state->native_filenames_cache->num_entries));
|
|
1114
1334
|
rb_str_concat(result, rb_sprintf(" otel_context_enabled=%d", state->otel_context_enabled));
|
|
1115
1335
|
rb_str_concat(result, rb_sprintf(
|
|
1116
1336
|
" time_converter_state={.system_epoch_ns_reference=%ld, .delta_to_epoch_ns=%ld}",
|
|
@@ -1120,56 +1340,54 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
|
1120
1340
|
rb_str_concat(result, rb_sprintf(" main_thread=%"PRIsVALUE, state->main_thread));
|
|
1121
1341
|
rb_str_concat(result, rb_sprintf(" gc_tracking=%"PRIsVALUE, gc_tracking_as_ruby_hash(state)));
|
|
1122
1342
|
rb_str_concat(result, rb_sprintf(" otel_current_span_key=%"PRIsVALUE, state->otel_current_span_key));
|
|
1123
|
-
rb_str_concat(result, rb_sprintf("
|
|
1124
|
-
|
|
1125
|
-
return result;
|
|
1126
|
-
}
|
|
1343
|
+
rb_str_concat(result, rb_sprintf(" waiting_for_gvl_threshold_ns=%u", state->waiting_for_gvl_threshold_ns));
|
|
1127
1344
|
|
|
1128
|
-
static VALUE per_thread_context_st_table_as_ruby_hash(struct thread_context_collector_state *state) {
|
|
1129
|
-
VALUE result = rb_hash_new();
|
|
1130
|
-
st_foreach(state->hash_map_per_thread_context, per_thread_context_as_ruby_hash, result);
|
|
1131
1345
|
return result;
|
|
1132
1346
|
}
|
|
1133
1347
|
|
|
1134
|
-
static
|
|
1135
|
-
VALUE thread = (VALUE) key_thread;
|
|
1136
|
-
struct per_thread_context *thread_context = (struct per_thread_context*) value_context;
|
|
1137
|
-
VALUE result = (VALUE) result_hash;
|
|
1348
|
+
static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context) {
|
|
1138
1349
|
VALUE context_as_hash = rb_hash_new();
|
|
1139
|
-
rb_hash_aset(result, thread, context_as_hash);
|
|
1140
1350
|
|
|
1141
1351
|
VALUE arguments[] = {
|
|
1142
1352
|
ID2SYM(rb_intern("thread_id")), /* => */ rb_str_new2(thread_context->thread_id),
|
|
1143
1353
|
ID2SYM(rb_intern("thread_invoke_location")), /* => */ rb_str_new2(thread_context->thread_invoke_location),
|
|
1144
1354
|
ID2SYM(rb_intern("thread_cpu_time_id_valid?")), /* => */ thread_context->thread_cpu_time_id.valid ? Qtrue : Qfalse,
|
|
1145
|
-
|
|
1355
|
+
#ifdef __APPLE__
|
|
1356
|
+
ID2SYM(rb_intern("thread_cpu_time_id")), /* => */ ULL2NUM(thread_context->thread_cpu_time_id.clock_id),
|
|
1357
|
+
#else
|
|
1358
|
+
ID2SYM(rb_intern("thread_cpu_time_id")), /* => */ CLOCKID2NUM(thread_context->thread_cpu_time_id.clock_id),
|
|
1359
|
+
#endif
|
|
1146
1360
|
ID2SYM(rb_intern("cpu_time_at_previous_sample_ns")), /* => */ LONG2NUM(thread_context->cpu_time_at_previous_sample_ns),
|
|
1147
1361
|
ID2SYM(rb_intern("wall_time_at_previous_sample_ns")), /* => */ LONG2NUM(thread_context->wall_time_at_previous_sample_ns),
|
|
1148
1362
|
|
|
1149
1363
|
ID2SYM(rb_intern("gc_tracking.cpu_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.cpu_time_at_start_ns),
|
|
1150
1364
|
ID2SYM(rb_intern("gc_tracking.wall_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.wall_time_at_start_ns),
|
|
1151
1365
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1366
|
+
ID2SYM(rb_intern("gvl_waiting_at")), /* => */ LONG2NUM(thread_context->gvl_waiting_at),
|
|
1367
|
+
ID2SYM(rb_intern("gvl_state_change_count")), /* => */ ULL2NUM(thread_context->gvl_state_change_count),
|
|
1368
|
+
ID2SYM(rb_intern("gvl_state_change_count_at_previous_sample")), /* => */ ULL2NUM(thread_context->gvl_state_change_count_at_previous_sample),
|
|
1369
|
+
ID2SYM(rb_intern("was_skipped_at_last_sample")), /* => */ thread_context->was_skipped_at_last_sample ? Qtrue : Qfalse,
|
|
1370
|
+
ID2SYM(rb_intern("is_profiler_internal_thread")), /* => */ thread_context->is_profiler_internal_thread ? Qtrue : Qfalse,
|
|
1155
1371
|
};
|
|
1156
1372
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(context_as_hash, arguments[i], arguments[i+1]);
|
|
1157
1373
|
|
|
1158
|
-
return
|
|
1374
|
+
return context_as_hash;
|
|
1159
1375
|
}
|
|
1160
1376
|
|
|
1161
|
-
static VALUE
|
|
1377
|
+
static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash) {
|
|
1162
1378
|
// Update this when modifying state struct (stats inner struct)
|
|
1163
|
-
VALUE stats_as_hash = rb_hash_new();
|
|
1164
1379
|
VALUE arguments[] = {
|
|
1380
|
+
ID2SYM(rb_intern("sample_count")), /* => */ UINT2NUM(state->stats.sample_count),
|
|
1165
1381
|
ID2SYM(rb_intern("gc_samples")), /* => */ UINT2NUM(state->stats.gc_samples),
|
|
1166
1382
|
ID2SYM(rb_intern("gc_samples_missed_due_to_missing_context")), /* => */ UINT2NUM(state->stats.gc_samples_missed_due_to_missing_context),
|
|
1383
|
+
ID2SYM(rb_intern("inactive_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.inactive_thread_samples_skipped),
|
|
1384
|
+
ID2SYM(rb_intern("profiler_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.profiler_thread_samples_skipped),
|
|
1167
1385
|
};
|
|
1168
|
-
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(
|
|
1169
|
-
return
|
|
1386
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
|
|
1387
|
+
return hash;
|
|
1170
1388
|
}
|
|
1171
1389
|
|
|
1172
|
-
static VALUE gc_tracking_as_ruby_hash(
|
|
1390
|
+
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state) {
|
|
1173
1391
|
// Update this when modifying state struct (gc_tracking inner struct)
|
|
1174
1392
|
VALUE result = rb_hash_new();
|
|
1175
1393
|
VALUE arguments[] = {
|
|
@@ -1182,72 +1400,82 @@ static VALUE gc_tracking_as_ruby_hash(struct thread_context_collector_state *sta
|
|
|
1182
1400
|
return result;
|
|
1183
1401
|
}
|
|
1184
1402
|
|
|
1185
|
-
static void remove_context_for_dead_threads(struct thread_context_collector_state *state) {
|
|
1186
|
-
st_foreach(state->hash_map_per_thread_context, remove_if_dead_thread, 0 /* unused */);
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, DDTRACE_UNUSED st_data_t _argument) {
|
|
1190
|
-
VALUE thread = (VALUE) key_thread;
|
|
1191
|
-
struct per_thread_context* thread_context = (struct per_thread_context*) value_context;
|
|
1192
|
-
|
|
1193
|
-
if (is_thread_alive(thread)) return ST_CONTINUE;
|
|
1194
|
-
|
|
1195
|
-
free_context(thread_context);
|
|
1196
|
-
return ST_DELETE;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
1403
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1200
1404
|
// It SHOULD NOT be used for other purposes.
|
|
1201
1405
|
//
|
|
1202
|
-
// Returns the whole contents of the per_thread_context structs being tracked.
|
|
1406
|
+
// Returns the whole contents of the per_thread_context structs being tracked, by iterating all live threads.
|
|
1203
1407
|
static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1204
|
-
|
|
1205
|
-
TypedData_Get_Struct(collector_instance,
|
|
1408
|
+
thread_context_collector_state *state;
|
|
1409
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1206
1410
|
|
|
1207
|
-
|
|
1411
|
+
VALUE result = rb_hash_new();
|
|
1412
|
+
VALUE threads = thread_list(state);
|
|
1413
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
1414
|
+
for (long i = 0; i < thread_count; i++) {
|
|
1415
|
+
VALUE thread = RARRAY_AREF(threads, i);
|
|
1416
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1417
|
+
if (thread_context != NULL) {
|
|
1418
|
+
rb_hash_aset(result, thread, per_thread_context_to_ruby_hash(thread_context));
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return result;
|
|
1208
1422
|
}
|
|
1209
1423
|
|
|
1210
|
-
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns,
|
|
1424
|
+
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, per_thread_context *thread_context) {
|
|
1211
1425
|
// If we didn't have a time for the previous sample, we use the current one
|
|
1212
1426
|
if (*time_at_previous_sample_ns == INVALID_TIME) *time_at_previous_sample_ns = current_time_ns;
|
|
1213
1427
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1428
|
+
// We don't expect to be sampling a thread (and thus updating these counters) while Ruby is doing GC (between
|
|
1429
|
+
// `thread_context_collector_on_gc_start` and `thread_context_collector_on_gc_finish`)
|
|
1430
|
+
if (thread_context->gc_tracking.cpu_time_at_start_ns != INVALID_TIME) {
|
|
1431
|
+
raise_error(
|
|
1432
|
+
rb_eRuntimeError,
|
|
1433
|
+
"BUG: Unexpected sample during GC (thread_id=%s, gc_tracking.cpu_time_at_start_ns=%ld, "
|
|
1434
|
+
"gc_tracking.wall_time_at_start_ns=%ld, monotonic_wall_time_now_ns=%ld)",
|
|
1435
|
+
thread_context->thread_id,
|
|
1436
|
+
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
1437
|
+
thread_context->gc_tracking.wall_time_at_start_ns,
|
|
1438
|
+
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1216
1441
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1442
|
+
long elapsed_time_ns = current_time_ns - *time_at_previous_sample_ns; // Capture all time since previous sample
|
|
1443
|
+
*time_at_previous_sample_ns = current_time_ns;
|
|
1219
1444
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
} else {
|
|
1223
|
-
elapsed_time_ns = gc_start_time_ns - *time_at_previous_sample_ns; // Capture time between previous sample and start of GC only
|
|
1224
|
-
}
|
|
1445
|
+
return elapsed_time_ns;
|
|
1446
|
+
}
|
|
1225
1447
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1448
|
+
static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns) {
|
|
1449
|
+
long elapsed_time_ns = update_time_since_previous_sample(
|
|
1450
|
+
&thread_context->cpu_time_at_previous_sample_ns,
|
|
1451
|
+
current_cpu_time_ns,
|
|
1452
|
+
thread_context
|
|
1453
|
+
);
|
|
1232
1454
|
|
|
1455
|
+
// We don't expect cpu-time to go backwards, so let's flag this as a bug
|
|
1233
1456
|
if (elapsed_time_ns < 0) {
|
|
1234
|
-
|
|
1235
|
-
// Wall-time can actually go backwards (e.g. when the system clock gets set) so we can't assume time going backwards
|
|
1236
|
-
// was a bug.
|
|
1237
|
-
// @ivoanjo: I've also observed time going backwards spuriously on macOS, see discussion on
|
|
1238
|
-
// https://github.com/DataDog/dd-trace-rb/pull/2336.
|
|
1239
|
-
elapsed_time_ns = 0;
|
|
1240
|
-
} else {
|
|
1241
|
-
// We don't expect non-wall time to go backwards, so let's flag this as a bug
|
|
1242
|
-
rb_raise(rb_eRuntimeError, "BUG: Unexpected negative elapsed_time_ns between samples");
|
|
1243
|
-
}
|
|
1457
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected CPU time going backwards between samples");
|
|
1244
1458
|
}
|
|
1245
1459
|
|
|
1246
1460
|
return elapsed_time_ns;
|
|
1247
1461
|
}
|
|
1248
1462
|
|
|
1463
|
+
static long update_wall_time_since_previous_sample(per_thread_context *thread_context, long current_wall_time_ns) {
|
|
1464
|
+
long elapsed_time_ns = update_time_since_previous_sample(
|
|
1465
|
+
&thread_context->wall_time_at_previous_sample_ns,
|
|
1466
|
+
current_wall_time_ns,
|
|
1467
|
+
thread_context
|
|
1468
|
+
);
|
|
1469
|
+
|
|
1470
|
+
// Wall-time can actually go backwards (e.g. when the system clock gets set) so we can't assume time going backwards
|
|
1471
|
+
// was a bug.
|
|
1472
|
+
// @ivoanjo: I've also observed time going backwards spuriously on macOS, see discussion on
|
|
1473
|
+
// https://github.com/DataDog/dd-trace-rb/pull/2336.
|
|
1474
|
+
return long_max_of(elapsed_time_ns, 0);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1249
1477
|
// Safety: This function is assumed never to raise exceptions by callers
|
|
1250
|
-
static long cpu_time_now_ns(
|
|
1478
|
+
static long cpu_time_now_ns(per_thread_context *thread_context) {
|
|
1251
1479
|
thread_cpu_time cpu_time = thread_cpu_time_for(thread_context->thread_cpu_time_id);
|
|
1252
1480
|
|
|
1253
1481
|
if (!cpu_time.valid) {
|
|
@@ -1278,33 +1506,43 @@ static long thread_id_for(VALUE thread) {
|
|
|
1278
1506
|
}
|
|
1279
1507
|
|
|
1280
1508
|
VALUE enforce_thread_context_collector_instance(VALUE object) {
|
|
1281
|
-
|
|
1509
|
+
ENFORCE_TYPED_DATA(object, &thread_context_collector_typed_data);
|
|
1282
1510
|
return object;
|
|
1283
1511
|
}
|
|
1284
1512
|
|
|
1513
|
+
// Finalize any pending heap allocation recordings.
|
|
1514
|
+
// On Ruby 4+, heap allocations are recorded in two phases: during on_newobj_event we capture
|
|
1515
|
+
// the object reference, then later we safely call rb_obj_id() to get the object ID.
|
|
1516
|
+
void thread_context_collector_after_allocation(VALUE self_instance) {
|
|
1517
|
+
thread_context_collector_state *state;
|
|
1518
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1519
|
+
|
|
1520
|
+
recorder_after_sample(state->recorder_instance);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1285
1523
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1286
1524
|
// It SHOULD NOT be used for other purposes.
|
|
1287
1525
|
static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1288
|
-
|
|
1289
|
-
TypedData_Get_Struct(collector_instance,
|
|
1526
|
+
thread_context_collector_state *state;
|
|
1527
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1290
1528
|
|
|
1291
|
-
return
|
|
1529
|
+
return stats_to_ruby_hash(state, rb_hash_new());
|
|
1292
1530
|
}
|
|
1293
1531
|
|
|
1294
1532
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1295
1533
|
// It SHOULD NOT be used for other purposes.
|
|
1296
1534
|
static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1297
|
-
|
|
1298
|
-
TypedData_Get_Struct(collector_instance,
|
|
1535
|
+
thread_context_collector_state *state;
|
|
1536
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1299
1537
|
|
|
1300
1538
|
return gc_tracking_as_ruby_hash(state);
|
|
1301
1539
|
}
|
|
1302
1540
|
|
|
1303
1541
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
1304
1542
|
static void trace_identifiers_for(
|
|
1305
|
-
|
|
1543
|
+
thread_context_collector_state *state,
|
|
1306
1544
|
VALUE thread,
|
|
1307
|
-
|
|
1545
|
+
trace_identifiers *trace_identifiers_result,
|
|
1308
1546
|
bool is_safe_to_allocate_objects
|
|
1309
1547
|
) {
|
|
1310
1548
|
if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
|
|
@@ -1383,34 +1621,55 @@ static bool should_collect_resource(VALUE root_span) {
|
|
|
1383
1621
|
//
|
|
1384
1622
|
// Assumption: This method gets called BEFORE restarting profiling -- e.g. there are no components attempting to
|
|
1385
1623
|
// trigger samples at the same time.
|
|
1624
|
+
//
|
|
1625
|
+
// Note that tests call this method directly in the same process without forking,
|
|
1626
|
+
// and in such a case non-current Threads keep running.
|
|
1386
1627
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
1387
|
-
|
|
1388
|
-
TypedData_Get_Struct(collector_instance,
|
|
1389
|
-
|
|
1390
|
-
// Release all context memory before clearing the existing context
|
|
1391
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
|
1392
|
-
|
|
1393
|
-
st_clear(state->hash_map_per_thread_context);
|
|
1628
|
+
thread_context_collector_state *state;
|
|
1629
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1394
1630
|
|
|
1395
1631
|
state->stats = (struct stats) {}; // Resets all stats back to zero
|
|
1396
1632
|
|
|
1633
|
+
// No need to clean-up the per-thread context because the CpuAndWallTimeWorker always cleans
|
|
1634
|
+
// it up unconditionally on every start/restart and that includes after a fork.
|
|
1635
|
+
|
|
1397
1636
|
rb_funcall(state->recorder_instance, rb_intern("reset_after_fork"), 0);
|
|
1398
1637
|
|
|
1399
1638
|
return Qtrue;
|
|
1400
1639
|
}
|
|
1401
1640
|
|
|
1402
|
-
static VALUE thread_list(
|
|
1641
|
+
static VALUE thread_list(thread_context_collector_state *state) {
|
|
1403
1642
|
VALUE result = state->thread_list_buffer;
|
|
1404
1643
|
rb_ary_clear(result);
|
|
1405
1644
|
ddtrace_thread_list(result);
|
|
1406
1645
|
return result;
|
|
1407
1646
|
}
|
|
1408
1647
|
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1648
|
+
// Inside a signal handler, we don't want to do the whole work of recording a sample, but we only record the stack of
|
|
1649
|
+
// the current thread.
|
|
1650
|
+
//
|
|
1651
|
+
// Assumptions for this function are same as for `thread_context_collector_sample` except that this function is
|
|
1652
|
+
// expected to be called from a signal handler and to be async-signal-safe.
|
|
1653
|
+
//
|
|
1654
|
+
// Also, no allocation (Ruby or malloc) can happen.
|
|
1655
|
+
bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
1413
1656
|
VALUE current_thread = rb_thread_current();
|
|
1657
|
+
per_thread_context *thread_context = get_per_thread_context(current_thread);
|
|
1658
|
+
if (thread_context == NULL) return false;
|
|
1659
|
+
|
|
1660
|
+
return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
// This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should never allocate in the
|
|
1664
|
+
// Ruby heap.
|
|
1665
|
+
//
|
|
1666
|
+
// Returns true if the after_allocation needs to be called (to do work that can't be done from inside the
|
|
1667
|
+
// tracepoint, such as allocate new objects), and false if it doesn't
|
|
1668
|
+
//
|
|
1669
|
+
// The callers must ensure thread_context is non-NULL.
|
|
1670
|
+
bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object) {
|
|
1671
|
+
thread_context_collector_state *state;
|
|
1672
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1414
1673
|
|
|
1415
1674
|
enum ruby_value_type type = rb_type(new_object);
|
|
1416
1675
|
|
|
@@ -1419,11 +1678,8 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
|
1419
1678
|
|
|
1420
1679
|
// Since this is stack allocated, be careful about moving it
|
|
1421
1680
|
ddog_CharSlice class_name;
|
|
1422
|
-
ddog_CharSlice *optional_class_name = NULL;
|
|
1423
1681
|
char imemo_type[100];
|
|
1424
1682
|
|
|
1425
|
-
optional_class_name = &class_name;
|
|
1426
|
-
|
|
1427
1683
|
if (
|
|
1428
1684
|
type == RUBY_T_OBJECT ||
|
|
1429
1685
|
type == RUBY_T_CLASS ||
|
|
@@ -1479,33 +1735,48 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
|
1479
1735
|
class_name = ruby_vm_type; // For other weird internal things we just use the VM type
|
|
1480
1736
|
}
|
|
1481
1737
|
|
|
1482
|
-
track_object(state->recorder_instance, new_object, sample_weight,
|
|
1738
|
+
bool needs_after_allocation = track_object(state->recorder_instance, new_object, sample_weight, class_name);
|
|
1483
1739
|
|
|
1484
|
-
|
|
1740
|
+
VALUE current_thread = rb_thread_current();
|
|
1485
1741
|
|
|
1486
1742
|
trigger_sample_for_thread(
|
|
1487
1743
|
state,
|
|
1488
|
-
|
|
1489
|
-
/* stack_from_thread: */ current_thread,
|
|
1744
|
+
current_thread,
|
|
1490
1745
|
thread_context,
|
|
1491
|
-
thread_context->sampling_buffer,
|
|
1492
1746
|
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
|
|
1493
1747
|
INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
|
|
1494
1748
|
&ruby_vm_type,
|
|
1495
|
-
|
|
1749
|
+
&class_name,
|
|
1496
1750
|
/* is_gvl_waiting_state: */ false,
|
|
1497
1751
|
/* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
|
|
1498
1752
|
);
|
|
1753
|
+
|
|
1754
|
+
return needs_after_allocation;
|
|
1499
1755
|
}
|
|
1500
1756
|
|
|
1501
1757
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1502
1758
|
// It SHOULD NOT be used for other purposes.
|
|
1503
1759
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object) {
|
|
1504
|
-
|
|
1505
|
-
|
|
1760
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
1761
|
+
if (thread_context == NULL) {
|
|
1762
|
+
rb_raise(rb_eRuntimeError, "Missing per_thread_context for current thread in _native_sample_allocation");
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
debug_enter_unsafe_context();
|
|
1766
|
+
|
|
1767
|
+
bool needs_after_allocation = thread_context_collector_sample_allocation(collector_instance, thread_context, NUM2UINT(sample_weight), new_object);
|
|
1768
|
+
|
|
1769
|
+
debug_leave_unsafe_context();
|
|
1770
|
+
|
|
1771
|
+
// We could instead choose to automatically trigger the after allocation here; yet, it seems kinda nice to keep it manual for
|
|
1772
|
+
// the tests so we can pull on each lever separately and observe "the sausage being made" in steps
|
|
1773
|
+
return needs_after_allocation ? Qtrue : Qfalse;
|
|
1506
1774
|
}
|
|
1507
1775
|
|
|
1508
|
-
static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
|
|
1776
|
+
static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
|
|
1777
|
+
rb_thread_sleep(INT_MAX);
|
|
1778
|
+
return Qnil;
|
|
1779
|
+
}
|
|
1509
1780
|
|
|
1510
1781
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1511
1782
|
// It SHOULD NOT be used for other purposes.
|
|
@@ -1549,7 +1820,7 @@ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
|
|
|
1549
1820
|
return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
|
|
1550
1821
|
}
|
|
1551
1822
|
|
|
1552
|
-
static VALUE get_otel_current_span_key(
|
|
1823
|
+
static VALUE get_otel_current_span_key(thread_context_collector_state *state, bool is_safe_to_allocate_objects) {
|
|
1553
1824
|
if (state->otel_current_span_key == Qtrue) { // Qtrue means we haven't tried to extract it yet
|
|
1554
1825
|
if (!is_safe_to_allocate_objects) {
|
|
1555
1826
|
// Calling read_otel_current_span_key_const below can trigger exceptions and arbitrary Ruby code running (e.g.
|
|
@@ -1572,7 +1843,7 @@ static VALUE get_otel_current_span_key(struct thread_context_collector_state *st
|
|
|
1572
1843
|
// This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
|
|
1573
1844
|
// differently, and this codepath handles it.
|
|
1574
1845
|
static void ddtrace_otel_trace_identifiers_for(
|
|
1575
|
-
|
|
1846
|
+
thread_context_collector_state *state,
|
|
1576
1847
|
VALUE *active_trace,
|
|
1577
1848
|
VALUE *root_span,
|
|
1578
1849
|
VALUE *numeric_span_id,
|
|
@@ -1597,7 +1868,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
1597
1868
|
// trace and span representing it. Each ddtrace trace is then connected to the previous otel span, forming a linked
|
|
1598
1869
|
// list. The local root span is going to be the trace/span we find at the end of this linked list.
|
|
1599
1870
|
while (otel_values != Qnil) {
|
|
1600
|
-
VALUE otel_span =
|
|
1871
|
+
VALUE otel_span = safely_lookup_hash_without_going_into_ruby_code(otel_values, otel_current_span_key);
|
|
1601
1872
|
if (otel_span == Qnil) break;
|
|
1602
1873
|
VALUE next_trace = rb_ivar_get(otel_span, at_datadog_trace_id);
|
|
1603
1874
|
if (next_trace == Qnil) break;
|
|
@@ -1616,8 +1887,8 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
1616
1887
|
}
|
|
1617
1888
|
|
|
1618
1889
|
void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples) {
|
|
1619
|
-
|
|
1620
|
-
TypedData_Get_Struct(self_instance,
|
|
1890
|
+
thread_context_collector_state *state;
|
|
1891
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1621
1892
|
|
|
1622
1893
|
ddog_prof_Label labels[] = {
|
|
1623
1894
|
// Providing .num = 0 should not be needed but the tracer-2.7 docker image ships a buggy gcc that complains about this
|
|
@@ -1640,10 +1911,51 @@ void thread_context_collector_sample_skipped_allocation_samples(VALUE self_insta
|
|
|
1640
1911
|
}
|
|
1641
1912
|
|
|
1642
1913
|
static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples) {
|
|
1914
|
+
debug_enter_unsafe_context();
|
|
1915
|
+
|
|
1643
1916
|
thread_context_collector_sample_skipped_allocation_samples(collector_instance, NUM2UINT(skipped_samples));
|
|
1917
|
+
|
|
1918
|
+
debug_leave_unsafe_context();
|
|
1919
|
+
|
|
1644
1920
|
return Qtrue;
|
|
1645
1921
|
}
|
|
1646
1922
|
|
|
1923
|
+
#ifndef NO_CURRENT_FIBER_FOR // Ruby 3.1+
|
|
1924
|
+
static VALUE otel_context_storage_for(thread_context_collector_state *state, VALUE thread) {
|
|
1925
|
+
if (state->otel_context_source == OTEL_CONTEXT_SOURCE_FIBER_IVAR) { // otel-api 1.5+
|
|
1926
|
+
VALUE current_fiber = current_fiber_for(thread);
|
|
1927
|
+
return current_fiber == Qnil ? Qnil : rb_ivar_get(current_fiber, otel_fiber_context_storage_id /* @opentelemetry_context */);
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
if (state->otel_context_source == OTEL_CONTEXT_SOURCE_FIBER_LOCAL) { // otel-api < 1.5
|
|
1931
|
+
return rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// If we got here, it means we never observed a context being set. Let's probe which one to use.
|
|
1935
|
+
VALUE current_fiber = current_fiber_for(thread);
|
|
1936
|
+
if (current_fiber != Qnil) {
|
|
1937
|
+
VALUE context_storage = rb_ivar_get(current_fiber, otel_fiber_context_storage_id /* @opentelemetry_context */);
|
|
1938
|
+
if (context_storage != Qnil) {
|
|
1939
|
+
state->otel_context_source = OTEL_CONTEXT_SOURCE_FIBER_IVAR; // Remember for next time
|
|
1940
|
+
return context_storage;
|
|
1941
|
+
}
|
|
1942
|
+
} else {
|
|
1943
|
+
VALUE context_storage = rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
|
|
1944
|
+
if (context_storage != Qnil) {
|
|
1945
|
+
state->otel_context_source = OTEL_CONTEXT_SOURCE_FIBER_LOCAL; // Remember for next time
|
|
1946
|
+
return context_storage;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// There's no context storage attached to the current thread
|
|
1951
|
+
return Qnil;
|
|
1952
|
+
}
|
|
1953
|
+
#else
|
|
1954
|
+
static inline VALUE otel_context_storage_for(DDTRACE_UNUSED thread_context_collector_state *state, VALUE thread) {
|
|
1955
|
+
return rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
|
|
1956
|
+
}
|
|
1957
|
+
#endif
|
|
1958
|
+
|
|
1647
1959
|
// This method differs from trace_identifiers_for/ddtrace_otel_trace_identifiers_for to support the situation where
|
|
1648
1960
|
// the opentelemetry ruby library is being used for tracing AND the ddtrace tracing bits are not involved at all.
|
|
1649
1961
|
//
|
|
@@ -1666,12 +1978,12 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
|
|
|
1666
1978
|
// root span id.
|
|
1667
1979
|
// This matches the semantics of how ddtrace tracing creates a TraceOperation and assigns a local root span to it.
|
|
1668
1980
|
static void otel_without_ddtrace_trace_identifiers_for(
|
|
1669
|
-
|
|
1981
|
+
thread_context_collector_state *state,
|
|
1670
1982
|
VALUE thread,
|
|
1671
|
-
|
|
1983
|
+
trace_identifiers *trace_identifiers_result,
|
|
1672
1984
|
bool is_safe_to_allocate_objects
|
|
1673
1985
|
) {
|
|
1674
|
-
VALUE context_storage =
|
|
1986
|
+
VALUE context_storage = otel_context_storage_for(state, thread);
|
|
1675
1987
|
|
|
1676
1988
|
// If it exists, context_storage is expected to be an Array[OpenTelemetry::Context]
|
|
1677
1989
|
if (context_storage == Qnil || !RB_TYPE_P(context_storage, T_ARRAY)) return;
|
|
@@ -1679,17 +1991,17 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
|
1679
1991
|
VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
|
|
1680
1992
|
if (otel_current_span_key == Qnil) return;
|
|
1681
1993
|
|
|
1682
|
-
|
|
1994
|
+
long active_context_index = RARRAY_LEN(context_storage) - 1;
|
|
1683
1995
|
if (active_context_index < 0) return;
|
|
1684
1996
|
|
|
1685
|
-
|
|
1997
|
+
otel_span active_span = otel_span_from(rb_ary_entry(context_storage, active_context_index), otel_current_span_key);
|
|
1686
1998
|
if (active_span.span == Qnil) return;
|
|
1687
1999
|
|
|
1688
|
-
|
|
2000
|
+
otel_span local_root_span = active_span;
|
|
1689
2001
|
|
|
1690
2002
|
// Now find the oldest span starting from the active span that still has the same trace id as the active span
|
|
1691
|
-
for (
|
|
1692
|
-
|
|
2003
|
+
for (long i = active_context_index - 1; i >= 0; i--) {
|
|
2004
|
+
otel_span checking_span = otel_span_from(rb_ary_entry(context_storage, i), otel_current_span_key);
|
|
1693
2005
|
if (checking_span.span == Qnil) return;
|
|
1694
2006
|
|
|
1695
2007
|
if (rb_str_equal(active_span.trace_id, checking_span.trace_id) == Qfalse) break;
|
|
@@ -1709,7 +2021,7 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
|
1709
2021
|
|
|
1710
2022
|
VALUE root_span_type = rb_ivar_get(local_root_span.span, at_kind_id /* @kind */);
|
|
1711
2023
|
// We filter out spans that don't have `kind: :server`
|
|
1712
|
-
if (root_span_type == Qnil || !RB_TYPE_P(root_span_type, T_SYMBOL) || SYM2ID(root_span_type) != server_id) return;
|
|
2024
|
+
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;
|
|
1713
2025
|
|
|
1714
2026
|
VALUE trace_resource = rb_ivar_get(local_root_span.span, at_name_id /* @name */);
|
|
1715
2027
|
if (!RB_TYPE_P(trace_resource, T_STRING)) return;
|
|
@@ -1717,8 +2029,8 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
|
1717
2029
|
trace_identifiers_result->trace_endpoint = trace_resource;
|
|
1718
2030
|
}
|
|
1719
2031
|
|
|
1720
|
-
static
|
|
1721
|
-
|
|
2032
|
+
static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key) {
|
|
2033
|
+
otel_span failed = {.span = Qnil, .span_id = Qnil, .trace_id = Qnil};
|
|
1722
2034
|
|
|
1723
2035
|
if (otel_context == Qnil) return failed;
|
|
1724
2036
|
|
|
@@ -1726,7 +2038,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp
|
|
|
1726
2038
|
if (context_entries == Qnil || !RB_TYPE_P(context_entries, T_HASH)) return failed;
|
|
1727
2039
|
|
|
1728
2040
|
// If it exists, context_entries is expected to be a Hash[OpenTelemetry::Context::Key, OpenTelemetry::Trace::Span]
|
|
1729
|
-
VALUE span =
|
|
2041
|
+
VALUE span = safely_lookup_hash_without_going_into_ruby_code(context_entries, otel_current_span_key);
|
|
1730
2042
|
if (span == Qnil) return failed;
|
|
1731
2043
|
|
|
1732
2044
|
// If it exists, span_context is expected to be a OpenTelemetry::Trace::SpanContext (don't confuse it with OpenTelemetry::Context)
|
|
@@ -1737,7 +2049,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp
|
|
|
1737
2049
|
VALUE trace_id = rb_ivar_get(span_context, at_trace_id_id /* @trace_id */);
|
|
1738
2050
|
if (span_id == Qnil || trace_id == Qnil || !RB_TYPE_P(span_id, T_STRING) || !RB_TYPE_P(trace_id, T_STRING)) return failed;
|
|
1739
2051
|
|
|
1740
|
-
return (
|
|
2052
|
+
return (otel_span) {.span = span, .span_id = span_id, .trace_id = trace_id};
|
|
1741
2053
|
}
|
|
1742
2054
|
|
|
1743
2055
|
// Otel span ids are represented as a big-endian 8-byte string
|
|
@@ -1757,57 +2069,179 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1757
2069
|
((uint64_t)span_bytes[7]);
|
|
1758
2070
|
}
|
|
1759
2071
|
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
//
|
|
1766
|
-
// Instead, we ask Ruby to hold the data we need in Ruby's own special per-thread context area
|
|
1767
|
-
// that's thread-safe and built for this kind of use
|
|
1768
|
-
//
|
|
1769
|
-
// Also, this function can get called on the non-main Ractor. We deal with this by checking if the value in the context
|
|
1770
|
-
// is non-zero, since only `initialize_context` ever sets the value from 0 to non-zero for threads it sees.
|
|
1771
|
-
intptr_t thread_being_profiled = gvl_profiling_state_get(thread);
|
|
1772
|
-
if (!thread_being_profiled) return;
|
|
2072
|
+
void thread_context_collector_stats(VALUE self_instance, VALUE stats_hash) {
|
|
2073
|
+
thread_context_collector_state *state;
|
|
2074
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2075
|
+
stats_to_ruby_hash(state, stats_hash);
|
|
2076
|
+
}
|
|
1773
2077
|
|
|
1774
|
-
|
|
1775
|
-
|
|
2078
|
+
void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
2079
|
+
thread_context_collector_state *state;
|
|
2080
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2081
|
+
state->stats = (struct stats) {};
|
|
2082
|
+
}
|
|
1776
2083
|
|
|
1777
|
-
|
|
2084
|
+
static void mark_thread_as_profiler_internal(per_thread_context *ctx) {
|
|
2085
|
+
ctx->is_profiler_internal_thread = true;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
void thread_context_collector_profiler_internal_thread_started(void) {
|
|
2089
|
+
per_thread_context *ctx = get_or_create_context_for(rb_thread_current());
|
|
2090
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2094
|
+
per_thread_context *ctx = get_or_create_context_for(thread);
|
|
2095
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2096
|
+
return Qnil;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
// Called via rb_ensure when a profiler-internal thread (worker or idle helper) is about to exit.
|
|
2100
|
+
// Records a final sample so the thread's accumulated cpu/wall time since the last on_serialize
|
|
2101
|
+
// flush is not lost. on_serialize (below) also flushes profiler-internal threads during periodic
|
|
2102
|
+
// serialization, but it can't help at shutdown: by the time the final serialize runs, these
|
|
2103
|
+
// threads are already dead and absent from thread_list.
|
|
2104
|
+
void thread_context_collector_profiler_internal_thread_done(VALUE self_instance) {
|
|
2105
|
+
thread_context_collector_state *state;
|
|
2106
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2107
|
+
|
|
2108
|
+
VALUE current_thread = rb_thread_current();
|
|
2109
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
2110
|
+
if (!thread_context->is_profiler_internal_thread) {
|
|
2111
|
+
rb_raise(rb_eRuntimeError, "current thread %"PRIsVALUE" is not profiler-internal thread", current_thread);
|
|
1778
2112
|
}
|
|
1779
2113
|
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
2114
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2115
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2116
|
+
|
|
2117
|
+
update_metrics_and_sample(
|
|
2118
|
+
state,
|
|
2119
|
+
current_thread,
|
|
2120
|
+
thread_context,
|
|
2121
|
+
current_cpu_time_ns,
|
|
2122
|
+
current_monotonic_wall_time_ns,
|
|
2123
|
+
true);
|
|
2124
|
+
}
|
|
1784
2125
|
|
|
1785
|
-
|
|
1786
|
-
|
|
2126
|
+
// Flushes threads whose last per-tick sample was skipped (either by the SUSPENDED-skip
|
|
2127
|
+
// optimization, or by is_profiler_internal_thread) so their accumulated time is recorded.
|
|
2128
|
+
// Called by the stack recorder at the start of _native_serialize (regular periodic flush).
|
|
2129
|
+
void thread_context_collector_on_serialize(VALUE self_instance) {
|
|
2130
|
+
thread_context_collector_state *state;
|
|
2131
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1787
2132
|
|
|
2133
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2134
|
+
VALUE threads = thread_list(state);
|
|
2135
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
2136
|
+
|
|
2137
|
+
for (long i = 0; i < thread_count; i++) {
|
|
2138
|
+
VALUE thread = RARRAY_AREF(threads, i);
|
|
2139
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2140
|
+
|
|
2141
|
+
if (thread_context != NULL && (thread_context->was_skipped_at_last_sample || thread_context->is_profiler_internal_thread)) {
|
|
2142
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2143
|
+
// We need to force_sample=true otherwise this sample would be skipped too
|
|
2144
|
+
update_metrics_and_sample(
|
|
2145
|
+
state,
|
|
2146
|
+
thread,
|
|
2147
|
+
thread_context,
|
|
2148
|
+
current_cpu_time_ns,
|
|
2149
|
+
current_monotonic_wall_time_ns,
|
|
2150
|
+
true);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
void thread_context_collector_on_gvl_released(per_thread_context *thread_context) {
|
|
2156
|
+
thread_context->gvl_state_change_count |= GVL_SUSPENDED;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
void thread_context_collector_on_gvl_waiting(per_thread_context *thread_context) {
|
|
2160
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
|
2161
|
+
if (current_monotonic_wall_time_ns <= 0) return;
|
|
2162
|
+
|
|
2163
|
+
thread_context->gvl_waiting_at = current_monotonic_wall_time_ns;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2167
|
+
ENFORCE_THREAD(thread);
|
|
2168
|
+
|
|
2169
|
+
debug_enter_unsafe_context();
|
|
2170
|
+
|
|
2171
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2172
|
+
if (thread_context) thread_context_collector_on_gvl_waiting(thread_context);
|
|
2173
|
+
|
|
2174
|
+
debug_leave_unsafe_context();
|
|
2175
|
+
|
|
2176
|
+
return Qnil;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2180
|
+
ENFORCE_THREAD(thread);
|
|
2181
|
+
|
|
2182
|
+
debug_enter_unsafe_context();
|
|
2183
|
+
|
|
2184
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2185
|
+
if (thread_context) thread_context_collector_on_gvl_released(thread_context);
|
|
2186
|
+
|
|
2187
|
+
debug_leave_unsafe_context();
|
|
2188
|
+
|
|
2189
|
+
return Qnil;
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
|
2193
|
+
// This function runs on the passed thread and has the GVL because it gets called just after the Ruby thread acquired the GVL
|
|
2194
|
+
__attribute__((warn_unused_result))
|
|
2195
|
+
on_gvl_running_result thread_context_collector_on_gvl_running(VALUE self_instance, VALUE thread, per_thread_context *thread_context) {
|
|
2196
|
+
thread_context_collector_state *state;
|
|
2197
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2198
|
+
|
|
2199
|
+
// Bump the event counter and clears the state bit to "running"
|
|
2200
|
+
uint64_t counter_portion = thread_context->gvl_state_change_count >> 1;
|
|
2201
|
+
thread_context->gvl_state_change_count = ((counter_portion + 1) << 1) | GVL_RUNNING;
|
|
2202
|
+
|
|
2203
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
2204
|
+
// Thread was not waiting on gvl
|
|
2205
|
+
if (gvl_waiting_at == 0) {
|
|
2206
|
+
return (on_gvl_running_result) {.action = ON_GVL_RUNNING_UNKNOWN, .waiting_for_gvl_duration_ns = 0};
|
|
2207
|
+
}
|
|
1788
2208
|
// @ivoanjo: I'm not sure if this can happen -- It means we should've sampled already but haven't gotten the chance yet?
|
|
1789
|
-
if (gvl_waiting_at < 0)
|
|
2209
|
+
if (gvl_waiting_at < 0) {
|
|
2210
|
+
return (on_gvl_running_result) {.action = ON_GVL_RUNNING_SAMPLE, .waiting_for_gvl_duration_ns = 0};
|
|
2211
|
+
}
|
|
1790
2212
|
|
|
1791
2213
|
long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - gvl_waiting_at;
|
|
1792
2214
|
|
|
1793
|
-
bool should_sample = waiting_for_gvl_duration_ns >= waiting_for_gvl_threshold_ns;
|
|
2215
|
+
bool should_sample = waiting_for_gvl_duration_ns >= state->waiting_for_gvl_threshold_ns;
|
|
1794
2216
|
|
|
1795
2217
|
if (should_sample) {
|
|
1796
2218
|
// We flip the gvl_waiting_at to negative to mark that the thread is now running and no longer waiting
|
|
1797
|
-
|
|
2219
|
+
long gvl_waiting_at_is_now_running = -gvl_waiting_at;
|
|
1798
2220
|
|
|
1799
|
-
|
|
2221
|
+
thread_context->gvl_waiting_at = gvl_waiting_at_is_now_running;
|
|
1800
2222
|
} else {
|
|
1801
|
-
|
|
1802
|
-
|
|
2223
|
+
thread_context->gvl_waiting_at = 0;
|
|
2224
|
+
|
|
2225
|
+
// Even though the GVL wait itself was below threshold, if the thread had skipped samples
|
|
2226
|
+
// (was suspended for a long time without the GVL), we still need to force a sample now.
|
|
2227
|
+
// Otherwise, the accumulated idle wall-time would be reported against whatever stack the
|
|
2228
|
+
// thread runs next, misrepresenting the time spent idle.
|
|
2229
|
+
if (thread_context->was_skipped_at_last_sample) {
|
|
2230
|
+
should_sample = true;
|
|
2231
|
+
}
|
|
1803
2232
|
}
|
|
1804
2233
|
|
|
1805
|
-
|
|
1806
|
-
|
|
2234
|
+
if (should_sample) {
|
|
2235
|
+
// We prepare the sample here because the postponed job might be called some time later,
|
|
2236
|
+
// possibly after some Ruby calls which change the Ruby stack,
|
|
2237
|
+
// and we want to attribute the time acquiring or without the GVL to the correct Ruby stack.
|
|
2238
|
+
prepare_sample_thread(thread, &thread_context->sampling_buffer);
|
|
2239
|
+
}
|
|
1807
2240
|
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
2241
|
+
return (on_gvl_running_result) {
|
|
2242
|
+
.action = should_sample ? ON_GVL_RUNNING_SAMPLE : ON_GVL_RUNNING_DONT_SAMPLE,
|
|
2243
|
+
.waiting_for_gvl_duration_ns = waiting_for_gvl_duration_ns,
|
|
2244
|
+
};
|
|
1811
2245
|
}
|
|
1812
2246
|
|
|
1813
2247
|
// Why does this method need to exist?
|
|
@@ -1827,7 +2261,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1827
2261
|
//
|
|
1828
2262
|
// Stack:
|
|
1829
2263
|
// If the thread starts working without the end of the Waiting for GVL sample, then by the time the thread is sampled
|
|
1830
|
-
// via the regular cpu/wall-time samples mechanism, the stack can be
|
|
2264
|
+
// via the regular cpu/wall-time samples mechanism, the stack can be inaccurate (e.g. does not correctly pinpoint
|
|
1831
2265
|
// where the waiting happened).
|
|
1832
2266
|
//
|
|
1833
2267
|
// Arguably, the last sample after Waiting for GVL ended (when gvl_waiting_at < 0) should always come from this method
|
|
@@ -1836,17 +2270,19 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1836
2270
|
//
|
|
1837
2271
|
// ---
|
|
1838
2272
|
//
|
|
2273
|
+
// Always called with the GVL, either from a postponed_job or from tests.
|
|
2274
|
+
//
|
|
1839
2275
|
// NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
|
|
1840
2276
|
// exists to enable testing.
|
|
1841
2277
|
VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns) {
|
|
1842
|
-
|
|
1843
|
-
TypedData_Get_Struct(self_instance,
|
|
2278
|
+
thread_context_collector_state *state;
|
|
2279
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1844
2280
|
|
|
1845
|
-
|
|
2281
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
1846
2282
|
|
|
1847
|
-
|
|
2283
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
1848
2284
|
|
|
1849
|
-
if (gvl_waiting_at >= 0) {
|
|
2285
|
+
if (gvl_waiting_at >= 0 && !thread_context->was_skipped_at_last_sample) {
|
|
1850
2286
|
// @ivoanjo: I'm not sure if this can ever happen. This means that we're not on the same thread
|
|
1851
2287
|
// that ran `thread_context_collector_on_gvl_running` and made the decision to sample OR a regular sample was
|
|
1852
2288
|
// triggered ahead of us.
|
|
@@ -1854,9 +2290,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1854
2290
|
return Qfalse;
|
|
1855
2291
|
}
|
|
1856
2292
|
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may chose to push an
|
|
2293
|
+
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may choose to push an
|
|
1860
2294
|
// extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
|
|
1861
2295
|
// cpu-time of the thread and let `update_metrics_and_sample` decide what to do with it.
|
|
1862
2296
|
long cpu_time_for_thread = cpu_time_now_ns(thread_context);
|
|
@@ -1865,13 +2299,11 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1865
2299
|
|
|
1866
2300
|
update_metrics_and_sample(
|
|
1867
2301
|
state,
|
|
1868
|
-
|
|
1869
|
-
/* stack_from_thread: */ current_thread,
|
|
2302
|
+
current_thread,
|
|
1870
2303
|
thread_context,
|
|
1871
|
-
thread_context->sampling_buffer,
|
|
1872
2304
|
cpu_time_for_thread,
|
|
1873
|
-
current_monotonic_wall_time_ns
|
|
1874
|
-
|
|
2305
|
+
current_monotonic_wall_time_ns,
|
|
2306
|
+
false);
|
|
1875
2307
|
|
|
1876
2308
|
return Qtrue;
|
|
1877
2309
|
}
|
|
@@ -1880,16 +2312,14 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1880
2312
|
// need to take when sampling cpu/wall-time for a thread that's in the "Waiting for GVL" state.
|
|
1881
2313
|
__attribute__((warn_unused_result))
|
|
1882
2314
|
static bool handle_gvl_waiting(
|
|
1883
|
-
|
|
2315
|
+
thread_context_collector_state *state,
|
|
1884
2316
|
VALUE thread_being_sampled,
|
|
1885
|
-
|
|
1886
|
-
struct per_thread_context *thread_context,
|
|
1887
|
-
sampling_buffer* sampling_buffer,
|
|
2317
|
+
per_thread_context *thread_context,
|
|
1888
2318
|
long current_cpu_time_ns
|
|
1889
2319
|
) {
|
|
1890
|
-
|
|
2320
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
1891
2321
|
|
|
1892
|
-
bool is_gvl_waiting_state = gvl_waiting_at != 0
|
|
2322
|
+
bool is_gvl_waiting_state = gvl_waiting_at != 0;
|
|
1893
2323
|
|
|
1894
2324
|
if (!is_gvl_waiting_state) return false;
|
|
1895
2325
|
|
|
@@ -1902,17 +2332,17 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1902
2332
|
// ...──────────────┬───────────────────...
|
|
1903
2333
|
// Other state │ Waiting for GVL
|
|
1904
2334
|
// ...──────────────┴───────────────────...
|
|
1905
|
-
// ▲
|
|
2335
|
+
// ▲ ▲
|
|
1906
2336
|
// └─ Previous sample └─ Regular sample (caller)
|
|
1907
2337
|
//
|
|
1908
2338
|
// In this case, we'll want to push two samples: a) one for the current time (handled by the caller), b) an extra sample
|
|
1909
|
-
// to represent the remaining cpu/wall time before the "Waiting for GVL" started:
|
|
2339
|
+
// to represent the remaining cpu/wall time before the "Waiting for GVL" started (for timeline purposes):
|
|
1910
2340
|
//
|
|
1911
2341
|
// time ─────►
|
|
1912
2342
|
// ...──────────────┬───────────────────...
|
|
1913
2343
|
// Other state │ Waiting for GVL
|
|
1914
2344
|
// ...──────────────┴───────────────────...
|
|
1915
|
-
// ▲
|
|
2345
|
+
// ▲ ▲ ▲
|
|
1916
2346
|
// └─ Prev... └─ Extra sample └─ Regular sample (caller)
|
|
1917
2347
|
//
|
|
1918
2348
|
// 2. The current sample is the n-th one after we entered the "Waiting for GVL" state
|
|
@@ -1922,7 +2352,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1922
2352
|
// ...──────────────┬───────────────────────────────────────────────...
|
|
1923
2353
|
// Other state │ Waiting for GVL
|
|
1924
2354
|
// ...──────────────┴───────────────────────────────────────────────...
|
|
1925
|
-
// ▲
|
|
2355
|
+
// ▲ ▲ ▲
|
|
1926
2356
|
// └─ Previous sample └─ Previous sample └─ Regular sample (caller)
|
|
1927
2357
|
//
|
|
1928
2358
|
// In this case, we just report back to the caller that the thread is in the "Waiting for GVL" state.
|
|
@@ -1937,33 +2367,22 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1937
2367
|
|
|
1938
2368
|
if (gvl_waiting_at < 0) {
|
|
1939
2369
|
// Negative means the waiting for GVL just ended, so we clear the state, so next samples no longer represent waiting
|
|
1940
|
-
|
|
2370
|
+
thread_context->gvl_waiting_at = 0;
|
|
1941
2371
|
}
|
|
1942
2372
|
|
|
1943
2373
|
long gvl_waiting_started_wall_time_ns = labs(gvl_waiting_at);
|
|
1944
2374
|
|
|
1945
2375
|
if (thread_context->wall_time_at_previous_sample_ns < gvl_waiting_started_wall_time_ns) { // situation 1 above
|
|
1946
|
-
long cpu_time_elapsed_ns =
|
|
1947
|
-
&thread_context->cpu_time_at_previous_sample_ns,
|
|
1948
|
-
current_cpu_time_ns,
|
|
1949
|
-
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
1950
|
-
IS_NOT_WALL_TIME
|
|
1951
|
-
);
|
|
2376
|
+
long cpu_time_elapsed_ns = update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
1952
2377
|
|
|
1953
|
-
long duration_until_start_of_gvl_waiting_ns =
|
|
1954
|
-
|
|
1955
|
-
gvl_waiting_started_wall_time_ns,
|
|
1956
|
-
INVALID_TIME,
|
|
1957
|
-
IS_WALL_TIME
|
|
1958
|
-
);
|
|
2378
|
+
long duration_until_start_of_gvl_waiting_ns =
|
|
2379
|
+
update_wall_time_since_previous_sample(thread_context, gvl_waiting_started_wall_time_ns);
|
|
1959
2380
|
|
|
1960
2381
|
// Push extra sample
|
|
1961
2382
|
trigger_sample_for_thread(
|
|
1962
2383
|
state,
|
|
1963
2384
|
thread_being_sampled,
|
|
1964
|
-
stack_from_thread,
|
|
1965
2385
|
thread_context,
|
|
1966
|
-
sampling_buffer,
|
|
1967
2386
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = duration_until_start_of_gvl_waiting_ns},
|
|
1968
2387
|
gvl_waiting_started_wall_time_ns,
|
|
1969
2388
|
NULL,
|
|
@@ -1976,57 +2395,119 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1976
2395
|
return true;
|
|
1977
2396
|
}
|
|
1978
2397
|
|
|
1979
|
-
static VALUE
|
|
2398
|
+
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
1980
2399
|
ENFORCE_THREAD(thread);
|
|
1981
2400
|
|
|
1982
|
-
|
|
1983
|
-
return Qnil;
|
|
1984
|
-
}
|
|
2401
|
+
debug_enter_unsafe_context();
|
|
1985
2402
|
|
|
1986
|
-
|
|
1987
|
-
|
|
2403
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2404
|
+
VALUE result = thread_context ? LONG2NUM(thread_context->gvl_waiting_at) : Qnil;
|
|
2405
|
+
|
|
2406
|
+
debug_leave_unsafe_context();
|
|
1988
2407
|
|
|
1989
|
-
|
|
1990
|
-
return LONG2NUM(gvl_waiting_at);
|
|
2408
|
+
return result;
|
|
1991
2409
|
}
|
|
1992
2410
|
|
|
1993
|
-
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2411
|
+
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
|
|
1994
2412
|
ENFORCE_THREAD(thread);
|
|
1995
2413
|
|
|
1996
|
-
|
|
2414
|
+
debug_enter_unsafe_context();
|
|
2415
|
+
|
|
2416
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2417
|
+
VALUE result;
|
|
2418
|
+
if (thread_context) {
|
|
2419
|
+
result = thread_context_collector_on_gvl_running(collector_instance, thread, thread_context).action == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
|
|
2420
|
+
} else {
|
|
2421
|
+
result = Qfalse;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
debug_leave_unsafe_context();
|
|
2425
|
+
|
|
2426
|
+
return result;
|
|
1997
2427
|
}
|
|
1998
2428
|
|
|
1999
|
-
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
|
|
2429
|
+
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE allow_exception) {
|
|
2000
2430
|
ENFORCE_THREAD(thread);
|
|
2431
|
+
ENFORCE_BOOLEAN(allow_exception);
|
|
2432
|
+
|
|
2433
|
+
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
2001
2434
|
|
|
2002
|
-
|
|
2435
|
+
VALUE result = thread_context_collector_sample_after_gvl_running(
|
|
2003
2436
|
collector_instance,
|
|
2004
2437
|
thread,
|
|
2005
2438
|
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
2006
2439
|
);
|
|
2007
|
-
}
|
|
2008
2440
|
|
|
2009
|
-
|
|
2010
|
-
ENFORCE_THREAD(thread);
|
|
2441
|
+
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
|
2011
2442
|
|
|
2012
|
-
|
|
2013
|
-
TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2014
|
-
|
|
2015
|
-
struct per_thread_context *thread_context = get_context_for(thread, state);
|
|
2016
|
-
if (thread_context == NULL) rb_raise(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2017
|
-
|
|
2018
|
-
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
|
2019
|
-
|
|
2020
|
-
return Qtrue;
|
|
2443
|
+
return result;
|
|
2021
2444
|
}
|
|
2022
2445
|
|
|
2023
2446
|
#else
|
|
2024
2447
|
static bool handle_gvl_waiting(
|
|
2025
|
-
DDTRACE_UNUSED
|
|
2448
|
+
DDTRACE_UNUSED thread_context_collector_state *state,
|
|
2026
2449
|
DDTRACE_UNUSED VALUE thread_being_sampled,
|
|
2027
|
-
DDTRACE_UNUSED
|
|
2028
|
-
DDTRACE_UNUSED struct per_thread_context *thread_context,
|
|
2029
|
-
DDTRACE_UNUSED sampling_buffer* sampling_buffer,
|
|
2450
|
+
DDTRACE_UNUSED per_thread_context *thread_context,
|
|
2030
2451
|
DDTRACE_UNUSED long current_cpu_time_ns
|
|
2031
2452
|
) { return false; }
|
|
2453
|
+
|
|
2032
2454
|
#endif // NO_GVL_INSTRUMENTATION
|
|
2455
|
+
|
|
2456
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns) {
|
|
2457
|
+
ENFORCE_THREAD(thread);
|
|
2458
|
+
|
|
2459
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2460
|
+
if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2461
|
+
|
|
2462
|
+
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
|
2463
|
+
|
|
2464
|
+
return Qtrue;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
#define MAX_SAFE_LOOKUP_SIZE 16
|
|
2468
|
+
|
|
2469
|
+
typedef struct { VALUE lookup_key; VALUE result; } safe_lookup_hash_state;
|
|
2470
|
+
|
|
2471
|
+
static int safe_lookup_hash_iterate(VALUE key, VALUE value, VALUE state_ptr) {
|
|
2472
|
+
safe_lookup_hash_state *state = (safe_lookup_hash_state *) state_ptr;
|
|
2473
|
+
|
|
2474
|
+
if (key == state->lookup_key) {
|
|
2475
|
+
state->result = value;
|
|
2476
|
+
return ST_STOP;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
return ST_CONTINUE;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
// This method exists because we need to look up a hash during sampling, but we don't want to invoke any
|
|
2483
|
+
// Ruby code as a side effect. To be able to look up by hash, `rb_hash_lookup` calls `#hash` on the key,
|
|
2484
|
+
// which we want to avoid.
|
|
2485
|
+
// Thus, instead, we opt to just iterate through the hash and check if we can find what we're looking for.
|
|
2486
|
+
//
|
|
2487
|
+
// To avoid having too much overhead here we only iterate in hashes up to MAX_SAFE_LOOKUP_SIZE.
|
|
2488
|
+
// Note that we don't even try to iterate if the hash is bigger -- this is to avoid flaky behavior where
|
|
2489
|
+
// depending on the internal storage order of the hash we may or not find the key, and instead we always
|
|
2490
|
+
// enforce the size.
|
|
2491
|
+
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key) {
|
|
2492
|
+
if (!RB_TYPE_P(hash, T_HASH) || RHASH_SIZE(hash) > MAX_SAFE_LOOKUP_SIZE) return Qnil;
|
|
2493
|
+
|
|
2494
|
+
safe_lookup_hash_state state = {.lookup_key = key, .result = Qnil};
|
|
2495
|
+
|
|
2496
|
+
rb_hash_foreach(hash, safe_lookup_hash_iterate, (VALUE) &state);
|
|
2497
|
+
|
|
2498
|
+
return state.result;
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
2502
|
+
thread_context_collector_state *state;
|
|
2503
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2504
|
+
|
|
2505
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2506
|
+
long system_epoch_time_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, current_monotonic_wall_time_ns);
|
|
2507
|
+
|
|
2508
|
+
return LONG2NUM(system_epoch_time_ns);
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self) {
|
|
2512
|
+
return thread_context_collector_prepare_sample_inside_signal_handler() ? Qtrue : Qfalse;
|
|
2513
|
+
}
|