datadog 2.4.0 → 2.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1261 -1
- data/README.md +3 -1
- data/bin/ddprofrb +4 -4
- data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
- 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 +653 -212
- 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 +33 -20
- data/ext/datadog_profiling_native_extension/collectors_stack.c +409 -113
- data/ext/datadog_profiling_native_extension/collectors_stack.h +41 -5
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +1208 -623
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +33 -8
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +240 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +85 -16
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +47 -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 +99 -120
- 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 +762 -608
- data/ext/datadog_profiling_native_extension/heap_recorder.h +67 -28
- 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 +3 -40
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +796 -417
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +48 -12
- data/ext/datadog_profiling_native_extension/profiling.c +93 -27
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +77 -137
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +21 -20
- 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 +458 -302
- data/ext/datadog_profiling_native_extension/stack_recorder.h +14 -3
- data/ext/datadog_profiling_native_extension/time_helpers.h +18 -11
- 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 +33 -30
- 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 +85 -16
- data/ext/libdatadog_api/datadog_ruby_common.h +47 -1
- data/ext/libdatadog_api/ddsketch.c +102 -0
- data/ext/libdatadog_api/di.c +218 -0
- data/ext/libdatadog_api/extconf.rb +44 -45
- data/ext/libdatadog_api/feature_flags.c +588 -0
- data/ext/libdatadog_api/feature_flags.h +5 -0
- data/ext/libdatadog_api/helpers.h +27 -0
- data/ext/libdatadog_api/init.c +35 -0
- data/ext/libdatadog_api/library_config.c +173 -0
- data/ext/libdatadog_api/library_config.h +25 -0
- data/ext/libdatadog_api/otel_thread_context.c +268 -0
- data/ext/libdatadog_api/otel_thread_context.h +5 -0
- data/ext/libdatadog_api/process_discovery.c +118 -0
- data/ext/libdatadog_api/process_discovery.h +5 -0
- data/ext/libdatadog_api/trace_exporter.c +1932 -0
- data/ext/libdatadog_api/trace_exporter.h +5 -0
- data/ext/libdatadog_extconf_helpers.rb +116 -18
- data/lib/datadog/ai_guard/autoload.rb +10 -0
- data/lib/datadog/ai_guard/component.rb +53 -0
- data/lib/datadog/ai_guard/configuration/ext.rb +19 -0
- data/lib/datadog/ai_guard/configuration.rb +128 -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 +73 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +31 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
- data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
- data/lib/datadog/ai_guard/evaluation/content_part.rb +48 -0
- data/lib/datadog/ai_guard/evaluation/message.rb +43 -0
- data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +36 -0
- data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +30 -0
- data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +38 -0
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +31 -0
- data/lib/datadog/ai_guard/evaluation.rb +130 -0
- data/lib/datadog/ai_guard/ext.rb +32 -0
- data/lib/datadog/ai_guard/http_client.rb +86 -0
- data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
- data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
- data/lib/datadog/ai_guard/redaction/result.rb +29 -0
- data/lib/datadog/ai_guard/redaction.rb +80 -0
- data/lib/datadog/ai_guard.rb +182 -0
- data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +68 -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 +1 -99
- 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 -75
- data/lib/datadog/appsec/compressed_json.rb +40 -0
- data/lib/datadog/appsec/configuration.rb +436 -1
- data/lib/datadog/appsec/context.rb +135 -0
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +94 -0
- data/lib/datadog/appsec/contrib/active_record/integration.rb +41 -0
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +104 -0
- 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 +34 -26
- 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 +21 -36
- 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 -106
- 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 +3 -6
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +17 -15
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +118 -114
- 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 -35
- 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/rails/request_middleware.rb +2 -1
- 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 -71
- 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/contrib/sinatra/request_middleware.rb +2 -1
- 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 -128
- data/lib/datadog/appsec/ext.rb +17 -1
- 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 -36
- data/lib/datadog/appsec/monitor.rb +1 -1
- data/lib/datadog/appsec/processor/rule_loader.rb +54 -60
- data/lib/datadog/appsec/rate_limiter.rb +1 -1
- data/lib/datadog/appsec/remote.rb +59 -70
- data/lib/datadog/appsec/response.rb +42 -94
- 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.rb +35 -28
- 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 +46 -59
- 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 +283 -54
- 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 +51 -30
- data/lib/datadog/core/configuration/options.rb +58 -19
- data/lib/datadog/core/configuration/settings.rb +341 -140
- data/lib/datadog/core/configuration/stable_config.rb +32 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +426 -0
- data/lib/datadog/core/configuration.rb +51 -21
- 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 +89 -39
- 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 +46 -9
- 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/gc.rb +1 -1
- 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/vm_cache.rb +1 -1
- data/lib/datadog/core/environment/yjit.rb +8 -2
- 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/helpers.rb +1 -1
- data/lib/datadog/core/metrics/logging.rb +8 -8
- data/lib/datadog/core/metrics/options.rb +5 -5
- 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 +82 -11
- data/lib/datadog/core/remote/client.rb +130 -107
- data/lib/datadog/core/remote/component.rb +97 -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 +19 -6
- 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 -106
- data/lib/datadog/core/remote/transport/negotiation.rb +9 -21
- data/lib/datadog/core/remote/worker.rb +40 -41
- data/lib/datadog/core/runtime/ext.rb +22 -22
- data/lib/datadog/core/runtime/metrics.rb +34 -14
- data/lib/datadog/core/semaphore.rb +32 -0
- 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 +143 -60
- data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
- data/lib/datadog/core/telemetry/emitter.rb +26 -14
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +67 -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 +274 -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 -9
- 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 +35 -17
- data/lib/datadog/core/telemetry/metric.rb +37 -15
- 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 +23 -11
- 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 -10
- 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 +181 -29
- 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 +3 -4
- data/lib/datadog/core/utils/spawn_monkey_patch.rb +68 -0
- data/lib/datadog/core/utils/time.rb +19 -33
- 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 +41 -15
- 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 +167 -0
- data/lib/datadog/data_streams/processor.rb +542 -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 +256 -18
- data/lib/datadog/di/component.rb +220 -0
- data/lib/datadog/di/configuration.rb +241 -2
- data/lib/datadog/di/context.rb +82 -0
- data/lib/datadog/di/contrib/active_record.rb +41 -0
- data/lib/datadog/di/contrib/railtie.rb +15 -0
- data/lib/datadog/di/contrib.rb +28 -0
- data/lib/datadog/di/el/compiler.rb +244 -0
- data/lib/datadog/di/el/evaluator.rb +239 -0
- data/lib/datadog/di/el/expression.rb +53 -0
- data/lib/datadog/di/el.rb +5 -0
- data/lib/datadog/di/error.rb +66 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +978 -0
- data/lib/datadog/di/logger.rb +30 -0
- data/lib/datadog/di/preload.rb +18 -0
- data/lib/datadog/di/probe.rb +188 -14
- data/lib/datadog/di/probe_builder.rb +132 -3
- 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 +352 -0
- data/lib/datadog/di/probe_notification_builder.rb +452 -0
- data/lib/datadog/di/probe_notifier_worker.rb +381 -0
- 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 +310 -0
- data/lib/datadog/di/serializer.rb +487 -96
- 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 +170 -0
- data/lib/datadog/di.rb +224 -2
- 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/agentless/configuration_source.rb +246 -0
- data/lib/datadog/open_feature/agentless/parser.rb +43 -0
- data/lib/datadog/open_feature/agentless/response.rb +19 -0
- data/lib/datadog/open_feature/agentless/transport.rb +103 -0
- data/lib/datadog/open_feature/component.rb +165 -0
- data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
- data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
- data/lib/datadog/open_feature/configuration/source.rb +43 -0
- data/lib/datadog/open_feature/configuration.rb +168 -0
- data/lib/datadog/open_feature/evaluation_engine.rb +96 -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 +67 -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 +40 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +442 -0
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +544 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +105 -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 +74 -0
- data/lib/datadog/open_feature/noop_evaluator.rb +31 -0
- data/lib/datadog/open_feature/provider.rb +235 -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 +126 -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 +21 -15
- 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 +57 -0
- data/lib/datadog/opentelemetry/trace.rb +8 -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 +60 -3
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -4
- data/lib/datadog/profiling/collectors/info.rb +62 -8
- data/lib/datadog/profiling/collectors/thread_context.rb +25 -18
- data/lib/datadog/profiling/component.rb +123 -104
- 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 +38 -18
- data/lib/datadog/profiling/tag_builder.rb +41 -41
- data/lib/datadog/profiling/tasks/exec.rb +11 -6
- data/lib/datadog/profiling/tasks/help.rb +1 -0
- data/lib/datadog/profiling/tasks/setup.rb +4 -2
- data/lib/datadog/profiling.rb +42 -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 +1092 -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 +63 -23
- data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
- data/lib/datadog/tracing/configuration/dynamic.rb +16 -16
- data/lib/datadog/tracing/configuration/ext.rb +62 -33
- data/lib/datadog/tracing/configuration/http.rb +2 -2
- data/lib/datadog/tracing/configuration/settings.rb +182 -34
- data/lib/datadog/tracing/context.rb +27 -5
- data/lib/datadog/tracing/context_provider.rb +12 -8
- 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 +5 -5
- 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 +3 -3
- 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 +38 -20
- 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 +3 -3
- 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 +3 -3
- 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 +9 -9
- 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 +17 -19
- data/lib/datadog/tracing/contrib/active_record/events.rb +3 -3
- 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 +29 -21
- 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 +16 -6
- 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 +27 -27
- 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 +6 -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 +63 -64
- data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +9 -9
- data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +7 -3
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +19 -17
- 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 +18 -17
- data/lib/datadog/tracing/contrib/excon/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/ext.rb +23 -20
- 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 +20 -17
- 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 +69 -36
- 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 +26 -24
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +14 -18
- 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 +13 -8
- 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 +14 -14
- data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +1 -7
- 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 -24
- 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 -27
- 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 +21 -29
- 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 +8 -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 +11 -6
- data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +11 -6
- data/lib/datadog/tracing/contrib/kafka/events.rb +10 -10
- 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 +10 -10
- data/lib/datadog/tracing/contrib/patchable.rb +1 -1
- data/lib/datadog/tracing/contrib/patcher.rb +15 -14
- 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 +3 -2
- 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 +4 -4
- 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 +14 -4
- 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 +6 -6
- 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 +131 -7
- 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 +5 -5
- 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 +41 -7
- data/lib/datadog/tracing/contrib/redis/tags.rb +7 -12
- data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +20 -12
- 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 +19 -15
- 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 +8 -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/jdbc_connection_string.rb +140 -0
- data/lib/datadog/tracing/contrib/sequel/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/sequel/utils.rb +103 -16
- 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 +14 -9
- 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 +8 -4
- 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 +5 -4
- 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} +17 -15
- 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 +21 -14
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +23 -15
- 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 +250 -0
- data/lib/datadog/tracing/distributed/datadog.rb +41 -38
- 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 +73 -14
- data/lib/datadog/tracing/distributed/propagation_policy.rb +47 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +31 -259
- data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
- data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
- data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
- data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
- 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 +89 -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 +9 -9
- data/lib/datadog/tracing/metadata.rb +5 -3
- data/lib/datadog/tracing/otel_thread_context.rb +99 -0
- 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 +228 -30
- 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 +91 -58
- 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 +29 -12
- 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 +120 -40
- data/lib/datadog/tracing/sync_writer.rb +22 -14
- data/lib/datadog/tracing/trace_digest.rb +58 -28
- data/lib/datadog/tracing/trace_operation.rb +263 -120
- data/lib/datadog/tracing/trace_segment.rb +18 -16
- data/lib/datadog/tracing/tracer.rb +208 -73
- 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 -73
- 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 +33 -39
- data/lib/datadog/tracing/transport/io.rb +3 -3
- data/lib/datadog/tracing/transport/native/response.rb +71 -0
- data/lib/datadog/tracing/transport/native.rb +379 -0
- data/lib/datadog/tracing/transport/serializable_trace.rb +47 -23
- data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
- data/lib/datadog/tracing/transport/statistics.rb +3 -3
- data/lib/datadog/tracing/transport/trace_formatter.rb +49 -10
- data/lib/datadog/tracing/transport/traces.rb +45 -99
- data/lib/datadog/tracing/utils.rb +3 -3
- data/lib/datadog/tracing/workers.rb +12 -10
- data/lib/datadog/tracing/writer.rb +54 -39
- data/lib/datadog/tracing.rb +19 -6
- data/lib/datadog/version.rb +4 -4
- data/lib/datadog.rb +21 -5
- metadata +372 -70
- 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 -207
- 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 -59
- data/lib/datadog/appsec/contrib/patcher.rb +0 -12
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +0 -81
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +0 -60
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +0 -66
- data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +0 -66
- data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +0 -61
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +0 -58
- data/lib/datadog/appsec/processor/actions.rb +0 -49
- data/lib/datadog/appsec/processor/rule_merger.rb +0 -170
- data/lib/datadog/appsec/processor.rb +0 -170
- 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 -61
- 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 -163
- data/lib/datadog/tracing/contrib/rails/utils.rb +0 -26
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -84
- 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,19 @@
|
|
|
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
|
+
|
|
78
|
+
// Used as markers in the code
|
|
79
|
+
#define OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED INT2FIX(1)
|
|
80
|
+
#define OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED INT2FIX(2)
|
|
81
|
+
|
|
77
82
|
#define TIME_BETWEEN_GC_EVENTS_NS MILLIS_AS_NS(10)
|
|
83
|
+
#define GVL_SUSPENDED ((uint64_t)1)
|
|
84
|
+
#define GVL_RUNNING ((uint64_t)0)
|
|
85
|
+
|
|
86
|
+
#define MAX(a, b) ((a) < (b) ? (b) : (a))
|
|
78
87
|
|
|
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
|
|
88
|
+
static ID dd_per_thread_context_id; // Hidden ivar (no @ prefix, inaccessible from Ruby)
|
|
83
89
|
|
|
84
90
|
static ID at_active_span_id; // id of :@active_span in Ruby
|
|
85
91
|
static ID at_active_trace_id; // id of :@active_trace in Ruby
|
|
@@ -101,60 +107,78 @@ static ID at_kind_id; // id of :@kind in Ruby
|
|
|
101
107
|
static ID at_name_id; // id of :@name in Ruby
|
|
102
108
|
static ID server_id; // id of :server in Ruby
|
|
103
109
|
static ID otel_context_storage_id; // id of :__opentelemetry_context_storage__ in Ruby
|
|
110
|
+
static ID otel_fiber_context_storage_id; // id of :@opentelemetry_context in Ruby
|
|
111
|
+
|
|
112
|
+
// This is mutable and gets set last-writer-wins style by
|
|
113
|
+
// `thread_context_collector_reset_all_per_thread_contexts`, which is called whenever
|
|
114
|
+
// profiling is starting or restarting.
|
|
115
|
+
//
|
|
116
|
+
// Note: We must be careful to not change this value while the profiler is still running,
|
|
117
|
+
// otherwise new threads can get the new value and cause profiling to stop with an
|
|
118
|
+
// exception due to the mismatched size.
|
|
119
|
+
//
|
|
120
|
+
// The initial value should be kept in sync with the default for DD_PROFILING_MAX_FRAMES
|
|
121
|
+
// in settings.rb. See `initialize_context` for details on why this is needed/used.
|
|
122
|
+
static uint16_t latest_max_frames = 400;
|
|
104
123
|
|
|
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);
|
|
124
|
+
// Global tracepoint for RUBY_EVENT_THREAD_BEGIN. Created and enabled once when the first ThreadContext collector is initialized.
|
|
125
|
+
static VALUE thread_begin_tracepoint = Qnil;
|
|
111
126
|
|
|
112
|
-
enum
|
|
127
|
+
typedef enum { OTEL_CONTEXT_ENABLED_FALSE, OTEL_CONTEXT_ENABLED_ONLY, OTEL_CONTEXT_ENABLED_BOTH } otel_context_enabled;
|
|
128
|
+
typedef enum { OTEL_CONTEXT_SOURCE_UNKNOWN, OTEL_CONTEXT_SOURCE_FIBER_IVAR, OTEL_CONTEXT_SOURCE_FIBER_LOCAL } otel_context_source;
|
|
113
129
|
|
|
114
130
|
// Contains state for a single ThreadContext instance
|
|
115
|
-
struct
|
|
131
|
+
typedef struct {
|
|
116
132
|
// Note: Places in this file that usually need to be changed when this struct is changed are tagged with
|
|
117
133
|
// "Update this when modifying state struct"
|
|
118
134
|
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
uint16_t max_frames;
|
|
122
|
-
// Hashmap <Thread Object, struct per_thread_context>
|
|
123
|
-
st_table *hash_map_per_thread_context;
|
|
135
|
+
// Output buffer for stack traces, passed to sample_thread()
|
|
136
|
+
sample_locations locations;
|
|
124
137
|
// Datadog::Profiling::StackRecorder instance
|
|
125
138
|
VALUE recorder_instance;
|
|
126
139
|
// If the tracer is available and enabled, this will be the fiber-local symbol for accessing its running context,
|
|
127
140
|
// to enable code hotspots and endpoint aggregation.
|
|
128
141
|
// When not available, this is set to MISSING_TRACER_CONTEXT_KEY.
|
|
129
142
|
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
143
|
// Reusable array to get list of threads
|
|
135
144
|
VALUE thread_list_buffer;
|
|
136
145
|
// Used to omit endpoint names (retrieved from tracer) from collected data
|
|
137
146
|
bool endpoint_collection_enabled;
|
|
138
|
-
// Used to omit timestamps / timeline events from collected data
|
|
139
|
-
bool timeline_enabled;
|
|
140
147
|
// Used to control context collection
|
|
141
|
-
|
|
142
|
-
// Used to
|
|
143
|
-
|
|
148
|
+
otel_context_enabled otel_context_enabled;
|
|
149
|
+
// Used to remember where otel context is being stored after we observe it the first time
|
|
150
|
+
otel_context_source otel_context_source;
|
|
144
151
|
// Used when calling monotonic_to_system_epoch_ns
|
|
145
152
|
monotonic_to_system_epoch_state time_converter_state;
|
|
146
153
|
// Used to identify the main thread, to give it a fallback name
|
|
147
154
|
VALUE main_thread;
|
|
148
155
|
// Used when extracting trace identifiers from otel spans. Lazily initialized.
|
|
149
|
-
//
|
|
150
|
-
//
|
|
156
|
+
// * `OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED`: we've not needed it yet
|
|
157
|
+
// * `OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED`: a sample needed it, and it's waiting to be extracted
|
|
158
|
+
// * anything else: the extracted value, or Qnil if we tried to extract it and failed
|
|
151
159
|
VALUE otel_current_span_key;
|
|
160
|
+
// Used to enable native filenames in stack traces
|
|
161
|
+
bool native_filenames_enabled;
|
|
162
|
+
// Used to include class/module names in method names (e.g. "Foo#bar" instead of "bar")
|
|
163
|
+
bool show_classes;
|
|
164
|
+
// Used to cache native filename lookup results (Map[void *function_pointer, char *filename])
|
|
165
|
+
st_table *native_filenames_cache;
|
|
166
|
+
// Used to attribute overhead during sampling to this component
|
|
167
|
+
VALUE overhead_filename;
|
|
152
168
|
|
|
153
169
|
struct stats {
|
|
170
|
+
// Track how many regular samples we've taken. Does not include garbage collection samples.
|
|
171
|
+
unsigned int sample_count;
|
|
154
172
|
// Track how many garbage collection samples we've taken.
|
|
155
173
|
unsigned int gc_samples;
|
|
156
174
|
// See thread_context_collector_on_gc_start for details
|
|
157
175
|
unsigned int gc_samples_missed_due_to_missing_context;
|
|
176
|
+
// See thread_context_collector_sample_after_gc for details
|
|
177
|
+
unsigned int gc_samples_skipped_nothing_to_flush;
|
|
178
|
+
// How many per-thread samples were skipped because the thread has been continuously suspended
|
|
179
|
+
// (no GVL) since its previous sample, so its Ruby stack cannot have changed.
|
|
180
|
+
unsigned int inactive_thread_samples_skipped;
|
|
181
|
+
unsigned int profiler_thread_samples_skipped;
|
|
158
182
|
} stats;
|
|
159
183
|
|
|
160
184
|
struct {
|
|
@@ -164,18 +188,93 @@ struct thread_context_collector_state {
|
|
|
164
188
|
long wall_time_at_previous_gc_ns; // Will be INVALID_TIME unless there's accumulated time above
|
|
165
189
|
long wall_time_at_last_flushed_gc_event_ns; // Starts at 0 and then will always be valid
|
|
166
190
|
} gc_tracking;
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
// Tracks per-thread state
|
|
191
|
+
} thread_context_collector_state;
|
|
192
|
+
|
|
193
|
+
// Tracks per-thread state.
|
|
194
|
+
// This state is global and lives forever on the Ruby Thread (until the Thread is GC'd).
|
|
195
|
+
// The state is created early on for all threads on the main Ractor
|
|
196
|
+
// (enabling a TracePoint only enables it for the current Ractor).
|
|
197
|
+
// The state is either created when the Thread starts running (via the RUBY_EVENT_THREAD_BEGIN TracePoint),
|
|
198
|
+
// or via `thread_context_collector_reset_all_per_thread_contexts` when the profiler starts.
|
|
199
|
+
//
|
|
200
|
+
// Unfortunately that RUBY_EVENT_THREAD_BEGIN TracePoint still fires after some other events:
|
|
201
|
+
// * RUBY_INTERNAL_THREAD_EVENT_RESUMED for the Thread acquiring the GVL for the first time
|
|
202
|
+
// * an early SIGPROF calling handle_sampling_signal()
|
|
203
|
+
// * Ruby might check for interrupts and run postponed jobs (e.g. thread_context_collector_sample)
|
|
204
|
+
// * another RUBY_EVENT_THREAD_BEGIN TracePoint which might run before ours
|
|
205
|
+
// * etc
|
|
206
|
+
// For those cases we have to ignore those events and we cannot assume the state is always set,
|
|
207
|
+
// however this only matters for a very short period when a thread starts.
|
|
170
208
|
struct per_thread_context {
|
|
171
|
-
sampling_buffer
|
|
209
|
+
sampling_buffer sampling_buffer;
|
|
210
|
+
bool prepare_sample_on_gc_finish;
|
|
172
211
|
char thread_id[THREAD_ID_LIMIT_CHARS];
|
|
173
212
|
ddog_CharSlice thread_id_char_slice;
|
|
174
213
|
char thread_invoke_location[THREAD_INVOKE_LOCATION_LIMIT_CHARS];
|
|
175
214
|
ddog_CharSlice thread_invoke_location_char_slice;
|
|
176
215
|
thread_cpu_time_id thread_cpu_time_id;
|
|
177
|
-
long cpu_time_at_previous_sample_ns;
|
|
178
|
-
long wall_time_at_previous_sample_ns;
|
|
216
|
+
long cpu_time_at_previous_sample_ns;
|
|
217
|
+
long wall_time_at_previous_sample_ns;
|
|
218
|
+
|
|
219
|
+
// There are 3 possible states for the GVL (per thread), and 3 transitions for which we receive GVL events:
|
|
220
|
+
// Thread holds the GVL
|
|
221
|
+
// on_gvl_released() the thread releases the GVL (RUBY_INTERNAL_THREAD_EVENT_SUSPENDED)
|
|
222
|
+
// Thread runs without the GVL
|
|
223
|
+
// on_gvl_waiting() the thread wants the GVL (RUBY_INTERNAL_THREAD_EVENT_READY)
|
|
224
|
+
// Thread is "Waiting for GVL"
|
|
225
|
+
// on_gvl_running() the thread now got the GVL (RUBY_INTERNAL_THREAD_EVENT_RESUMED)
|
|
226
|
+
// ... and the cycle restarts
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
// --- GVL waiting tracking state machine ---
|
|
230
|
+
//
|
|
231
|
+
// gvl_waiting_at tracks the GVL wait state for each profiled thread:
|
|
232
|
+
//
|
|
233
|
+
// ┌───────────────────────────────────┐
|
|
234
|
+
// │ on_gvl_waiting │
|
|
235
|
+
// │ ▼
|
|
236
|
+
// Not Waiting (0) ◀────────────────── Waiting (> 0)
|
|
237
|
+
// ▲ on_gvl_running │
|
|
238
|
+
// │ (below threshold) │ on_gvl_running (above threshold)
|
|
239
|
+
// │ ▼
|
|
240
|
+
// └─────────────────────────── Sample Pending (< 0)
|
|
241
|
+
// sample / sample_after_gvl_running
|
|
242
|
+
//
|
|
243
|
+
// Not Waiting (0): thread is running or not waiting for the GVL
|
|
244
|
+
// Waiting (> 0): monotonic wall time (ns) when the thread started waiting
|
|
245
|
+
// Sample Pending (< 0): negated timestamp; the wait ended and a sample is pending
|
|
246
|
+
//
|
|
247
|
+
// The field is accessed under the GVL for most functions EXCEPT on_gvl_waiting() which writes to it without the GVL.
|
|
248
|
+
// So we need to pack the above state in a single long to ensure atomicity.
|
|
249
|
+
long gvl_waiting_at;
|
|
250
|
+
|
|
251
|
+
// Per-thread "state + version" word, updated on every GVL state transition. The encoding is:
|
|
252
|
+
// - low bit: current state (1 = currently suspended, 0 = currently running)
|
|
253
|
+
// - bits 1+: monotonic event counter (incremented on every RESUMED)
|
|
254
|
+
// The hooks set the state bit explicitly rather than relying on parity, so the encoding stays
|
|
255
|
+
// correct even when events are not paired properly (as in tests).
|
|
256
|
+
//
|
|
257
|
+
// Note that SUSPENDED can happen multiple times in a row on Ruby 3.2,
|
|
258
|
+
// see https://github.com/DataDog/dd-trace-rb/pull/5777#discussion_r3388560254,
|
|
259
|
+
// the encoding is designed to naturally not change the field in such a case.
|
|
260
|
+
uint64_t gvl_state_change_count;
|
|
261
|
+
// Snapshot of the thread's gvl_state_change_count at the moment we last sampled it.
|
|
262
|
+
// Equality with this snapshot means no GVL transition since the last sample.
|
|
263
|
+
uint64_t gvl_state_change_count_at_previous_sample;
|
|
264
|
+
// True when the previous per-tick sample was skipped by the SUSPENDED-skip optimization, so the
|
|
265
|
+
// flush-before-serialize pass knows it needs to report this thread.
|
|
266
|
+
// As a result, we will accumulate all wall & CPU time as a single batch per reporting period,
|
|
267
|
+
// but this is deemed worth it for this optimization. In any case we don't know exactly
|
|
268
|
+
// at what time a thread was doing CPU work (unless it's on CPU 100% of the time).
|
|
269
|
+
bool was_skipped_at_last_sample;
|
|
270
|
+
// Set as true for CpuAndWallTimeWorker and IdleSamplingHelper threads.
|
|
271
|
+
// When true, per-tick samples are skipped entirely; the thread is sampled only once per
|
|
272
|
+
// reporting period during the on_serialize flush.
|
|
273
|
+
//
|
|
274
|
+
// These threads are always in native code so their stacks aren't interesting;
|
|
275
|
+
// the Profiling::Scheduler thread on the other hand does a lot of different
|
|
276
|
+
// things using a mix of Ruby and native code, so that one isn't considered internal.
|
|
277
|
+
bool is_profiler_internal_thread;
|
|
179
278
|
|
|
180
279
|
struct {
|
|
181
280
|
// Both of these fields are set by on_gc_start and kept until on_gc_finish is called.
|
|
@@ -186,55 +285,40 @@ struct per_thread_context {
|
|
|
186
285
|
};
|
|
187
286
|
|
|
188
287
|
// Used to correlate profiles with traces
|
|
189
|
-
struct
|
|
288
|
+
typedef struct {
|
|
190
289
|
bool valid;
|
|
191
290
|
uint64_t local_root_span_id;
|
|
192
291
|
uint64_t span_id;
|
|
193
292
|
VALUE trace_endpoint;
|
|
194
|
-
};
|
|
293
|
+
} trace_identifiers;
|
|
195
294
|
|
|
196
|
-
struct
|
|
295
|
+
typedef struct {
|
|
197
296
|
VALUE span;
|
|
198
297
|
VALUE span_id;
|
|
199
298
|
VALUE trace_id;
|
|
200
|
-
};
|
|
299
|
+
} otel_span;
|
|
201
300
|
|
|
202
301
|
static void thread_context_collector_typed_data_mark(void *state_ptr);
|
|
203
302
|
static void thread_context_collector_typed_data_free(void *state_ptr);
|
|
204
|
-
static
|
|
205
|
-
static
|
|
303
|
+
static void per_thread_context_typed_data_mark(void *ctx_ptr);
|
|
304
|
+
static void per_thread_context_typed_data_free(void *ctx_ptr);
|
|
206
305
|
static VALUE _native_new(VALUE klass);
|
|
207
|
-
static VALUE _native_initialize(
|
|
208
|
-
|
|
209
|
-
VALUE collector_instance,
|
|
210
|
-
VALUE recorder_instance,
|
|
211
|
-
VALUE max_frames,
|
|
212
|
-
VALUE tracer_context_key,
|
|
213
|
-
VALUE endpoint_collection_enabled,
|
|
214
|
-
VALUE timeline_enabled,
|
|
215
|
-
VALUE waiting_for_gvl_threshold_ns,
|
|
216
|
-
VALUE otel_context_enabled,
|
|
217
|
-
VALUE allocation_type_enabled
|
|
218
|
-
);
|
|
219
|
-
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE profiler_overhead_stack_thread);
|
|
306
|
+
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
|
307
|
+
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE allow_exception);
|
|
220
308
|
static VALUE _native_on_gc_start(VALUE self, VALUE collector_instance);
|
|
221
309
|
static VALUE _native_on_gc_finish(VALUE self, VALUE collector_instance);
|
|
222
|
-
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance
|
|
310
|
+
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
223
311
|
static void update_metrics_and_sample(
|
|
224
|
-
|
|
312
|
+
thread_context_collector_state *state,
|
|
225
313
|
VALUE thread_being_sampled,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
long current_cpu_time_ns,
|
|
230
|
-
long current_monotonic_wall_time_ns
|
|
314
|
+
per_thread_context *thread_context,
|
|
315
|
+
long current_monotonic_wall_time_ns,
|
|
316
|
+
bool force_sample
|
|
231
317
|
);
|
|
232
318
|
static void trigger_sample_for_thread(
|
|
233
|
-
|
|
234
|
-
VALUE
|
|
235
|
-
|
|
236
|
-
struct per_thread_context *thread_context,
|
|
237
|
-
sampling_buffer* sampling_buffer,
|
|
319
|
+
thread_context_collector_state *state,
|
|
320
|
+
VALUE thread_being_sampled,
|
|
321
|
+
per_thread_context *thread_context,
|
|
238
322
|
sample_values values,
|
|
239
323
|
long current_monotonic_wall_time_ns,
|
|
240
324
|
ddog_CharSlice *ruby_vm_type,
|
|
@@ -242,32 +326,33 @@ static void trigger_sample_for_thread(
|
|
|
242
326
|
bool is_gvl_waiting_state
|
|
243
327
|
);
|
|
244
328
|
static VALUE _native_thread_list(VALUE self);
|
|
245
|
-
static
|
|
246
|
-
static
|
|
247
|
-
static void initialize_context(VALUE thread,
|
|
248
|
-
static void free_context(struct per_thread_context* thread_context);
|
|
329
|
+
static void check_frozen_thread(VALUE thread);
|
|
330
|
+
static per_thread_context *get_or_create_context_for(VALUE thread);
|
|
331
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context);
|
|
249
332
|
static VALUE _native_inspect(VALUE self, VALUE collector_instance);
|
|
250
|
-
static VALUE
|
|
251
|
-
static
|
|
252
|
-
static VALUE
|
|
253
|
-
static VALUE gc_tracking_as_ruby_hash(struct thread_context_collector_state *state);
|
|
254
|
-
static void remove_context_for_dead_threads(struct thread_context_collector_state *state);
|
|
255
|
-
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, st_data_t _argument);
|
|
333
|
+
static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context);
|
|
334
|
+
static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash);
|
|
335
|
+
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state);
|
|
256
336
|
static VALUE _native_per_thread_context(VALUE self, VALUE collector_instance);
|
|
257
|
-
static long
|
|
258
|
-
static long
|
|
337
|
+
static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns);
|
|
338
|
+
static long update_wall_time_since_previous_sample(per_thread_context *thread_context, long current_wall_time_ns);
|
|
339
|
+
static long cpu_time_now_ns(per_thread_context *thread_context);
|
|
259
340
|
static long thread_id_for(VALUE thread);
|
|
260
341
|
static VALUE _native_stats(VALUE self, VALUE collector_instance);
|
|
261
342
|
static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
|
|
262
|
-
static void trace_identifiers_for(
|
|
343
|
+
static void trace_identifiers_for(
|
|
344
|
+
thread_context_collector_state *state,
|
|
345
|
+
VALUE thread,
|
|
346
|
+
trace_identifiers *trace_identifiers_result
|
|
347
|
+
);
|
|
263
348
|
static bool should_collect_resource(VALUE root_span);
|
|
264
349
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
265
|
-
static VALUE thread_list(
|
|
350
|
+
static VALUE thread_list(thread_context_collector_state *state);
|
|
266
351
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object);
|
|
267
352
|
static VALUE _native_new_empty_thread(VALUE self);
|
|
268
353
|
static ddog_CharSlice ruby_value_type_to_class_name(enum ruby_value_type type);
|
|
269
354
|
static void ddtrace_otel_trace_identifiers_for(
|
|
270
|
-
|
|
355
|
+
thread_context_collector_state *state,
|
|
271
356
|
VALUE *active_trace,
|
|
272
357
|
VALUE *root_span,
|
|
273
358
|
VALUE *numeric_span_id,
|
|
@@ -276,29 +361,41 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
276
361
|
);
|
|
277
362
|
static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples);
|
|
278
363
|
static bool handle_gvl_waiting(
|
|
279
|
-
|
|
364
|
+
thread_context_collector_state *state,
|
|
280
365
|
VALUE thread_being_sampled,
|
|
281
|
-
|
|
282
|
-
struct per_thread_context *thread_context,
|
|
283
|
-
sampling_buffer* sampling_buffer,
|
|
366
|
+
per_thread_context *thread_context,
|
|
284
367
|
long current_cpu_time_ns
|
|
285
368
|
);
|
|
286
369
|
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
287
|
-
static VALUE
|
|
288
|
-
|
|
289
|
-
static VALUE
|
|
290
|
-
static VALUE
|
|
370
|
+
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
371
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
|
372
|
+
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
373
|
+
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE waiting_for_gvl_threshold_ns);
|
|
374
|
+
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
|
|
375
|
+
#endif
|
|
376
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns);
|
|
291
377
|
static void otel_without_ddtrace_trace_identifiers_for(
|
|
292
|
-
|
|
378
|
+
thread_context_collector_state *state,
|
|
293
379
|
VALUE thread,
|
|
294
|
-
|
|
380
|
+
trace_identifiers *trace_identifiers_result
|
|
295
381
|
);
|
|
296
|
-
static
|
|
382
|
+
static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
|
|
383
|
+
static bool is_otel_current_span_key_needed(thread_context_collector_state *state);
|
|
297
384
|
static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
|
|
385
|
+
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
|
|
386
|
+
static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
387
|
+
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
388
|
+
static VALUE _native_request_prepare_on_gc_finish(DDTRACE_UNUSED VALUE self);
|
|
389
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
390
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
391
|
+
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
392
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample);
|
|
393
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused);
|
|
298
394
|
|
|
299
395
|
void collectors_thread_context_init(VALUE profiling_module) {
|
|
300
396
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
|
301
397
|
VALUE collectors_thread_context_class = rb_define_class_under(collectors_module, "ThreadContext", rb_cObject);
|
|
398
|
+
|
|
302
399
|
// Hosts methods used for testing the native code using RSpec
|
|
303
400
|
VALUE testing_module = rb_define_module_under(collectors_thread_context_class, "Testing");
|
|
304
401
|
|
|
@@ -312,27 +409,34 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
312
409
|
// https://bugs.ruby-lang.org/issues/18007 for a discussion around this.
|
|
313
410
|
rb_define_alloc_func(collectors_thread_context_class, _native_new);
|
|
314
411
|
|
|
315
|
-
rb_define_singleton_method(collectors_thread_context_class, "_native_initialize", _native_initialize,
|
|
412
|
+
rb_define_singleton_method(collectors_thread_context_class, "_native_initialize", _native_initialize, -1);
|
|
316
413
|
rb_define_singleton_method(collectors_thread_context_class, "_native_inspect", _native_inspect, 1);
|
|
317
414
|
rb_define_singleton_method(collectors_thread_context_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
|
318
415
|
rb_define_singleton_method(testing_module, "_native_sample", _native_sample, 2);
|
|
319
416
|
rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
|
|
320
417
|
rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
|
|
321
418
|
rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
|
|
322
|
-
rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc,
|
|
419
|
+
rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 1);
|
|
323
420
|
rb_define_singleton_method(testing_module, "_native_thread_list", _native_thread_list, 0);
|
|
324
421
|
rb_define_singleton_method(testing_module, "_native_per_thread_context", _native_per_thread_context, 1);
|
|
325
422
|
rb_define_singleton_method(testing_module, "_native_stats", _native_stats, 1);
|
|
326
423
|
rb_define_singleton_method(testing_module, "_native_gc_tracking", _native_gc_tracking, 1);
|
|
327
424
|
rb_define_singleton_method(testing_module, "_native_new_empty_thread", _native_new_empty_thread, 0);
|
|
328
425
|
rb_define_singleton_method(testing_module, "_native_sample_skipped_allocation_samples", _native_sample_skipped_allocation_samples, 2);
|
|
426
|
+
rb_define_singleton_method(testing_module, "_native_system_epoch_time_now_ns", _native_system_epoch_time_now_ns, 1);
|
|
427
|
+
rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler, 0);
|
|
428
|
+
rb_define_singleton_method(testing_module, "_native_request_prepare_on_gc_finish", _native_request_prepare_on_gc_finish, 0);
|
|
429
|
+
rb_define_singleton_method(testing_module, "_native_remove_per_thread_context_for", _native_remove_per_thread_context_for, 1);
|
|
430
|
+
rb_define_singleton_method(testing_module, "_native_global_reset_per_thread_context", _native_global_reset_per_thread_context, 1);
|
|
431
|
+
rb_define_singleton_method(testing_module, "_native_mark_thread_as_profiler_internal", _native_mark_thread_as_profiler_internal, 1);
|
|
432
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
433
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_released", _native_on_gvl_released, 1);
|
|
329
434
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
330
|
-
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
331
435
|
rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
|
|
332
|
-
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running,
|
|
436
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 2);
|
|
333
437
|
rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 2);
|
|
334
|
-
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);
|
|
335
438
|
#endif
|
|
439
|
+
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);
|
|
336
440
|
|
|
337
441
|
at_active_span_id = rb_intern_const("@active_span");
|
|
338
442
|
at_active_trace_id = rb_intern_const("@active_trace");
|
|
@@ -351,16 +455,19 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
351
455
|
at_name_id = rb_intern_const("@name");
|
|
352
456
|
server_id = rb_intern_const("server");
|
|
353
457
|
otel_context_storage_id = rb_intern_const("__opentelemetry_context_storage__");
|
|
458
|
+
otel_fiber_context_storage_id = rb_intern_const("@opentelemetry_context");
|
|
354
459
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
460
|
+
dd_per_thread_context_id = rb_intern_const("dd_per_thread_context");
|
|
461
|
+
|
|
462
|
+
// This will raise if Ruby already ran out of thread-local keys
|
|
463
|
+
per_thread_context_tls_init();
|
|
464
|
+
|
|
465
|
+
rb_global_variable(&thread_begin_tracepoint);
|
|
359
466
|
|
|
360
467
|
gc_profiling_init();
|
|
361
468
|
}
|
|
362
469
|
|
|
363
|
-
// This structure is used to define a Ruby object that stores a pointer to a
|
|
470
|
+
// This structure is used to define a Ruby object that stores a pointer to a thread_context_collector_state
|
|
364
471
|
// See also https://github.com/ruby/ruby/blob/master/doc/extension.rdoc for how this works
|
|
365
472
|
static const rb_data_type_t thread_context_collector_typed_data = {
|
|
366
473
|
.wrap_struct_name = "Datadog::Profiling::Collectors::ThreadContext",
|
|
@@ -376,71 +483,94 @@ static const rb_data_type_t thread_context_collector_typed_data = {
|
|
|
376
483
|
// This function is called by the Ruby GC to give us a chance to mark any Ruby objects that we're holding on to,
|
|
377
484
|
// so that they don't get garbage collected
|
|
378
485
|
static void thread_context_collector_typed_data_mark(void *state_ptr) {
|
|
379
|
-
|
|
486
|
+
thread_context_collector_state *state = (thread_context_collector_state *) state_ptr;
|
|
380
487
|
|
|
381
488
|
// Update this when modifying state struct
|
|
382
489
|
rb_gc_mark(state->recorder_instance);
|
|
383
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_mark, 0 /* unused */);
|
|
384
490
|
rb_gc_mark(state->thread_list_buffer);
|
|
385
491
|
rb_gc_mark(state->main_thread);
|
|
386
492
|
rb_gc_mark(state->otel_current_span_key);
|
|
493
|
+
rb_gc_mark(state->overhead_filename);
|
|
387
494
|
}
|
|
388
495
|
|
|
389
496
|
static void thread_context_collector_typed_data_free(void *state_ptr) {
|
|
390
|
-
|
|
497
|
+
thread_context_collector_state *state = (thread_context_collector_state *) state_ptr;
|
|
391
498
|
|
|
392
499
|
// Update this when modifying state struct
|
|
393
500
|
|
|
394
501
|
// Important: Remember that we're only guaranteed to see here what's been set in _native_new, aka
|
|
395
502
|
// pointers that have been set NULL there may still be NULL here.
|
|
396
|
-
|
|
503
|
+
sample_locations_free(&state->locations);
|
|
397
504
|
|
|
398
|
-
|
|
399
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
|
400
|
-
// ...and then the map
|
|
401
|
-
st_free_table(state->hash_map_per_thread_context);
|
|
505
|
+
st_free_table(state->native_filenames_cache);
|
|
402
506
|
|
|
403
507
|
ruby_xfree(state);
|
|
404
508
|
}
|
|
405
509
|
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
510
|
+
// per_thread_context is wrapped in a TypedData Ruby object stored as an ivar on each Ruby Thread.
|
|
511
|
+
// This gives us automatic GC marking (for sampling_buffer iseq VALUEs) and lifecycle management.
|
|
512
|
+
static const rb_data_type_t per_thread_context_typed_data = {
|
|
513
|
+
.wrap_struct_name = "Datadog::Profiling::PerThreadContext",
|
|
514
|
+
.function = {
|
|
515
|
+
.dmark = per_thread_context_typed_data_mark,
|
|
516
|
+
.dfree = per_thread_context_typed_data_free,
|
|
517
|
+
.dsize = NULL,
|
|
518
|
+
},
|
|
519
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
static void per_thread_context_typed_data_mark(void *ctx_ptr) {
|
|
523
|
+
per_thread_context *ctx = (per_thread_context *) ctx_ptr;
|
|
524
|
+
if (sampling_buffer_needs_marking(&ctx->sampling_buffer)) {
|
|
525
|
+
sampling_buffer_mark(&ctx->sampling_buffer);
|
|
526
|
+
}
|
|
411
527
|
}
|
|
412
528
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
529
|
+
static void per_thread_context_typed_data_free(void *ctx_ptr) {
|
|
530
|
+
per_thread_context *ctx = (per_thread_context *) ctx_ptr;
|
|
531
|
+
sampling_buffer_free(&ctx->sampling_buffer);
|
|
532
|
+
free(ctx);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Only for testing: removes the per-thread context without recreating it, so the thread has no context.
|
|
536
|
+
// This simulates situations where we observe a thread running before our RUBY_EVENT_THREAD_BEGIN tracepoint fires,
|
|
537
|
+
// so that we can test our `get_per_thread_context(...) => null` code paths.
|
|
538
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
539
|
+
check_frozen_thread(thread);
|
|
540
|
+
per_thread_context *ctx = get_per_thread_context(thread);
|
|
541
|
+
if (ctx != NULL) {
|
|
542
|
+
set_per_thread_context(thread, NULL);
|
|
543
|
+
rb_ivar_set(thread, dd_per_thread_context_id, Qnil);
|
|
544
|
+
}
|
|
545
|
+
return Qnil;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
549
|
+
thread_context_collector_reset_all_per_thread_contexts(collector_instance);
|
|
550
|
+
return Qnil;
|
|
418
551
|
}
|
|
419
552
|
|
|
420
553
|
static VALUE _native_new(VALUE klass) {
|
|
421
|
-
|
|
554
|
+
thread_context_collector_state *state = ruby_xcalloc(1, sizeof(thread_context_collector_state));
|
|
422
555
|
|
|
423
556
|
// Note: Any exceptions raised from this note until the TypedData_Wrap_Struct call will lead to the state memory
|
|
424
557
|
// being leaked.
|
|
425
558
|
|
|
426
559
|
// Update this when modifying state struct
|
|
427
|
-
state->locations = NULL;
|
|
428
|
-
state->max_frames = 0;
|
|
429
|
-
state->hash_map_per_thread_context =
|
|
430
|
-
// "numtable" is an awful name, but TL;DR it's what should be used when keys are `VALUE`s.
|
|
431
|
-
st_init_numtable();
|
|
432
560
|
state->recorder_instance = Qnil;
|
|
433
561
|
state->tracer_context_key = MISSING_TRACER_CONTEXT_KEY;
|
|
434
562
|
VALUE thread_list_buffer = rb_ary_new();
|
|
435
563
|
state->thread_list_buffer = thread_list_buffer;
|
|
436
564
|
state->endpoint_collection_enabled = true;
|
|
437
|
-
state->
|
|
438
|
-
state->
|
|
439
|
-
state->
|
|
565
|
+
state->native_filenames_enabled = false;
|
|
566
|
+
state->show_classes = false;
|
|
567
|
+
state->native_filenames_cache = st_init_numtable();
|
|
568
|
+
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
|
|
569
|
+
state->otel_context_source = OTEL_CONTEXT_SOURCE_UNKNOWN;
|
|
440
570
|
state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
|
|
441
571
|
VALUE main_thread = rb_thread_main();
|
|
442
572
|
state->main_thread = main_thread;
|
|
443
|
-
state->otel_current_span_key =
|
|
573
|
+
state->otel_current_span_key = OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED;
|
|
444
574
|
state->gc_tracking.wall_time_at_previous_gc_ns = INVALID_TIME;
|
|
445
575
|
state->gc_tracking.wall_time_at_last_flushed_gc_event_ns = 0;
|
|
446
576
|
|
|
@@ -456,46 +586,49 @@ static VALUE _native_new(VALUE klass) {
|
|
|
456
586
|
return instance;
|
|
457
587
|
}
|
|
458
588
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
VALUE
|
|
465
|
-
VALUE
|
|
466
|
-
VALUE
|
|
467
|
-
VALUE
|
|
468
|
-
VALUE
|
|
469
|
-
VALUE otel_context_enabled,
|
|
470
|
-
VALUE
|
|
471
|
-
)
|
|
589
|
+
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
|
590
|
+
VALUE options;
|
|
591
|
+
rb_scan_args(argc, argv, "0:", &options);
|
|
592
|
+
if (options == Qnil) options = rb_hash_new();
|
|
593
|
+
|
|
594
|
+
VALUE self_instance = rb_hash_fetch(options, ID2SYM(rb_intern("self_instance")));
|
|
595
|
+
VALUE recorder_instance = rb_hash_fetch(options, ID2SYM(rb_intern("recorder")));
|
|
596
|
+
VALUE max_frames = rb_hash_fetch(options, ID2SYM(rb_intern("max_frames")));
|
|
597
|
+
VALUE tracer_context_key = rb_hash_fetch(options, ID2SYM(rb_intern("tracer_context_key")));
|
|
598
|
+
VALUE endpoint_collection_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("endpoint_collection_enabled")));
|
|
599
|
+
VALUE otel_context_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("otel_context_enabled")));
|
|
600
|
+
VALUE native_filenames_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("native_filenames_enabled")));
|
|
601
|
+
VALUE show_classes = rb_hash_fetch(options, ID2SYM(rb_intern("show_classes")));
|
|
602
|
+
VALUE overhead_filename = rb_hash_fetch(options, ID2SYM(rb_intern("overhead_filename")));
|
|
603
|
+
|
|
604
|
+
ENFORCE_TYPE(max_frames, T_FIXNUM);
|
|
472
605
|
ENFORCE_BOOLEAN(endpoint_collection_enabled);
|
|
473
|
-
ENFORCE_BOOLEAN(
|
|
474
|
-
|
|
475
|
-
|
|
606
|
+
ENFORCE_BOOLEAN(native_filenames_enabled);
|
|
607
|
+
ENFORCE_BOOLEAN(show_classes);
|
|
608
|
+
ENFORCE_TYPE(overhead_filename, T_STRING);
|
|
476
609
|
|
|
477
|
-
|
|
478
|
-
|
|
610
|
+
uint16_t max_frame_int = sampling_buffer_check_max_frames(NUM2INT(max_frames));
|
|
611
|
+
|
|
612
|
+
thread_context_collector_state *state;
|
|
613
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
479
614
|
|
|
480
615
|
// Update this when modifying state struct
|
|
481
|
-
state->
|
|
482
|
-
state->locations = ruby_xcalloc(state->max_frames, sizeof(ddog_prof_Location));
|
|
483
|
-
// hash_map_per_thread_context is already initialized, nothing to do here
|
|
616
|
+
sample_locations_initialize(&state->locations, max_frame_int, show_classes == Qtrue);
|
|
484
617
|
state->recorder_instance = enforce_recorder_instance(recorder_instance);
|
|
618
|
+
recorder_install_on_serialize(recorder_instance, self_instance);
|
|
485
619
|
state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
|
|
486
|
-
state->
|
|
620
|
+
state->native_filenames_enabled = (native_filenames_enabled == Qtrue);
|
|
621
|
+
state->show_classes = (show_classes == Qtrue);
|
|
622
|
+
state->overhead_filename = overhead_filename;
|
|
487
623
|
if (otel_context_enabled == Qfalse || otel_context_enabled == Qnil) {
|
|
488
|
-
state->otel_context_enabled =
|
|
624
|
+
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
|
|
489
625
|
} else if (otel_context_enabled == ID2SYM(rb_intern("only"))) {
|
|
490
|
-
state->otel_context_enabled =
|
|
626
|
+
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_ONLY;
|
|
491
627
|
} else if (otel_context_enabled == ID2SYM(rb_intern("both"))) {
|
|
492
|
-
state->otel_context_enabled =
|
|
628
|
+
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_BOTH;
|
|
493
629
|
} else {
|
|
494
|
-
|
|
630
|
+
raise_error(rb_eArgError, "Unexpected value for otel_context_enabled: %+" PRIsVALUE, otel_context_enabled);
|
|
495
631
|
}
|
|
496
|
-
state->allocation_type_enabled = (allocation_type_enabled == Qtrue);
|
|
497
|
-
|
|
498
|
-
global_waiting_for_gvl_threshold_ns = NUM2UINT(waiting_for_gvl_threshold_ns);
|
|
499
632
|
|
|
500
633
|
if (RTEST(tracer_context_key)) {
|
|
501
634
|
ENFORCE_TYPE(tracer_context_key, T_SYMBOL);
|
|
@@ -505,48 +638,104 @@ static VALUE _native_initialize(
|
|
|
505
638
|
state->tracer_context_key = rb_to_id(tracer_context_key);
|
|
506
639
|
}
|
|
507
640
|
|
|
641
|
+
if (thread_begin_tracepoint == Qnil) {
|
|
642
|
+
thread_begin_tracepoint = rb_tracepoint_new(Qnil, RUBY_EVENT_THREAD_BEGIN, on_thread_begin_event, NULL);
|
|
643
|
+
rb_tracepoint_enable(thread_begin_tracepoint);
|
|
644
|
+
}
|
|
645
|
+
|
|
508
646
|
return Qtrue;
|
|
509
647
|
}
|
|
510
648
|
|
|
511
649
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
512
650
|
// It SHOULD NOT be used for other purposes.
|
|
513
|
-
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE
|
|
514
|
-
|
|
651
|
+
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE allow_exception) {
|
|
652
|
+
ENFORCE_BOOLEAN(allow_exception);
|
|
653
|
+
|
|
654
|
+
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
655
|
+
|
|
656
|
+
bool needs_otel_span_key =
|
|
657
|
+
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE));
|
|
658
|
+
|
|
659
|
+
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
|
660
|
+
|
|
661
|
+
// Same as the CpuAndWallTimeWorker does: this can't happen during a sample (and thus not inside the unsafe context)
|
|
662
|
+
if (needs_otel_span_key) thread_context_collector_resolve_otel_span_key_may_lose_gvl(collector_instance);
|
|
515
663
|
|
|
516
|
-
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE), profiler_overhead_stack_thread);
|
|
517
664
|
return Qtrue;
|
|
518
665
|
}
|
|
519
666
|
|
|
520
667
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
521
668
|
// It SHOULD NOT be used for other purposes.
|
|
522
669
|
static VALUE _native_on_gc_start(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
670
|
+
debug_enter_unsafe_context();
|
|
671
|
+
|
|
523
672
|
thread_context_collector_on_gc_start(collector_instance);
|
|
673
|
+
|
|
674
|
+
debug_leave_unsafe_context();
|
|
675
|
+
|
|
524
676
|
return Qtrue;
|
|
525
677
|
}
|
|
526
678
|
|
|
527
679
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
528
680
|
// It SHOULD NOT be used for other purposes.
|
|
529
681
|
static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
530
|
-
(
|
|
682
|
+
debug_enter_unsafe_context();
|
|
683
|
+
|
|
684
|
+
(void) !thread_context_collector_on_gc_finish(collector_instance, true);
|
|
685
|
+
|
|
686
|
+
debug_leave_unsafe_context();
|
|
687
|
+
|
|
531
688
|
return Qtrue;
|
|
532
689
|
}
|
|
533
690
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state) {
|
|
537
|
-
ENFORCE_BOOLEAN(reset_monotonic_to_system_state);
|
|
691
|
+
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
692
|
+
debug_enter_unsafe_context();
|
|
538
693
|
|
|
539
|
-
|
|
540
|
-
TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
694
|
+
thread_context_collector_sample_after_gc(collector_instance);
|
|
541
695
|
|
|
542
|
-
|
|
543
|
-
state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
|
|
544
|
-
}
|
|
696
|
+
debug_leave_unsafe_context();
|
|
545
697
|
|
|
546
|
-
thread_context_collector_sample_after_gc(collector_instance);
|
|
547
698
|
return Qtrue;
|
|
548
699
|
}
|
|
549
700
|
|
|
701
|
+
// Record profiler sampling overhead as a placeholder stack
|
|
702
|
+
static void record_sampling_overhead(thread_context_collector_state *state, per_thread_context *current_thread_context) {
|
|
703
|
+
long wall_time_after_sampling = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
704
|
+
long cpu_time_after_sampling = cpu_time_now_ns(current_thread_context);
|
|
705
|
+
|
|
706
|
+
long overhead_cpu_time_ns = update_cpu_time_since_previous_sample(current_thread_context, cpu_time_after_sampling);
|
|
707
|
+
long overhead_wall_time_ns = update_wall_time_since_previous_sample(current_thread_context, wall_time_after_sampling);
|
|
708
|
+
|
|
709
|
+
ddog_prof_Label overhead_labels[] = {
|
|
710
|
+
{.key = DDOG_CHARSLICE_C("thread id"), .str = DDOG_CHARSLICE_C("0"), .num = 0},
|
|
711
|
+
{.key = DDOG_CHARSLICE_C("thread name"), .str = DDOG_CHARSLICE_C("Datadog::Profiling::Sampling"), .num = 0},
|
|
712
|
+
{.key = DDOG_CHARSLICE_C("state"), .str = DDOG_CHARSLICE_C("had cpu"), .num = 0},
|
|
713
|
+
{.key = DDOG_CHARSLICE_C("profiler overhead"), .num = 1},
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
int64_t end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, wall_time_after_sampling);
|
|
717
|
+
|
|
718
|
+
ddog_prof_Location overhead_location = {
|
|
719
|
+
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
|
720
|
+
.function = {
|
|
721
|
+
.name = DDOG_CHARSLICE_C("sampling"),
|
|
722
|
+
.filename = char_slice_from_ruby_string(state->overhead_filename),
|
|
723
|
+
},
|
|
724
|
+
.line = 0,
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
record_sample(
|
|
728
|
+
state->recorder_instance,
|
|
729
|
+
(ddog_prof_Slice_Location) {.ptr = &overhead_location, .len = 1},
|
|
730
|
+
(sample_values) {.cpu_time_ns = overhead_cpu_time_ns, .cpu_or_wall_samples = 1, .wall_time_ns = overhead_wall_time_ns},
|
|
731
|
+
(sample_labels) {
|
|
732
|
+
.labels = (ddog_prof_Slice_Label) {.ptr = overhead_labels, .len = sizeof(overhead_labels) / sizeof(overhead_labels[0])},
|
|
733
|
+
.state_label = NULL,
|
|
734
|
+
.end_timestamp_ns = end_timestamp_ns,
|
|
735
|
+
}
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
550
739
|
// This function gets called from the Collectors::CpuAndWallTimeWorker to trigger the actual sampling.
|
|
551
740
|
//
|
|
552
741
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
@@ -554,92 +743,79 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_
|
|
|
554
743
|
// Assumption 3: This function IS NOT called from a signal handler. This function is not async-signal-safe.
|
|
555
744
|
// Assumption 4: This function IS NOT called in a reentrant way.
|
|
556
745
|
// Assumption 5: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
746
|
+
// Assumption 6: When called while the profiler is active, `during_sample` MUST be set.
|
|
557
747
|
//
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
struct thread_context_collector_state *state;
|
|
562
|
-
TypedData_Get_Struct(self_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
748
|
+
bool thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns) {
|
|
749
|
+
thread_context_collector_state *state;
|
|
750
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
563
751
|
|
|
564
752
|
VALUE current_thread = rb_thread_current();
|
|
565
|
-
|
|
566
|
-
|
|
753
|
+
per_thread_context *current_thread_context = get_or_create_context_for(current_thread);
|
|
754
|
+
|
|
755
|
+
// Sample the current thread (which will use the current CPU-time) first.
|
|
756
|
+
// This ensures any CPU-time the profiler spends sampling (e.g. the rest of the function) is not wrongly blamed on this thread,
|
|
757
|
+
// and instead is separately accounted by `record_sampling_overhead()` below.
|
|
758
|
+
update_metrics_and_sample(
|
|
759
|
+
state,
|
|
760
|
+
current_thread,
|
|
761
|
+
current_thread_context,
|
|
762
|
+
current_monotonic_wall_time_ns,
|
|
763
|
+
false);
|
|
567
764
|
|
|
568
765
|
VALUE threads = thread_list(state);
|
|
569
766
|
|
|
570
767
|
const long thread_count = RARRAY_LEN(threads);
|
|
571
768
|
for (long i = 0; i < thread_count; i++) {
|
|
572
|
-
VALUE thread =
|
|
573
|
-
|
|
769
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
770
|
+
ENFORCE_THREAD(thread);
|
|
574
771
|
|
|
575
|
-
|
|
576
|
-
// blaming the time the profiler took on whatever's running on the thread right now
|
|
577
|
-
long current_cpu_time_ns = thread != current_thread ? cpu_time_now_ns(thread_context) : cpu_time_at_sample_start_for_current_thread;
|
|
772
|
+
if (thread == current_thread) continue; // Already sampled above
|
|
578
773
|
|
|
579
774
|
update_metrics_and_sample(
|
|
580
775
|
state,
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
current_cpu_time_ns,
|
|
586
|
-
current_monotonic_wall_time_ns
|
|
587
|
-
);
|
|
776
|
+
thread,
|
|
777
|
+
get_or_create_context_for(thread),
|
|
778
|
+
current_monotonic_wall_time_ns,
|
|
779
|
+
false);
|
|
588
780
|
}
|
|
589
781
|
|
|
590
|
-
state->sample_count++;
|
|
782
|
+
state->stats.sample_count++;
|
|
591
783
|
|
|
592
|
-
//
|
|
593
|
-
//
|
|
594
|
-
|
|
784
|
+
// If the current thread is a profiler-internal thread, we don't use record_sampling_overhead()
|
|
785
|
+
// and accept the sampling overhead is attributed to the profiler-internal thread
|
|
786
|
+
// (both are under the same datadog group in the flamegraph).
|
|
787
|
+
// The reason we need to do this is profiler-internal threads are skipped during per-tick sampling
|
|
788
|
+
// so their timestamps are not updated. record_sampling_overhead() would see the stale previous-sample timestamps and
|
|
789
|
+
// attribute the entire sleep interval incorrectly as overhead instead of just the sampling work.
|
|
790
|
+
if (!current_thread_context->is_profiler_internal_thread) {
|
|
791
|
+
record_sampling_overhead(state, current_thread_context);
|
|
792
|
+
}
|
|
595
793
|
|
|
596
|
-
|
|
597
|
-
state,
|
|
598
|
-
/* thread_being_sampled: */ current_thread,
|
|
599
|
-
/* stack_from_thread: */ profiler_overhead_stack_thread,
|
|
600
|
-
current_thread_context,
|
|
601
|
-
// Here we use the overhead thread's sampling buffer so as to not invalidate the cache in the buffer of the thread being sampled
|
|
602
|
-
get_or_create_context_for(profiler_overhead_stack_thread, state)->sampling_buffer,
|
|
603
|
-
cpu_time_now_ns(current_thread_context),
|
|
604
|
-
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
605
|
-
);
|
|
794
|
+
return is_otel_current_span_key_needed(state);
|
|
606
795
|
}
|
|
607
796
|
|
|
608
797
|
static void update_metrics_and_sample(
|
|
609
|
-
|
|
798
|
+
thread_context_collector_state *state,
|
|
610
799
|
VALUE thread_being_sampled,
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
long current_cpu_time_ns,
|
|
615
|
-
long current_monotonic_wall_time_ns
|
|
800
|
+
per_thread_context *thread_context,
|
|
801
|
+
long current_monotonic_wall_time_ns,
|
|
802
|
+
bool force_sample
|
|
616
803
|
) {
|
|
804
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
617
805
|
bool is_gvl_waiting_state =
|
|
618
|
-
handle_gvl_waiting(state, thread_being_sampled,
|
|
806
|
+
handle_gvl_waiting(state, thread_being_sampled, thread_context, current_cpu_time_ns);
|
|
807
|
+
|
|
808
|
+
if (skip_sample(state, thread_context, is_gvl_waiting_state, force_sample)) return;
|
|
619
809
|
|
|
620
810
|
// Don't assign/update cpu during "Waiting for GVL"
|
|
621
|
-
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 :
|
|
622
|
-
&thread_context->cpu_time_at_previous_sample_ns,
|
|
623
|
-
current_cpu_time_ns,
|
|
624
|
-
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
625
|
-
IS_NOT_WALL_TIME
|
|
626
|
-
);
|
|
811
|
+
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
627
812
|
|
|
628
|
-
long wall_time_elapsed_ns =
|
|
629
|
-
&thread_context->wall_time_at_previous_sample_ns,
|
|
630
|
-
current_monotonic_wall_time_ns,
|
|
631
|
-
// We explicitly pass in `INVALID_TIME` as an argument for `gc_start_time_ns` here because we don't want wall-time
|
|
632
|
-
// accounting to change during GC.
|
|
633
|
-
// E.g. if 60 seconds pass in the real world, 60 seconds of wall-time are recorded, regardless of the thread doing
|
|
634
|
-
// GC or not.
|
|
635
|
-
INVALID_TIME,
|
|
636
|
-
IS_WALL_TIME
|
|
637
|
-
);
|
|
813
|
+
long wall_time_elapsed_ns = update_wall_time_since_previous_sample(thread_context, current_monotonic_wall_time_ns);
|
|
638
814
|
|
|
639
815
|
// A thread enters "Waiting for GVL", well, as the name implies, without the GVL.
|
|
640
816
|
//
|
|
641
817
|
// As a consequence, it's possible that a thread enters "Waiting for GVL" in parallel with the current thread working
|
|
642
|
-
// on sampling, and thus for the
|
|
818
|
+
// on sampling, and thus for the `current_monotonic_wall_time_ns` (which is recorded at the start of sampling)
|
|
643
819
|
// to be < the time at which we started Waiting for GVL.
|
|
644
820
|
//
|
|
645
821
|
// All together, this means that when `handle_gvl_waiting` creates an extra sample (see comments on that function for
|
|
@@ -654,9 +830,7 @@ static void update_metrics_and_sample(
|
|
|
654
830
|
trigger_sample_for_thread(
|
|
655
831
|
state,
|
|
656
832
|
thread_being_sampled,
|
|
657
|
-
stack_from_thread,
|
|
658
833
|
thread_context,
|
|
659
|
-
sampling_buffer,
|
|
660
834
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = wall_time_elapsed_ns},
|
|
661
835
|
current_monotonic_wall_time_ns,
|
|
662
836
|
NULL,
|
|
@@ -665,6 +839,37 @@ static void update_metrics_and_sample(
|
|
|
665
839
|
);
|
|
666
840
|
}
|
|
667
841
|
|
|
842
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample) {
|
|
843
|
+
if (!force_sample && thread_context->is_profiler_internal_thread) {
|
|
844
|
+
state->stats.profiler_thread_samples_skipped++;
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Racy read but harmless, can only cause an extra sample
|
|
849
|
+
uint64_t gvl_state_change_count = thread_context->gvl_state_change_count;
|
|
850
|
+
|
|
851
|
+
// Skip this per-tick sample entirely when the thread does not have the GVL and did not acquire
|
|
852
|
+
// it since the previous sample: its Ruby-level stack has not changed. The skipped wall-time will
|
|
853
|
+
// be picked up by either by an extra sample when the thread acquires the GVL, or by
|
|
854
|
+
// the on-serialize flush in the stack recorder (using was_skipped_at_last_sample).
|
|
855
|
+
// The check is gated by `!is_gvl_waiting_state` so the existing "Waiting for GVL" machinery
|
|
856
|
+
// in handle_gvl_waiting (situation 1 extra sample, situation 2 regular sample) keeps running.
|
|
857
|
+
// TODO: we could probably also skip while "Waiting for GVL"
|
|
858
|
+
if (!is_gvl_waiting_state &&
|
|
859
|
+
!force_sample &&
|
|
860
|
+
(gvl_state_change_count & GVL_SUSPENDED) &&
|
|
861
|
+
gvl_state_change_count == thread_context->gvl_state_change_count_at_previous_sample) {
|
|
862
|
+
state->stats.inactive_thread_samples_skipped++;
|
|
863
|
+
thread_context->was_skipped_at_last_sample = true;
|
|
864
|
+
return true; // Do NOT update wall_time_at_previous_sample_ns or cpu_time_at_previous_sample_ns
|
|
865
|
+
} else {
|
|
866
|
+
// We are going to sample, update the state accordingly:
|
|
867
|
+
thread_context->gvl_state_change_count_at_previous_sample = gvl_state_change_count;
|
|
868
|
+
thread_context->was_skipped_at_last_sample = false;
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
668
873
|
// This function gets called when Ruby is about to start running the Garbage Collector on the current thread.
|
|
669
874
|
// It updates the per_thread_context of the current thread to include the current cpu/wall times, to be used to later
|
|
670
875
|
// create an event including the cpu/wall time spent in garbage collector work.
|
|
@@ -676,24 +881,27 @@ static void update_metrics_and_sample(
|
|
|
676
881
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
677
882
|
// Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
678
883
|
void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
679
|
-
|
|
884
|
+
thread_context_collector_state *state;
|
|
680
885
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return;
|
|
681
|
-
// This should never fail
|
|
682
|
-
TypedData_Get_Struct(self_instance,
|
|
886
|
+
// This should never fail when the above check passes
|
|
887
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
683
888
|
|
|
684
|
-
|
|
889
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
685
890
|
|
|
686
|
-
//
|
|
687
|
-
//
|
|
688
|
-
// GC" is a rare enough case that we can just ignore it.
|
|
689
|
-
// We can always improve this later if we find that this happens often (and we have the counter to help us figure that out)!
|
|
891
|
+
// Context is created eagerly via on_thread_begin_event, so this should not normally be NULL.
|
|
892
|
+
// We can't get_or_create_context_for() here since we can't allocate (GC context).
|
|
690
893
|
if (thread_context == NULL) {
|
|
691
894
|
state->stats.gc_samples_missed_due_to_missing_context++;
|
|
692
895
|
return;
|
|
693
896
|
}
|
|
694
897
|
|
|
695
898
|
// Here we record the wall-time first and in on_gc_finish we record it second to try to avoid having wall-time be slightly < cpu-time
|
|
696
|
-
|
|
899
|
+
long wall_time_at_start_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
|
900
|
+
|
|
901
|
+
// If our start timestamp is not OK, we skip tracking this GC as well
|
|
902
|
+
if (wall_time_at_start_ns == 0) return;
|
|
903
|
+
|
|
904
|
+
thread_context->gc_tracking.wall_time_at_start_ns = wall_time_at_start_ns;
|
|
697
905
|
thread_context->gc_tracking.cpu_time_at_start_ns = cpu_time_now_ns(thread_context);
|
|
698
906
|
}
|
|
699
907
|
|
|
@@ -707,23 +915,29 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
|
707
915
|
//
|
|
708
916
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
709
917
|
// Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
918
|
+
// Assumption 3: If allow_prepare_sample is true while profiling is active, during_sample MUST be set by the caller.
|
|
710
919
|
__attribute__((warn_unused_result))
|
|
711
|
-
bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
712
|
-
|
|
920
|
+
bool thread_context_collector_on_gc_finish(VALUE self_instance, bool allow_prepare_sample) {
|
|
921
|
+
thread_context_collector_state *state;
|
|
713
922
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
|
714
|
-
// This should never fail
|
|
715
|
-
TypedData_Get_Struct(self_instance,
|
|
923
|
+
// This should never fail when the above check passes
|
|
924
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
716
925
|
|
|
717
|
-
|
|
926
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
718
927
|
|
|
719
|
-
//
|
|
720
|
-
// how often this happens -- see on_gc_start.
|
|
928
|
+
// Context is created eagerly, so this should not normally be NULL (see on_gc_start).
|
|
721
929
|
if (thread_context == NULL) return false;
|
|
722
930
|
|
|
931
|
+
if (thread_context->prepare_sample_on_gc_finish) {
|
|
932
|
+
thread_context->prepare_sample_on_gc_finish = false;
|
|
933
|
+
if (allow_prepare_sample) prepare_sample_thread(rb_thread_current(), &thread_context->sampling_buffer);
|
|
934
|
+
}
|
|
935
|
+
|
|
723
936
|
long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
|
|
724
937
|
long wall_time_at_start_ns = thread_context->gc_tracking.wall_time_at_start_ns;
|
|
725
938
|
|
|
726
|
-
|
|
939
|
+
// Both fields are always set and cleared together, so checking one is enough
|
|
940
|
+
if (cpu_time_at_start_ns == INVALID_TIME) {
|
|
727
941
|
// If this happened, it means that on_gc_start was either never called for the thread OR it was called but no thread
|
|
728
942
|
// context existed at the time. The former can be the result of a bug, but since we can't distinguish them, we just
|
|
729
943
|
// do nothing.
|
|
@@ -758,9 +972,9 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
758
972
|
state->gc_tracking.accumulated_wall_time_ns += gc_wall_time_elapsed_ns;
|
|
759
973
|
state->gc_tracking.wall_time_at_previous_gc_ns = wall_time_at_finish_ns;
|
|
760
974
|
|
|
761
|
-
// Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample
|
|
762
|
-
// We don't
|
|
763
|
-
//
|
|
975
|
+
// Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample.
|
|
976
|
+
// We don't do the same for wall-time, because GC is just like any other reason a thread didn't make
|
|
977
|
+
// progress -- time always goes forward regardless of the thread making progress on what it wanted.
|
|
764
978
|
if (thread_context->cpu_time_at_previous_sample_ns != INVALID_TIME) {
|
|
765
979
|
thread_context->cpu_time_at_previous_sample_ns += gc_cpu_time_elapsed_ns;
|
|
766
980
|
}
|
|
@@ -786,12 +1000,20 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
786
1000
|
// Assumption 2: This function is allowed to raise exceptions. Caller is responsible for handling them, if needed.
|
|
787
1001
|
// Assumption 3: Unlike `on_gc_start` and `on_gc_finish`, this method is allowed to allocate memory as needed.
|
|
788
1002
|
// Assumption 4: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
1003
|
+
// Assumption 5: When called while the profiler is active, `during_sample` MUST be set.
|
|
789
1004
|
VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
790
|
-
|
|
791
|
-
TypedData_Get_Struct(self_instance,
|
|
1005
|
+
thread_context_collector_state *state;
|
|
1006
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
792
1007
|
|
|
793
1008
|
if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
|
|
794
|
-
|
|
1009
|
+
// Rarely, we might be called with nothing to do, as an earlier call already flushed the needed info.
|
|
1010
|
+
// This is because Ruby clears the "pending" flag for an entire batch of postponed jobs BEFORE running any of them
|
|
1011
|
+
// (see `rb_postponed_job_flush`). Thus, if another GC happens while an earlier job in the postponed batch is
|
|
1012
|
+
// running (and other parts of the profiler such as the heap profiler can trigger this)
|
|
1013
|
+
// then `on_gc_finish` records that data and asks for a second flush, even though Ruby has not yet run the
|
|
1014
|
+
// first one. Because of this, Ruby can call us once more with nothing left to do.
|
|
1015
|
+
state->stats.gc_samples_skipped_nothing_to_flush++;
|
|
1016
|
+
return Qnil;
|
|
795
1017
|
}
|
|
796
1018
|
|
|
797
1019
|
int max_labels_needed_for_gc = 7; // Magic number gets validated inside gc_profiling_set_metadata
|
|
@@ -801,11 +1023,7 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
801
1023
|
ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = label_pos};
|
|
802
1024
|
|
|
803
1025
|
// The end_timestamp_ns is treated specially by libdatadog and that's why it's not added as a ddog_prof_Label
|
|
804
|
-
int64_t end_timestamp_ns =
|
|
805
|
-
|
|
806
|
-
if (state->timeline_enabled) {
|
|
807
|
-
end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, state->gc_tracking.wall_time_at_previous_gc_ns);
|
|
808
|
-
}
|
|
1026
|
+
int64_t end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, state->gc_tracking.wall_time_at_previous_gc_ns);
|
|
809
1027
|
|
|
810
1028
|
record_placeholder_stack(
|
|
811
1029
|
state->recorder_instance,
|
|
@@ -833,12 +1051,23 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
833
1051
|
return Qnil;
|
|
834
1052
|
}
|
|
835
1053
|
|
|
1054
|
+
// Let the recorder do any cleanup/updates it requires after a GC step.
|
|
1055
|
+
//
|
|
1056
|
+
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
1057
|
+
// Assumption 2: This function is allowed to raise exceptions. Caller is responsible for handling them, if needed.
|
|
1058
|
+
VALUE thread_context_collector_heap_update_may_lose_gvl(VALUE self_instance) {
|
|
1059
|
+
thread_context_collector_state *state;
|
|
1060
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1061
|
+
|
|
1062
|
+
recorder_heap_update_may_lose_gvl(state->recorder_instance);
|
|
1063
|
+
|
|
1064
|
+
return Qnil; // for rb_rescue2
|
|
1065
|
+
}
|
|
1066
|
+
|
|
836
1067
|
static void trigger_sample_for_thread(
|
|
837
|
-
|
|
838
|
-
VALUE
|
|
839
|
-
|
|
840
|
-
struct per_thread_context *thread_context,
|
|
841
|
-
sampling_buffer* sampling_buffer,
|
|
1068
|
+
thread_context_collector_state *state,
|
|
1069
|
+
VALUE thread_being_sampled,
|
|
1070
|
+
per_thread_context *thread_context,
|
|
842
1071
|
sample_values values,
|
|
843
1072
|
long current_monotonic_wall_time_ns,
|
|
844
1073
|
// These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
|
|
@@ -849,7 +1078,6 @@ static void trigger_sample_for_thread(
|
|
|
849
1078
|
int max_label_count =
|
|
850
1079
|
1 + // thread id
|
|
851
1080
|
1 + // thread name
|
|
852
|
-
1 + // profiler overhead
|
|
853
1081
|
2 + // ruby vm type and allocation class
|
|
854
1082
|
1 + // state (only set for cpu/wall-time samples)
|
|
855
1083
|
2; // local root span id and span id
|
|
@@ -861,13 +1089,13 @@ static void trigger_sample_for_thread(
|
|
|
861
1089
|
.str = thread_context->thread_id_char_slice
|
|
862
1090
|
};
|
|
863
1091
|
|
|
864
|
-
VALUE thread_name = thread_name_for(
|
|
1092
|
+
VALUE thread_name = thread_name_for(thread_being_sampled);
|
|
865
1093
|
if (thread_name != Qnil) {
|
|
866
1094
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
867
1095
|
.key = DDOG_CHARSLICE_C("thread name"),
|
|
868
1096
|
.str = char_slice_from_ruby_string(thread_name)
|
|
869
1097
|
};
|
|
870
|
-
} else if (
|
|
1098
|
+
} else if (thread_being_sampled == state->main_thread) { // Threads are often not named, but we can have a nice fallback for this special thread
|
|
871
1099
|
ddog_CharSlice main_thread_name = DDOG_CHARSLICE_C("main");
|
|
872
1100
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
873
1101
|
.key = DDOG_CHARSLICE_C("thread name"),
|
|
@@ -882,12 +1110,12 @@ static void trigger_sample_for_thread(
|
|
|
882
1110
|
};
|
|
883
1111
|
}
|
|
884
1112
|
|
|
885
|
-
|
|
886
|
-
trace_identifiers_for(state,
|
|
1113
|
+
trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
|
|
1114
|
+
trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result);
|
|
887
1115
|
|
|
888
|
-
if (!trace_identifiers_result.valid && state->otel_context_enabled !=
|
|
1116
|
+
if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
|
|
889
1117
|
// If we couldn't get something with ddtrace, let's see if we can get some trace identifiers from opentelemetry directly
|
|
890
|
-
otel_without_ddtrace_trace_identifiers_for(state,
|
|
1118
|
+
otel_without_ddtrace_trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result);
|
|
891
1119
|
}
|
|
892
1120
|
|
|
893
1121
|
if (trace_identifiers_result.valid) {
|
|
@@ -912,13 +1140,6 @@ static void trigger_sample_for_thread(
|
|
|
912
1140
|
}
|
|
913
1141
|
}
|
|
914
1142
|
|
|
915
|
-
if (thread != stack_from_thread) {
|
|
916
|
-
labels[label_pos++] = (ddog_prof_Label) {
|
|
917
|
-
.key = DDOG_CHARSLICE_C("profiler overhead"),
|
|
918
|
-
.num = 1
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
|
|
922
1143
|
if (ruby_vm_type != NULL) {
|
|
923
1144
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
924
1145
|
.key = DDOG_CHARSLICE_C("ruby vm type"),
|
|
@@ -952,20 +1173,21 @@ static void trigger_sample_for_thread(
|
|
|
952
1173
|
// @ivoanjo: I wonder if C compilers are smart enough to statically prove this check never triggers unless someone
|
|
953
1174
|
// changes the code erroneously and remove it entirely?
|
|
954
1175
|
if (label_pos > max_label_count) {
|
|
955
|
-
|
|
1176
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected label_pos (%d) > max_label_count (%d)", label_pos, max_label_count);
|
|
956
1177
|
}
|
|
957
1178
|
|
|
958
1179
|
ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = label_pos};
|
|
959
1180
|
|
|
960
1181
|
// The end_timestamp_ns is treated specially by libdatadog and that's why it's not added as a ddog_prof_Label
|
|
961
1182
|
int64_t end_timestamp_ns = 0;
|
|
962
|
-
if (
|
|
1183
|
+
if (current_monotonic_wall_time_ns != INVALID_TIME) {
|
|
963
1184
|
end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, current_monotonic_wall_time_ns);
|
|
964
1185
|
}
|
|
965
1186
|
|
|
966
1187
|
sample_thread(
|
|
967
|
-
|
|
968
|
-
sampling_buffer,
|
|
1188
|
+
thread_being_sampled,
|
|
1189
|
+
&thread_context->sampling_buffer,
|
|
1190
|
+
state->locations,
|
|
969
1191
|
state->recorder_instance,
|
|
970
1192
|
values,
|
|
971
1193
|
(sample_labels) {
|
|
@@ -973,7 +1195,10 @@ static void trigger_sample_for_thread(
|
|
|
973
1195
|
.state_label = state_label,
|
|
974
1196
|
.end_timestamp_ns = end_timestamp_ns,
|
|
975
1197
|
.is_gvl_waiting_state = is_gvl_waiting_state,
|
|
976
|
-
}
|
|
1198
|
+
},
|
|
1199
|
+
state->native_filenames_enabled,
|
|
1200
|
+
state->native_filenames_cache,
|
|
1201
|
+
state->show_classes
|
|
977
1202
|
);
|
|
978
1203
|
}
|
|
979
1204
|
|
|
@@ -981,36 +1206,48 @@ static void trigger_sample_for_thread(
|
|
|
981
1206
|
// It SHOULD NOT be used for other purposes.
|
|
982
1207
|
static VALUE _native_thread_list(DDTRACE_UNUSED VALUE _self) {
|
|
983
1208
|
VALUE result = rb_ary_new();
|
|
1209
|
+
|
|
1210
|
+
debug_enter_unsafe_context();
|
|
1211
|
+
|
|
984
1212
|
ddtrace_thread_list(result);
|
|
1213
|
+
|
|
1214
|
+
debug_leave_unsafe_context();
|
|
1215
|
+
|
|
985
1216
|
return result;
|
|
986
1217
|
}
|
|
987
1218
|
|
|
988
|
-
static
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
|
|
993
|
-
thread_context = (struct per_thread_context*) value_context;
|
|
994
|
-
} else {
|
|
995
|
-
thread_context = ruby_xcalloc(1, sizeof(struct per_thread_context));
|
|
996
|
-
initialize_context(thread, thread_context, state);
|
|
997
|
-
st_insert(state->hash_map_per_thread_context, (st_data_t) thread, (st_data_t) thread_context);
|
|
1219
|
+
static void check_frozen_thread(VALUE thread) {
|
|
1220
|
+
if (RB_OBJ_FROZEN(thread)) {
|
|
1221
|
+
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);
|
|
998
1222
|
}
|
|
999
|
-
|
|
1000
|
-
return thread_context;
|
|
1001
1223
|
}
|
|
1002
1224
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1225
|
+
// See the docs on struct per_thread_context.
|
|
1226
|
+
// This allocates a Ruby object and therefore needs the GVL and is not safe to call from RUBY_INTERNAL_EVENT_* hooks.
|
|
1227
|
+
static per_thread_context *get_or_create_context_for(VALUE thread) {
|
|
1228
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1229
|
+
if (thread_context != NULL) return thread_context;
|
|
1006
1230
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1231
|
+
check_frozen_thread(thread);
|
|
1232
|
+
|
|
1233
|
+
thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
|
|
1234
|
+
initialize_context(thread, thread_context);
|
|
1235
|
+
|
|
1236
|
+
VALUE wrapper = TypedData_Wrap_Struct(rb_cObject, &per_thread_context_typed_data, thread_context);
|
|
1237
|
+
rb_ivar_set(thread, dd_per_thread_context_id, wrapper);
|
|
1010
1238
|
|
|
1239
|
+
set_per_thread_context(thread, thread_context);
|
|
1011
1240
|
return thread_context;
|
|
1012
1241
|
}
|
|
1013
1242
|
|
|
1243
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
|
|
1244
|
+
if (!ddtrace_rb_ractor_main_p()) return;
|
|
1245
|
+
|
|
1246
|
+
VALUE thread = rb_tracearg_self(rb_tracearg_from_tracepoint(tracepoint_data));
|
|
1247
|
+
ENFORCE_THREAD(thread);
|
|
1248
|
+
get_or_create_context_for(thread);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1014
1251
|
#define LOGGING_GEM_PATH "/lib/logging/diagnostic_context.rb"
|
|
1015
1252
|
|
|
1016
1253
|
// The `logging` gem monkey patches thread creation, which makes the `invoke_location_for` useless, since every thread
|
|
@@ -1032,8 +1269,11 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
|
|
|
1032
1269
|
return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
|
|
1033
1270
|
}
|
|
1034
1271
|
|
|
1035
|
-
static void initialize_context(VALUE thread,
|
|
1036
|
-
|
|
1272
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context) {
|
|
1273
|
+
// We always create per_thread_context's with latest_max_frames; that value is kept in sync with the
|
|
1274
|
+
// active profiler's max_frames by the global reset that runs when profiling starts
|
|
1275
|
+
// so we expect to always see here the latest correct value to be used.
|
|
1276
|
+
sampling_buffer_initialize(&thread_context->sampling_buffer, latest_max_frames);
|
|
1037
1277
|
|
|
1038
1278
|
snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
|
|
1039
1279
|
thread_context->thread_id_char_slice = (ddog_CharSlice) {.ptr = thread_context->thread_id, .len = strlen(thread_context->thread_id)};
|
|
@@ -1052,7 +1292,7 @@ static void initialize_context(VALUE thread, struct per_thread_context *thread_c
|
|
|
1052
1292
|
} else {
|
|
1053
1293
|
snprintf(thread_context->thread_invoke_location, THREAD_INVOKE_LOCATION_LIMIT_CHARS, "%s", "(Unnamed thread)");
|
|
1054
1294
|
}
|
|
1055
|
-
} else if (thread !=
|
|
1295
|
+
} else if (thread != rb_thread_main()) {
|
|
1056
1296
|
// If the first function of a thread is native code, there won't be an invoke location, so we use this fallback.
|
|
1057
1297
|
// 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
|
|
1058
1298
|
// symbol attached to it.
|
|
@@ -1066,52 +1306,76 @@ static void initialize_context(VALUE thread, struct per_thread_context *thread_c
|
|
|
1066
1306
|
|
|
1067
1307
|
thread_context->thread_cpu_time_id = thread_cpu_time_id_for(thread);
|
|
1068
1308
|
|
|
1069
|
-
|
|
1070
|
-
thread_context->cpu_time_at_previous_sample_ns =
|
|
1071
|
-
thread_context->wall_time_at_previous_sample_ns = INVALID_TIME;
|
|
1309
|
+
thread_context->wall_time_at_previous_sample_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
1310
|
+
thread_context->cpu_time_at_previous_sample_ns = cpu_time_now_ns(thread_context);
|
|
1072
1311
|
|
|
1073
1312
|
// These will only be used during a GC operation
|
|
1074
1313
|
thread_context->gc_tracking.cpu_time_at_start_ns = INVALID_TIME;
|
|
1075
1314
|
thread_context->gc_tracking.wall_time_at_start_ns = INVALID_TIME;
|
|
1076
1315
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
// kind of synchronization (e.g. by threads without the GVL).
|
|
1080
|
-
//
|
|
1081
|
-
// We set this marker here for two purposes:
|
|
1082
|
-
// * To make sure there's no stale data from a previous execution of the profiler.
|
|
1083
|
-
// * To mark threads that are actually being profiled
|
|
1084
|
-
//
|
|
1085
|
-
// (Setting this is potentially a race, but what we want is to avoid _stale_ data, so
|
|
1086
|
-
// if this gets set concurrently with context initialization, then such a value will belong
|
|
1087
|
-
// to the current profiler instance, so that's OK)
|
|
1088
|
-
gvl_profiling_state_thread_object_set(thread, GVL_WAITING_ENABLED_EMPTY);
|
|
1089
|
-
#endif
|
|
1316
|
+
thread_context->gvl_waiting_at = 0;
|
|
1317
|
+
thread_context->gvl_state_change_count = 0;
|
|
1090
1318
|
}
|
|
1091
1319
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1320
|
+
// This MUST be called before profiling starts, so that a new profiler session starts from a fresh state and never
|
|
1321
|
+
// observes or includes any leftover stale state from a previous session.
|
|
1322
|
+
// Such a call MUST happen while the CpuAndWallTimeWorker is stopped (e.g. no tracepoints active, no signals
|
|
1323
|
+
// triggering samples, no gvl hooks, etc).
|
|
1324
|
+
//
|
|
1325
|
+
// It updates the global `latest_max_frames` from the given (latest) ThreadContext and (re)creates every per-thread
|
|
1326
|
+
// context's sampling buffer sized accordingly, so the buffers always match the collector that's about to start
|
|
1327
|
+
// sampling -- even if a previous session used a different max_frames.
|
|
1328
|
+
void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance) {
|
|
1329
|
+
thread_context_collector_state *state;
|
|
1330
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1331
|
+
|
|
1332
|
+
// Update global max frames to be used when allocating sampling buffers
|
|
1333
|
+
if (state->locations.len == 0) {
|
|
1334
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected locations.len == 0. Is this ThreadContext not initialized?");
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
latest_max_frames = state->locations.len;
|
|
1338
|
+
|
|
1339
|
+
VALUE threads = thread_list(state);
|
|
1340
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
1341
|
+
for (long i = 0; i < thread_count; i++) {
|
|
1342
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
1343
|
+
ENFORCE_THREAD(thread);
|
|
1344
|
+
|
|
1345
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1346
|
+
if (thread_context != NULL) {
|
|
1347
|
+
bool is_profiler_internal_thread = thread_context->is_profiler_internal_thread;
|
|
1348
|
+
|
|
1349
|
+
sampling_buffer_free(&thread_context->sampling_buffer);
|
|
1350
|
+
memset(thread_context, 0, sizeof(per_thread_context));
|
|
1351
|
+
initialize_context(thread, thread_context);
|
|
1352
|
+
|
|
1353
|
+
thread_context->is_profiler_internal_thread = is_profiler_internal_thread;
|
|
1354
|
+
} else {
|
|
1355
|
+
// If thread didn't have a context, let's trigger its creation
|
|
1356
|
+
get_or_create_context_for(thread);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1095
1359
|
}
|
|
1096
1360
|
|
|
1097
1361
|
static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1098
|
-
|
|
1099
|
-
TypedData_Get_Struct(collector_instance,
|
|
1362
|
+
thread_context_collector_state *state;
|
|
1363
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1100
1364
|
|
|
1101
1365
|
VALUE result = rb_str_new2(" (native state)");
|
|
1102
1366
|
|
|
1103
1367
|
// Update this when modifying state struct
|
|
1104
|
-
rb_str_concat(result, rb_sprintf(" max_frames=%d", state->
|
|
1105
|
-
rb_str_concat(result, rb_sprintf(" hash_map_per_thread_context=%"PRIsVALUE, per_thread_context_st_table_as_ruby_hash(state)));
|
|
1368
|
+
rb_str_concat(result, rb_sprintf(" max_frames=%d", state->locations.len));
|
|
1106
1369
|
rb_str_concat(result, rb_sprintf(" recorder_instance=%"PRIsVALUE, state->recorder_instance));
|
|
1107
1370
|
VALUE tracer_context_key = state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY ? Qnil : ID2SYM(state->tracer_context_key);
|
|
1108
1371
|
rb_str_concat(result, rb_sprintf(" tracer_context_key=%+"PRIsVALUE, tracer_context_key));
|
|
1109
|
-
rb_str_concat(result, rb_sprintf("
|
|
1110
|
-
rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_as_ruby_hash(state)));
|
|
1372
|
+
rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_to_ruby_hash(state, rb_hash_new())));
|
|
1111
1373
|
rb_str_concat(result, rb_sprintf(" endpoint_collection_enabled=%"PRIsVALUE, state->endpoint_collection_enabled ? Qtrue : Qfalse));
|
|
1112
|
-
rb_str_concat(result, rb_sprintf("
|
|
1374
|
+
rb_str_concat(result, rb_sprintf(" native_filenames_enabled=%"PRIsVALUE, state->native_filenames_enabled ? Qtrue : Qfalse));
|
|
1375
|
+
rb_str_concat(result, rb_sprintf(" show_classes=%"PRIsVALUE, state->show_classes ? Qtrue : Qfalse));
|
|
1376
|
+
// Note: `st_table_size()` is available from Ruby 3.2+ but not before
|
|
1377
|
+
rb_str_concat(result, rb_sprintf(" native_filenames_cache_size=%zu", state->native_filenames_cache->num_entries));
|
|
1113
1378
|
rb_str_concat(result, rb_sprintf(" otel_context_enabled=%d", state->otel_context_enabled));
|
|
1114
|
-
rb_str_concat(result, rb_sprintf(" allocation_type_enabled=%"PRIsVALUE, state->allocation_type_enabled ? Qtrue : Qfalse));
|
|
1115
1379
|
rb_str_concat(result, rb_sprintf(
|
|
1116
1380
|
" time_converter_state={.system_epoch_ns_reference=%ld, .delta_to_epoch_ns=%ld}",
|
|
1117
1381
|
state->time_converter_state.system_epoch_ns_reference,
|
|
@@ -1120,56 +1384,55 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
|
1120
1384
|
rb_str_concat(result, rb_sprintf(" main_thread=%"PRIsVALUE, state->main_thread));
|
|
1121
1385
|
rb_str_concat(result, rb_sprintf(" gc_tracking=%"PRIsVALUE, gc_tracking_as_ruby_hash(state)));
|
|
1122
1386
|
rb_str_concat(result, rb_sprintf(" otel_current_span_key=%"PRIsVALUE, state->otel_current_span_key));
|
|
1123
|
-
rb_str_concat(result, rb_sprintf(" global_waiting_for_gvl_threshold_ns=%u", global_waiting_for_gvl_threshold_ns));
|
|
1124
1387
|
|
|
1125
1388
|
return result;
|
|
1126
1389
|
}
|
|
1127
1390
|
|
|
1128
|
-
static VALUE
|
|
1129
|
-
VALUE result = rb_hash_new();
|
|
1130
|
-
st_foreach(state->hash_map_per_thread_context, per_thread_context_as_ruby_hash, result);
|
|
1131
|
-
return result;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash) {
|
|
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;
|
|
1391
|
+
static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context) {
|
|
1138
1392
|
VALUE context_as_hash = rb_hash_new();
|
|
1139
|
-
rb_hash_aset(result, thread, context_as_hash);
|
|
1140
1393
|
|
|
1141
1394
|
VALUE arguments[] = {
|
|
1142
1395
|
ID2SYM(rb_intern("thread_id")), /* => */ rb_str_new2(thread_context->thread_id),
|
|
1143
1396
|
ID2SYM(rb_intern("thread_invoke_location")), /* => */ rb_str_new2(thread_context->thread_invoke_location),
|
|
1144
1397
|
ID2SYM(rb_intern("thread_cpu_time_id_valid?")), /* => */ thread_context->thread_cpu_time_id.valid ? Qtrue : Qfalse,
|
|
1145
|
-
|
|
1398
|
+
#ifdef __APPLE__
|
|
1399
|
+
ID2SYM(rb_intern("thread_cpu_time_id")), /* => */ ULL2NUM(thread_context->thread_cpu_time_id.clock_id),
|
|
1400
|
+
#else
|
|
1401
|
+
ID2SYM(rb_intern("thread_cpu_time_id")), /* => */ CLOCKID2NUM(thread_context->thread_cpu_time_id.clock_id),
|
|
1402
|
+
#endif
|
|
1146
1403
|
ID2SYM(rb_intern("cpu_time_at_previous_sample_ns")), /* => */ LONG2NUM(thread_context->cpu_time_at_previous_sample_ns),
|
|
1147
1404
|
ID2SYM(rb_intern("wall_time_at_previous_sample_ns")), /* => */ LONG2NUM(thread_context->wall_time_at_previous_sample_ns),
|
|
1148
1405
|
|
|
1149
1406
|
ID2SYM(rb_intern("gc_tracking.cpu_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.cpu_time_at_start_ns),
|
|
1150
1407
|
ID2SYM(rb_intern("gc_tracking.wall_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.wall_time_at_start_ns),
|
|
1151
1408
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1409
|
+
ID2SYM(rb_intern("gvl_waiting_at")), /* => */ LONG2NUM(thread_context->gvl_waiting_at),
|
|
1410
|
+
ID2SYM(rb_intern("gvl_state_change_count")), /* => */ ULL2NUM(thread_context->gvl_state_change_count),
|
|
1411
|
+
ID2SYM(rb_intern("gvl_state_change_count_at_previous_sample")), /* => */ ULL2NUM(thread_context->gvl_state_change_count_at_previous_sample),
|
|
1412
|
+
ID2SYM(rb_intern("was_skipped_at_last_sample")), /* => */ thread_context->was_skipped_at_last_sample ? Qtrue : Qfalse,
|
|
1413
|
+
ID2SYM(rb_intern("prepare_sample_on_gc_finish")), /* => */ thread_context->prepare_sample_on_gc_finish ? Qtrue : Qfalse,
|
|
1414
|
+
ID2SYM(rb_intern("is_profiler_internal_thread")), /* => */ thread_context->is_profiler_internal_thread ? Qtrue : Qfalse,
|
|
1155
1415
|
};
|
|
1156
1416
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(context_as_hash, arguments[i], arguments[i+1]);
|
|
1157
1417
|
|
|
1158
|
-
return
|
|
1418
|
+
return context_as_hash;
|
|
1159
1419
|
}
|
|
1160
1420
|
|
|
1161
|
-
static VALUE
|
|
1421
|
+
static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash) {
|
|
1162
1422
|
// Update this when modifying state struct (stats inner struct)
|
|
1163
|
-
VALUE stats_as_hash = rb_hash_new();
|
|
1164
1423
|
VALUE arguments[] = {
|
|
1424
|
+
ID2SYM(rb_intern("sample_count")), /* => */ UINT2NUM(state->stats.sample_count),
|
|
1165
1425
|
ID2SYM(rb_intern("gc_samples")), /* => */ UINT2NUM(state->stats.gc_samples),
|
|
1166
1426
|
ID2SYM(rb_intern("gc_samples_missed_due_to_missing_context")), /* => */ UINT2NUM(state->stats.gc_samples_missed_due_to_missing_context),
|
|
1427
|
+
ID2SYM(rb_intern("gc_samples_skipped_nothing_to_flush")), /* => */ UINT2NUM(state->stats.gc_samples_skipped_nothing_to_flush),
|
|
1428
|
+
ID2SYM(rb_intern("inactive_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.inactive_thread_samples_skipped),
|
|
1429
|
+
ID2SYM(rb_intern("profiler_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.profiler_thread_samples_skipped),
|
|
1167
1430
|
};
|
|
1168
|
-
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(
|
|
1169
|
-
return
|
|
1431
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
|
|
1432
|
+
return hash;
|
|
1170
1433
|
}
|
|
1171
1434
|
|
|
1172
|
-
static VALUE gc_tracking_as_ruby_hash(
|
|
1435
|
+
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state) {
|
|
1173
1436
|
// Update this when modifying state struct (gc_tracking inner struct)
|
|
1174
1437
|
VALUE result = rb_hash_new();
|
|
1175
1438
|
VALUE arguments[] = {
|
|
@@ -1182,72 +1445,88 @@ static VALUE gc_tracking_as_ruby_hash(struct thread_context_collector_state *sta
|
|
|
1182
1445
|
return result;
|
|
1183
1446
|
}
|
|
1184
1447
|
|
|
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
1448
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1200
1449
|
// It SHOULD NOT be used for other purposes.
|
|
1201
1450
|
//
|
|
1202
|
-
// Returns the whole contents of the per_thread_context structs being tracked.
|
|
1451
|
+
// Returns the whole contents of the per_thread_context structs being tracked, by iterating all live threads.
|
|
1203
1452
|
static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1204
|
-
|
|
1205
|
-
TypedData_Get_Struct(collector_instance,
|
|
1453
|
+
thread_context_collector_state *state;
|
|
1454
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1206
1455
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1456
|
+
VALUE result = rb_hash_new();
|
|
1457
|
+
VALUE threads = thread_list(state);
|
|
1458
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
1459
|
+
for (long i = 0; i < thread_count; i++) {
|
|
1460
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
1461
|
+
ENFORCE_THREAD(thread);
|
|
1209
1462
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1463
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1464
|
+
if (thread_context != NULL) {
|
|
1465
|
+
rb_hash_aset(result, thread, per_thread_context_to_ruby_hash(thread_context));
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
return result;
|
|
1469
|
+
}
|
|
1213
1470
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1471
|
+
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, per_thread_context *thread_context) {
|
|
1472
|
+
// We don't expect to be sampling a thread (and thus updating these counters) while Ruby is doing GC (between
|
|
1473
|
+
// `thread_context_collector_on_gc_start` and `thread_context_collector_on_gc_finish`)
|
|
1474
|
+
if (thread_context->gc_tracking.cpu_time_at_start_ns != INVALID_TIME) {
|
|
1475
|
+
raise_error(
|
|
1476
|
+
rb_eRuntimeError,
|
|
1477
|
+
"BUG: Unexpected sample during GC (thread_id=%s, gc_tracking.cpu_time_at_start_ns=%ld, "
|
|
1478
|
+
"gc_tracking.wall_time_at_start_ns=%ld, monotonic_wall_time_now_ns=%ld)",
|
|
1479
|
+
thread_context->thread_id,
|
|
1480
|
+
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
1481
|
+
thread_context->gc_tracking.wall_time_at_start_ns,
|
|
1482
|
+
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1216
1485
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1486
|
+
long elapsed_time_ns = current_time_ns - *time_at_previous_sample_ns; // Capture all time since previous sample
|
|
1487
|
+
*time_at_previous_sample_ns = current_time_ns;
|
|
1219
1488
|
|
|
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
|
-
}
|
|
1489
|
+
return elapsed_time_ns;
|
|
1490
|
+
}
|
|
1225
1491
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1492
|
+
static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns) {
|
|
1493
|
+
// A previous `cpu_time_now_ns` may have failed to read the clock and thus invalidated our baseline; in that case we
|
|
1494
|
+
// use the current time, so this sample gets no cpu-time and the next one gets an accurate delta.
|
|
1495
|
+
// Note wall-time never needs this: it is always seeded with a real value in `initialize_context`.
|
|
1496
|
+
if (thread_context->cpu_time_at_previous_sample_ns == INVALID_TIME) {
|
|
1497
|
+
thread_context->cpu_time_at_previous_sample_ns = current_cpu_time_ns;
|
|
1231
1498
|
}
|
|
1232
1499
|
|
|
1500
|
+
long elapsed_time_ns = update_time_since_previous_sample(
|
|
1501
|
+
&thread_context->cpu_time_at_previous_sample_ns,
|
|
1502
|
+
current_cpu_time_ns,
|
|
1503
|
+
thread_context
|
|
1504
|
+
);
|
|
1505
|
+
|
|
1506
|
+
// We don't expect cpu-time to go backwards, so let's flag this as a bug
|
|
1233
1507
|
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
|
-
}
|
|
1508
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected CPU time going backwards between samples");
|
|
1244
1509
|
}
|
|
1245
1510
|
|
|
1246
1511
|
return elapsed_time_ns;
|
|
1247
1512
|
}
|
|
1248
1513
|
|
|
1514
|
+
static long update_wall_time_since_previous_sample(per_thread_context *thread_context, long current_wall_time_ns) {
|
|
1515
|
+
long elapsed_time_ns = update_time_since_previous_sample(
|
|
1516
|
+
&thread_context->wall_time_at_previous_sample_ns,
|
|
1517
|
+
current_wall_time_ns,
|
|
1518
|
+
thread_context
|
|
1519
|
+
);
|
|
1520
|
+
|
|
1521
|
+
// Wall-time can actually go backwards (e.g. when the system clock gets set) so we can't assume time going backwards
|
|
1522
|
+
// was a bug.
|
|
1523
|
+
// @ivoanjo: I've also observed time going backwards spuriously on macOS, see discussion on
|
|
1524
|
+
// https://github.com/DataDog/dd-trace-rb/pull/2336.
|
|
1525
|
+
return long_max_of(elapsed_time_ns, 0);
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1249
1528
|
// Safety: This function is assumed never to raise exceptions by callers
|
|
1250
|
-
static long cpu_time_now_ns(
|
|
1529
|
+
static long cpu_time_now_ns(per_thread_context *thread_context) {
|
|
1251
1530
|
thread_cpu_time cpu_time = thread_cpu_time_for(thread_context->thread_cpu_time_id);
|
|
1252
1531
|
|
|
1253
1532
|
if (!cpu_time.valid) {
|
|
@@ -1278,31 +1557,45 @@ static long thread_id_for(VALUE thread) {
|
|
|
1278
1557
|
}
|
|
1279
1558
|
|
|
1280
1559
|
VALUE enforce_thread_context_collector_instance(VALUE object) {
|
|
1281
|
-
|
|
1560
|
+
ENFORCE_TYPED_DATA(object, &thread_context_collector_typed_data);
|
|
1282
1561
|
return object;
|
|
1283
1562
|
}
|
|
1284
1563
|
|
|
1564
|
+
// Commit any pending heap allocation recordings.
|
|
1565
|
+
// Recording an allocation only puts it on a pending list, because taking the weak reference the heap profiler needs
|
|
1566
|
+
// can't be done from inside the NEWOBJ tracepoint -- see `heap_recorder_commit_recordings_may_lose_gvl`.
|
|
1567
|
+
void thread_context_collector_commit_heap_recordings_may_lose_gvl(VALUE self_instance) {
|
|
1568
|
+
thread_context_collector_state *state;
|
|
1569
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1570
|
+
|
|
1571
|
+
recorder_commit_heap_recordings_may_lose_gvl(state->recorder_instance);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1285
1574
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1286
1575
|
// It SHOULD NOT be used for other purposes.
|
|
1287
1576
|
static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1288
|
-
|
|
1289
|
-
TypedData_Get_Struct(collector_instance,
|
|
1577
|
+
thread_context_collector_state *state;
|
|
1578
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1290
1579
|
|
|
1291
|
-
return
|
|
1580
|
+
return stats_to_ruby_hash(state, rb_hash_new());
|
|
1292
1581
|
}
|
|
1293
1582
|
|
|
1294
1583
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1295
1584
|
// It SHOULD NOT be used for other purposes.
|
|
1296
1585
|
static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1297
|
-
|
|
1298
|
-
TypedData_Get_Struct(collector_instance,
|
|
1586
|
+
thread_context_collector_state *state;
|
|
1587
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1299
1588
|
|
|
1300
1589
|
return gc_tracking_as_ruby_hash(state);
|
|
1301
1590
|
}
|
|
1302
1591
|
|
|
1303
1592
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
1304
|
-
static void trace_identifiers_for(
|
|
1305
|
-
|
|
1593
|
+
static void trace_identifiers_for(
|
|
1594
|
+
thread_context_collector_state *state,
|
|
1595
|
+
VALUE thread,
|
|
1596
|
+
trace_identifiers *trace_identifiers_result
|
|
1597
|
+
) {
|
|
1598
|
+
if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
|
|
1306
1599
|
if (state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY) return;
|
|
1307
1600
|
|
|
1308
1601
|
VALUE current_context = rb_thread_local_aref(thread, state->tracer_context_key);
|
|
@@ -1320,7 +1613,9 @@ static void trace_identifiers_for(struct thread_context_collector_state *state,
|
|
|
1320
1613
|
|
|
1321
1614
|
VALUE numeric_span_id = Qnil;
|
|
1322
1615
|
|
|
1323
|
-
if (otel_values != Qnil)
|
|
1616
|
+
if (otel_values != Qnil) {
|
|
1617
|
+
ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values);
|
|
1618
|
+
}
|
|
1324
1619
|
|
|
1325
1620
|
if (root_span == Qnil || (active_span == Qnil && numeric_span_id == Qnil)) return;
|
|
1326
1621
|
|
|
@@ -1376,34 +1671,62 @@ static bool should_collect_resource(VALUE root_span) {
|
|
|
1376
1671
|
//
|
|
1377
1672
|
// Assumption: This method gets called BEFORE restarting profiling -- e.g. there are no components attempting to
|
|
1378
1673
|
// trigger samples at the same time.
|
|
1674
|
+
//
|
|
1675
|
+
// Note that tests call this method directly in the same process without forking,
|
|
1676
|
+
// and in such a case non-current Threads keep running.
|
|
1379
1677
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
1380
|
-
|
|
1381
|
-
TypedData_Get_Struct(collector_instance,
|
|
1382
|
-
|
|
1383
|
-
// Release all context memory before clearing the existing context
|
|
1384
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
|
1385
|
-
|
|
1386
|
-
st_clear(state->hash_map_per_thread_context);
|
|
1678
|
+
thread_context_collector_state *state;
|
|
1679
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1387
1680
|
|
|
1388
1681
|
state->stats = (struct stats) {}; // Resets all stats back to zero
|
|
1389
1682
|
|
|
1683
|
+
// No need to clean-up the per-thread context because the CpuAndWallTimeWorker always cleans
|
|
1684
|
+
// it up unconditionally on every start/restart and that includes after a fork.
|
|
1685
|
+
|
|
1390
1686
|
rb_funcall(state->recorder_instance, rb_intern("reset_after_fork"), 0);
|
|
1391
1687
|
|
|
1392
1688
|
return Qtrue;
|
|
1393
1689
|
}
|
|
1394
1690
|
|
|
1395
|
-
static VALUE thread_list(
|
|
1691
|
+
static VALUE thread_list(thread_context_collector_state *state) {
|
|
1396
1692
|
VALUE result = state->thread_list_buffer;
|
|
1397
1693
|
rb_ary_clear(result);
|
|
1398
1694
|
ddtrace_thread_list(result);
|
|
1399
1695
|
return result;
|
|
1400
1696
|
}
|
|
1401
1697
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1698
|
+
// Inside a signal handler, we don't want to do the whole work of recording a sample, but we only record the stack of
|
|
1699
|
+
// the current thread.
|
|
1700
|
+
//
|
|
1701
|
+
// Assumptions for this function are same as for `thread_context_collector_sample` except that this function is
|
|
1702
|
+
// expected to be called from a signal handler and to be async-signal-safe, and `during_sample` MUST be unset.
|
|
1703
|
+
//
|
|
1704
|
+
// Also, no allocation (Ruby or malloc) can happen.
|
|
1705
|
+
bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
1406
1706
|
VALUE current_thread = rb_thread_current();
|
|
1707
|
+
per_thread_context *thread_context = get_per_thread_context(current_thread);
|
|
1708
|
+
if (thread_context == NULL) return false;
|
|
1709
|
+
|
|
1710
|
+
return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// Called from the sampling signal handler; must not allocate or inspect the Ruby stack during GC.
|
|
1714
|
+
void thread_context_collector_request_prepare_on_gc_finish(void) {
|
|
1715
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
1716
|
+
if (thread_context != NULL) thread_context->prepare_sample_on_gc_finish = true;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
|
|
1720
|
+
// Ruby heap nor release the GVL (https://github.com/DataDog/dd-trace-rb/pull/4240).
|
|
1721
|
+
//
|
|
1722
|
+
// Returns true if `thread_context_collector_commit_heap_recordings_may_lose_gvl` needs to be called (to do work
|
|
1723
|
+
// that can't be done from inside the tracepoint, such as allocate new objects), and false if it doesn't
|
|
1724
|
+
//
|
|
1725
|
+
// Assumption 1: The callers must ensure thread_context is non-NULL.
|
|
1726
|
+
// Assumption 2: When called while the profiler is active, `during_sample` MUST be set.
|
|
1727
|
+
bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object) {
|
|
1728
|
+
thread_context_collector_state *state;
|
|
1729
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1407
1730
|
|
|
1408
1731
|
enum ruby_value_type type = rb_type(new_object);
|
|
1409
1732
|
|
|
@@ -1412,93 +1735,101 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
|
1412
1735
|
|
|
1413
1736
|
// Since this is stack allocated, be careful about moving it
|
|
1414
1737
|
ddog_CharSlice class_name;
|
|
1415
|
-
ddog_CharSlice *optional_class_name = NULL;
|
|
1416
1738
|
char imemo_type[100];
|
|
1417
1739
|
|
|
1418
|
-
if (
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
const char *name = rb_class2name(klass);
|
|
1451
|
-
size_t name_length = name != NULL ? strlen(name) : 0;
|
|
1452
|
-
|
|
1453
|
-
if (name_length > 0) {
|
|
1454
|
-
class_name = (ddog_CharSlice) {.ptr = name, .len = name_length};
|
|
1455
|
-
} else {
|
|
1456
|
-
// @ivoanjo: I'm not sure this can ever happen, but just-in-case
|
|
1457
|
-
class_name = ruby_value_type_to_class_name(type);
|
|
1458
|
-
}
|
|
1740
|
+
if (
|
|
1741
|
+
type == RUBY_T_OBJECT ||
|
|
1742
|
+
type == RUBY_T_CLASS ||
|
|
1743
|
+
type == RUBY_T_MODULE ||
|
|
1744
|
+
type == RUBY_T_FLOAT ||
|
|
1745
|
+
type == RUBY_T_STRING ||
|
|
1746
|
+
type == RUBY_T_REGEXP ||
|
|
1747
|
+
type == RUBY_T_ARRAY ||
|
|
1748
|
+
type == RUBY_T_HASH ||
|
|
1749
|
+
type == RUBY_T_STRUCT ||
|
|
1750
|
+
type == RUBY_T_BIGNUM ||
|
|
1751
|
+
type == RUBY_T_FILE ||
|
|
1752
|
+
type == RUBY_T_DATA ||
|
|
1753
|
+
type == RUBY_T_MATCH ||
|
|
1754
|
+
type == RUBY_T_COMPLEX ||
|
|
1755
|
+
type == RUBY_T_RATIONAL ||
|
|
1756
|
+
type == RUBY_T_NIL ||
|
|
1757
|
+
type == RUBY_T_TRUE ||
|
|
1758
|
+
type == RUBY_T_FALSE ||
|
|
1759
|
+
type == RUBY_T_SYMBOL ||
|
|
1760
|
+
type == RUBY_T_FIXNUM
|
|
1761
|
+
) {
|
|
1762
|
+
VALUE klass = rb_class_of(new_object);
|
|
1763
|
+
|
|
1764
|
+
// Ruby sometimes plays a bit fast and loose with some of its internal objects, e.g.
|
|
1765
|
+
// `rb_str_tmp_frozen_acquire` allocates a string with no class (klass=0).
|
|
1766
|
+
// Thus, we need to make sure there's actually a class before getting its name.
|
|
1767
|
+
|
|
1768
|
+
if (klass != 0) {
|
|
1769
|
+
VALUE name = ddtrace_alloc_free_rb_mod_name(klass);
|
|
1770
|
+
if (name != Qnil) {
|
|
1771
|
+
class_name = char_slice_from_ruby_string(name);
|
|
1459
1772
|
} else {
|
|
1460
|
-
// Fallback for objects with no class
|
|
1461
1773
|
class_name = ruby_value_type_to_class_name(type);
|
|
1462
1774
|
}
|
|
1463
|
-
} else if (type == RUBY_T_IMEMO) {
|
|
1464
|
-
const char *imemo_string = imemo_kind(new_object);
|
|
1465
|
-
if (imemo_string != NULL) {
|
|
1466
|
-
snprintf(imemo_type, 100, "(VM Internal, T_IMEMO, %s)", imemo_string);
|
|
1467
|
-
class_name = (ddog_CharSlice) {.ptr = imemo_type, .len = strlen(imemo_type)};
|
|
1468
|
-
} else { // Ruby < 3
|
|
1469
|
-
class_name = DDOG_CHARSLICE_C("(VM Internal, T_IMEMO)");
|
|
1470
|
-
}
|
|
1471
1775
|
} else {
|
|
1472
|
-
|
|
1776
|
+
// Fallback for objects with no class. Objects with no class are a way for the Ruby VM to mark them
|
|
1777
|
+
// as internal objects; see rb_objspace_internal_object_p for details.
|
|
1778
|
+
class_name = ruby_value_type_to_class_name(type);
|
|
1779
|
+
}
|
|
1780
|
+
} else if (type == RUBY_T_IMEMO) {
|
|
1781
|
+
const char *imemo_string = imemo_kind(new_object);
|
|
1782
|
+
if (imemo_string != NULL) {
|
|
1783
|
+
snprintf(imemo_type, 100, "(VM Internal, T_IMEMO, %s)", imemo_string);
|
|
1784
|
+
class_name = (ddog_CharSlice) {.ptr = imemo_type, .len = strlen(imemo_type)};
|
|
1785
|
+
} else { // Ruby < 3
|
|
1786
|
+
class_name = DDOG_CHARSLICE_C("(VM Internal, T_IMEMO)");
|
|
1473
1787
|
}
|
|
1788
|
+
} else {
|
|
1789
|
+
class_name = ruby_vm_type; // For other weird internal things we just use the VM type
|
|
1474
1790
|
}
|
|
1475
1791
|
|
|
1476
|
-
|
|
1792
|
+
VALUE current_thread = rb_thread_current();
|
|
1477
1793
|
|
|
1478
|
-
|
|
1794
|
+
heap_sample_values heap_sample = {.new_object = new_object, .alloc_class = class_name};
|
|
1479
1795
|
|
|
1480
1796
|
trigger_sample_for_thread(
|
|
1481
1797
|
state,
|
|
1482
|
-
|
|
1483
|
-
/* stack_from_thread: */ current_thread,
|
|
1798
|
+
current_thread,
|
|
1484
1799
|
thread_context,
|
|
1485
|
-
|
|
1486
|
-
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
|
|
1800
|
+
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = &heap_sample},
|
|
1487
1801
|
INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
|
|
1488
1802
|
&ruby_vm_type,
|
|
1489
|
-
|
|
1490
|
-
false
|
|
1803
|
+
&class_name,
|
|
1804
|
+
/* is_gvl_waiting_state: */ false
|
|
1491
1805
|
);
|
|
1806
|
+
|
|
1807
|
+
return heap_sample.out_needs_commit;
|
|
1492
1808
|
}
|
|
1493
1809
|
|
|
1494
1810
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1495
1811
|
// It SHOULD NOT be used for other purposes.
|
|
1496
1812
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object) {
|
|
1497
|
-
|
|
1498
|
-
|
|
1813
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
1814
|
+
if (thread_context == NULL) {
|
|
1815
|
+
rb_raise(rb_eRuntimeError, "Missing per_thread_context for current thread in _native_sample_allocation");
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
debug_enter_unsafe_context();
|
|
1819
|
+
|
|
1820
|
+
bool needs_commit = thread_context_collector_sample_allocation(collector_instance, thread_context, NUM2UINT(sample_weight), new_object);
|
|
1821
|
+
|
|
1822
|
+
debug_leave_unsafe_context();
|
|
1823
|
+
|
|
1824
|
+
// We could instead choose to automatically trigger the after allocation here; yet, it seems kinda nice to keep it manual for
|
|
1825
|
+
// the tests so we can pull on each lever separately and observe "the sausage being made" in steps
|
|
1826
|
+
return needs_commit ? Qtrue : Qfalse;
|
|
1499
1827
|
}
|
|
1500
1828
|
|
|
1501
|
-
static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
|
|
1829
|
+
static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
|
|
1830
|
+
rb_thread_sleep(INT_MAX);
|
|
1831
|
+
return Qnil;
|
|
1832
|
+
}
|
|
1502
1833
|
|
|
1503
1834
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1504
1835
|
// It SHOULD NOT be used for other purposes.
|
|
@@ -1542,23 +1873,61 @@ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
|
|
|
1542
1873
|
return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
|
|
1543
1874
|
}
|
|
1544
1875
|
|
|
1545
|
-
static VALUE
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
// gets refactored, we should not fall on our face)
|
|
1549
|
-
VALUE span_key = rb_protect(read_otel_current_span_key_const, Qnil, NULL);
|
|
1876
|
+
static VALUE otel_current_span_key_not_found(DDTRACE_UNUSED VALUE _unused, DDTRACE_UNUSED VALUE _exception) {
|
|
1877
|
+
return Qnil;
|
|
1878
|
+
}
|
|
1550
1879
|
|
|
1551
|
-
|
|
1552
|
-
|
|
1880
|
+
static VALUE get_otel_current_span_key(thread_context_collector_state *state) {
|
|
1881
|
+
if (state->otel_current_span_key == OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED) {
|
|
1882
|
+
// Extracting this needs calling into Ruby code, which we don't want to do in the middle of a sample.
|
|
1883
|
+
// So instead we signal that we want this, and let it be resolved separately.
|
|
1884
|
+
state->otel_current_span_key = OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED;
|
|
1885
|
+
return Qnil;
|
|
1553
1886
|
}
|
|
1554
1887
|
|
|
1888
|
+
// We asked for it, but it hasn't been extracted yet
|
|
1889
|
+
if (is_otel_current_span_key_needed(state)) return Qnil;
|
|
1890
|
+
|
|
1555
1891
|
return state->otel_current_span_key;
|
|
1556
1892
|
}
|
|
1557
1893
|
|
|
1894
|
+
static bool is_otel_current_span_key_needed(thread_context_collector_state *state) {
|
|
1895
|
+
return state->otel_current_span_key == OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
// Extracts `otel_current_span_key`, if a sample asked for it (see `get_otel_current_span_key`).
|
|
1899
|
+
// This is deliberately not done during sampling because we can lose the GVL.
|
|
1900
|
+
//
|
|
1901
|
+
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
1902
|
+
void thread_context_collector_resolve_otel_span_key_may_lose_gvl(VALUE self_instance) {
|
|
1903
|
+
thread_context_collector_state *state;
|
|
1904
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1905
|
+
|
|
1906
|
+
if (!is_otel_current_span_key_needed(state)) return;
|
|
1907
|
+
|
|
1908
|
+
// If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
|
|
1909
|
+
// gets refactored, we should not fall on our face).
|
|
1910
|
+
//
|
|
1911
|
+
// Note that we use `rb_rescue2` and not `rb_protect` so that we only swallow exceptions: anything else that can
|
|
1912
|
+
// unwind through here (e.g. a `Thread#kill`) is not ours to swallow. `rb_rescue2` also takes care of restoring
|
|
1913
|
+
// any previously-pending exception for us.
|
|
1914
|
+
VALUE span_key = rb_rescue2(
|
|
1915
|
+
read_otel_current_span_key_const,
|
|
1916
|
+
Qnil,
|
|
1917
|
+
otel_current_span_key_not_found,
|
|
1918
|
+
Qnil,
|
|
1919
|
+
rb_eException, // rb_eException is the base class of all Ruby exceptions
|
|
1920
|
+
0 // Required by API to be the last argument
|
|
1921
|
+
);
|
|
1922
|
+
|
|
1923
|
+
// Note that this gets set to Qnil if we failed to extract the correct value, and thus we won't try to extract it again
|
|
1924
|
+
state->otel_current_span_key = span_key;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1558
1927
|
// This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
|
|
1559
1928
|
// differently, and this codepath handles it.
|
|
1560
1929
|
static void ddtrace_otel_trace_identifiers_for(
|
|
1561
|
-
|
|
1930
|
+
thread_context_collector_state *state,
|
|
1562
1931
|
VALUE *active_trace,
|
|
1563
1932
|
VALUE *root_span,
|
|
1564
1933
|
VALUE *numeric_span_id,
|
|
@@ -1582,7 +1951,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
1582
1951
|
// trace and span representing it. Each ddtrace trace is then connected to the previous otel span, forming a linked
|
|
1583
1952
|
// list. The local root span is going to be the trace/span we find at the end of this linked list.
|
|
1584
1953
|
while (otel_values != Qnil) {
|
|
1585
|
-
VALUE otel_span =
|
|
1954
|
+
VALUE otel_span = safely_lookup_hash_without_going_into_ruby_code(otel_values, otel_current_span_key);
|
|
1586
1955
|
if (otel_span == Qnil) break;
|
|
1587
1956
|
VALUE next_trace = rb_ivar_get(otel_span, at_datadog_trace_id);
|
|
1588
1957
|
if (next_trace == Qnil) break;
|
|
@@ -1600,9 +1969,10 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
1600
1969
|
*numeric_span_id = resolved_numeric_span_id;
|
|
1601
1970
|
}
|
|
1602
1971
|
|
|
1972
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
1603
1973
|
void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples) {
|
|
1604
|
-
|
|
1605
|
-
TypedData_Get_Struct(self_instance,
|
|
1974
|
+
thread_context_collector_state *state;
|
|
1975
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1606
1976
|
|
|
1607
1977
|
ddog_prof_Label labels[] = {
|
|
1608
1978
|
// Providing .num = 0 should not be needed but the tracer-2.7 docker image ships a buggy gcc that complains about this
|
|
@@ -1625,10 +1995,51 @@ void thread_context_collector_sample_skipped_allocation_samples(VALUE self_insta
|
|
|
1625
1995
|
}
|
|
1626
1996
|
|
|
1627
1997
|
static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples) {
|
|
1998
|
+
debug_enter_unsafe_context();
|
|
1999
|
+
|
|
1628
2000
|
thread_context_collector_sample_skipped_allocation_samples(collector_instance, NUM2UINT(skipped_samples));
|
|
2001
|
+
|
|
2002
|
+
debug_leave_unsafe_context();
|
|
2003
|
+
|
|
1629
2004
|
return Qtrue;
|
|
1630
2005
|
}
|
|
1631
2006
|
|
|
2007
|
+
#ifndef NO_CURRENT_FIBER_FOR // Ruby 3.1+
|
|
2008
|
+
static VALUE otel_context_storage_for(thread_context_collector_state *state, VALUE thread) {
|
|
2009
|
+
if (state->otel_context_source == OTEL_CONTEXT_SOURCE_FIBER_IVAR) { // otel-api 1.5+
|
|
2010
|
+
VALUE current_fiber = current_fiber_for(thread);
|
|
2011
|
+
return current_fiber == Qnil ? Qnil : rb_ivar_get(current_fiber, otel_fiber_context_storage_id /* @opentelemetry_context */);
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
if (state->otel_context_source == OTEL_CONTEXT_SOURCE_FIBER_LOCAL) { // otel-api < 1.5
|
|
2015
|
+
return rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
// If we got here, it means we never observed a context being set. Let's probe which one to use.
|
|
2019
|
+
VALUE current_fiber = current_fiber_for(thread);
|
|
2020
|
+
if (current_fiber != Qnil) {
|
|
2021
|
+
VALUE context_storage = rb_ivar_get(current_fiber, otel_fiber_context_storage_id /* @opentelemetry_context */);
|
|
2022
|
+
if (context_storage != Qnil) {
|
|
2023
|
+
state->otel_context_source = OTEL_CONTEXT_SOURCE_FIBER_IVAR; // Remember for next time
|
|
2024
|
+
return context_storage;
|
|
2025
|
+
}
|
|
2026
|
+
} else {
|
|
2027
|
+
VALUE context_storage = rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
|
|
2028
|
+
if (context_storage != Qnil) {
|
|
2029
|
+
state->otel_context_source = OTEL_CONTEXT_SOURCE_FIBER_LOCAL; // Remember for next time
|
|
2030
|
+
return context_storage;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
// There's no context storage attached to the current thread
|
|
2035
|
+
return Qnil;
|
|
2036
|
+
}
|
|
2037
|
+
#else
|
|
2038
|
+
static inline VALUE otel_context_storage_for(DDTRACE_UNUSED thread_context_collector_state *state, VALUE thread) {
|
|
2039
|
+
return rb_thread_local_aref(thread, otel_context_storage_id /* __opentelemetry_context_storage__ */);
|
|
2040
|
+
}
|
|
2041
|
+
#endif
|
|
2042
|
+
|
|
1632
2043
|
// This method differs from trace_identifiers_for/ddtrace_otel_trace_identifiers_for to support the situation where
|
|
1633
2044
|
// the opentelemetry ruby library is being used for tracing AND the ddtrace tracing bits are not involved at all.
|
|
1634
2045
|
//
|
|
@@ -1651,11 +2062,11 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
|
|
|
1651
2062
|
// root span id.
|
|
1652
2063
|
// This matches the semantics of how ddtrace tracing creates a TraceOperation and assigns a local root span to it.
|
|
1653
2064
|
static void otel_without_ddtrace_trace_identifiers_for(
|
|
1654
|
-
|
|
2065
|
+
thread_context_collector_state *state,
|
|
1655
2066
|
VALUE thread,
|
|
1656
|
-
|
|
2067
|
+
trace_identifiers *trace_identifiers_result
|
|
1657
2068
|
) {
|
|
1658
|
-
VALUE context_storage =
|
|
2069
|
+
VALUE context_storage = otel_context_storage_for(state, thread);
|
|
1659
2070
|
|
|
1660
2071
|
// If it exists, context_storage is expected to be an Array[OpenTelemetry::Context]
|
|
1661
2072
|
if (context_storage == Qnil || !RB_TYPE_P(context_storage, T_ARRAY)) return;
|
|
@@ -1663,17 +2074,17 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
|
1663
2074
|
VALUE otel_current_span_key = get_otel_current_span_key(state);
|
|
1664
2075
|
if (otel_current_span_key == Qnil) return;
|
|
1665
2076
|
|
|
1666
|
-
|
|
2077
|
+
long active_context_index = RARRAY_LEN(context_storage) - 1;
|
|
1667
2078
|
if (active_context_index < 0) return;
|
|
1668
2079
|
|
|
1669
|
-
|
|
2080
|
+
otel_span active_span = otel_span_from(rb_ary_entry(context_storage, active_context_index), otel_current_span_key);
|
|
1670
2081
|
if (active_span.span == Qnil) return;
|
|
1671
2082
|
|
|
1672
|
-
|
|
2083
|
+
otel_span local_root_span = active_span;
|
|
1673
2084
|
|
|
1674
2085
|
// Now find the oldest span starting from the active span that still has the same trace id as the active span
|
|
1675
|
-
for (
|
|
1676
|
-
|
|
2086
|
+
for (long i = active_context_index - 1; i >= 0; i--) {
|
|
2087
|
+
otel_span checking_span = otel_span_from(rb_ary_entry(context_storage, i), otel_current_span_key);
|
|
1677
2088
|
if (checking_span.span == Qnil) return;
|
|
1678
2089
|
|
|
1679
2090
|
if (rb_str_equal(active_span.trace_id, checking_span.trace_id) == Qfalse) break;
|
|
@@ -1693,7 +2104,7 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
|
1693
2104
|
|
|
1694
2105
|
VALUE root_span_type = rb_ivar_get(local_root_span.span, at_kind_id /* @kind */);
|
|
1695
2106
|
// We filter out spans that don't have `kind: :server`
|
|
1696
|
-
if (root_span_type == Qnil || !RB_TYPE_P(root_span_type, T_SYMBOL) || SYM2ID(root_span_type) != server_id) return;
|
|
2107
|
+
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;
|
|
1697
2108
|
|
|
1698
2109
|
VALUE trace_resource = rb_ivar_get(local_root_span.span, at_name_id /* @name */);
|
|
1699
2110
|
if (!RB_TYPE_P(trace_resource, T_STRING)) return;
|
|
@@ -1701,8 +2112,8 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
|
1701
2112
|
trace_identifiers_result->trace_endpoint = trace_resource;
|
|
1702
2113
|
}
|
|
1703
2114
|
|
|
1704
|
-
static
|
|
1705
|
-
|
|
2115
|
+
static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key) {
|
|
2116
|
+
otel_span failed = {.span = Qnil, .span_id = Qnil, .trace_id = Qnil};
|
|
1706
2117
|
|
|
1707
2118
|
if (otel_context == Qnil) return failed;
|
|
1708
2119
|
|
|
@@ -1710,7 +2121,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp
|
|
|
1710
2121
|
if (context_entries == Qnil || !RB_TYPE_P(context_entries, T_HASH)) return failed;
|
|
1711
2122
|
|
|
1712
2123
|
// If it exists, context_entries is expected to be a Hash[OpenTelemetry::Context::Key, OpenTelemetry::Trace::Span]
|
|
1713
|
-
VALUE span =
|
|
2124
|
+
VALUE span = safely_lookup_hash_without_going_into_ruby_code(context_entries, otel_current_span_key);
|
|
1714
2125
|
if (span == Qnil) return failed;
|
|
1715
2126
|
|
|
1716
2127
|
// If it exists, span_context is expected to be a OpenTelemetry::Trace::SpanContext (don't confuse it with OpenTelemetry::Context)
|
|
@@ -1721,7 +2132,7 @@ static struct otel_span otel_span_from(VALUE otel_context, VALUE otel_current_sp
|
|
|
1721
2132
|
VALUE trace_id = rb_ivar_get(span_context, at_trace_id_id /* @trace_id */);
|
|
1722
2133
|
if (span_id == Qnil || trace_id == Qnil || !RB_TYPE_P(span_id, T_STRING) || !RB_TYPE_P(trace_id, T_STRING)) return failed;
|
|
1723
2134
|
|
|
1724
|
-
return (
|
|
2135
|
+
return (otel_span) {.span = span, .span_id = span_id, .trace_id = trace_id};
|
|
1725
2136
|
}
|
|
1726
2137
|
|
|
1727
2138
|
// Otel span ids are represented as a big-endian 8-byte string
|
|
@@ -1741,36 +2152,149 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1741
2152
|
((uint64_t)span_bytes[7]);
|
|
1742
2153
|
}
|
|
1743
2154
|
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
2155
|
+
void thread_context_collector_stats(VALUE self_instance, VALUE stats_hash) {
|
|
2156
|
+
thread_context_collector_state *state;
|
|
2157
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2158
|
+
stats_to_ruby_hash(state, stats_hash);
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
2162
|
+
thread_context_collector_state *state;
|
|
2163
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2164
|
+
state->stats = (struct stats) {};
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
static void mark_thread_as_profiler_internal(per_thread_context *ctx) {
|
|
2168
|
+
ctx->is_profiler_internal_thread = true;
|
|
2169
|
+
}
|
|
1757
2170
|
|
|
1758
|
-
|
|
1759
|
-
|
|
2171
|
+
void thread_context_collector_profiler_internal_thread_started(void) {
|
|
2172
|
+
per_thread_context *ctx = get_or_create_context_for(rb_thread_current());
|
|
2173
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2177
|
+
per_thread_context *ctx = get_or_create_context_for(thread);
|
|
2178
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2179
|
+
return Qnil;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
// Called via rb_ensure when a profiler-internal thread (worker or idle helper) is about to exit.
|
|
2183
|
+
// Records a final sample so the thread's accumulated cpu/wall time since the last on_serialize
|
|
2184
|
+
// flush is not lost. on_serialize (below) also flushes profiler-internal threads during periodic
|
|
2185
|
+
// serialization, but it can't help at shutdown: by the time the final serialize runs, these
|
|
2186
|
+
// threads are already dead and absent from thread_list.
|
|
2187
|
+
//
|
|
2188
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2189
|
+
VALUE thread_context_collector_profiler_internal_thread_done(VALUE self_instance) {
|
|
2190
|
+
thread_context_collector_state *state;
|
|
2191
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1760
2192
|
|
|
1761
|
-
|
|
2193
|
+
VALUE current_thread = rb_thread_current();
|
|
2194
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
2195
|
+
if (!thread_context->is_profiler_internal_thread) {
|
|
2196
|
+
rb_raise(rb_eRuntimeError, "current thread %"PRIsVALUE" is not profiler-internal thread", current_thread);
|
|
1762
2197
|
}
|
|
1763
2198
|
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
2199
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2200
|
+
|
|
2201
|
+
update_metrics_and_sample(
|
|
2202
|
+
state,
|
|
2203
|
+
current_thread,
|
|
2204
|
+
thread_context,
|
|
2205
|
+
current_monotonic_wall_time_ns,
|
|
2206
|
+
true);
|
|
2207
|
+
|
|
2208
|
+
return Qnil;
|
|
2209
|
+
}
|
|
1768
2210
|
|
|
1769
|
-
|
|
1770
|
-
|
|
2211
|
+
// Flushes threads whose last per-tick sample was skipped (either by the SUSPENDED-skip
|
|
2212
|
+
// optimization, or by is_profiler_internal_thread) so their accumulated time is recorded.
|
|
2213
|
+
// Called by the stack recorder at the start of _native_serialize (regular periodic flush).
|
|
2214
|
+
//
|
|
2215
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2216
|
+
void thread_context_collector_on_serialize(VALUE self_instance) {
|
|
2217
|
+
thread_context_collector_state *state;
|
|
2218
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1771
2219
|
|
|
2220
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2221
|
+
VALUE threads = thread_list(state);
|
|
2222
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
2223
|
+
|
|
2224
|
+
for (long i = 0; i < thread_count; i++) {
|
|
2225
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
2226
|
+
ENFORCE_THREAD(thread);
|
|
2227
|
+
|
|
2228
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2229
|
+
|
|
2230
|
+
if (thread_context != NULL && (thread_context->was_skipped_at_last_sample || thread_context->is_profiler_internal_thread)) {
|
|
2231
|
+
update_metrics_and_sample(
|
|
2232
|
+
state,
|
|
2233
|
+
thread,
|
|
2234
|
+
thread_context,
|
|
2235
|
+
current_monotonic_wall_time_ns,
|
|
2236
|
+
true // We need to force_sample=true otherwise this sample would be skipped too
|
|
2237
|
+
);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
void thread_context_collector_on_gvl_released(per_thread_context *thread_context) {
|
|
2243
|
+
thread_context->gvl_state_change_count |= GVL_SUSPENDED;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
void thread_context_collector_on_gvl_waiting(per_thread_context *thread_context) {
|
|
2247
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
|
2248
|
+
if (current_monotonic_wall_time_ns <= 0) return;
|
|
2249
|
+
|
|
2250
|
+
thread_context->gvl_waiting_at = current_monotonic_wall_time_ns;
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2254
|
+
ENFORCE_THREAD(thread);
|
|
2255
|
+
|
|
2256
|
+
debug_enter_unsafe_context();
|
|
2257
|
+
|
|
2258
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2259
|
+
if (thread_context) thread_context_collector_on_gvl_waiting(thread_context);
|
|
2260
|
+
|
|
2261
|
+
debug_leave_unsafe_context();
|
|
2262
|
+
|
|
2263
|
+
return Qnil;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2267
|
+
ENFORCE_THREAD(thread);
|
|
2268
|
+
|
|
2269
|
+
debug_enter_unsafe_context();
|
|
2270
|
+
|
|
2271
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2272
|
+
if (thread_context) thread_context_collector_on_gvl_released(thread_context);
|
|
2273
|
+
|
|
2274
|
+
debug_leave_unsafe_context();
|
|
2275
|
+
|
|
2276
|
+
return Qnil;
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
|
2280
|
+
// We must only use async-signal-safe functions here, see notes in the caller
|
|
2281
|
+
//
|
|
2282
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2283
|
+
__attribute__((warn_unused_result))
|
|
2284
|
+
on_gvl_running_result thread_context_collector_on_gvl_running(VALUE thread, per_thread_context *thread_context, uint32_t waiting_for_gvl_threshold_ns) {
|
|
2285
|
+
// Bump the event counter and clears the state bit to "running"
|
|
2286
|
+
uint64_t counter_portion = thread_context->gvl_state_change_count >> 1;
|
|
2287
|
+
thread_context->gvl_state_change_count = ((counter_portion + 1) << 1) | GVL_RUNNING;
|
|
2288
|
+
|
|
2289
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
2290
|
+
// Thread was not waiting on gvl
|
|
2291
|
+
if (gvl_waiting_at == 0) {
|
|
2292
|
+
return (on_gvl_running_result) {.action = ON_GVL_RUNNING_UNKNOWN, .waiting_for_gvl_duration_ns = 0};
|
|
2293
|
+
}
|
|
1772
2294
|
// @ivoanjo: I'm not sure if this can happen -- It means we should've sampled already but haven't gotten the chance yet?
|
|
1773
|
-
if (gvl_waiting_at < 0)
|
|
2295
|
+
if (gvl_waiting_at < 0) {
|
|
2296
|
+
return (on_gvl_running_result) {.action = ON_GVL_RUNNING_SAMPLE, .waiting_for_gvl_duration_ns = 0};
|
|
2297
|
+
}
|
|
1774
2298
|
|
|
1775
2299
|
long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - gvl_waiting_at;
|
|
1776
2300
|
|
|
@@ -1778,20 +2302,32 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1778
2302
|
|
|
1779
2303
|
if (should_sample) {
|
|
1780
2304
|
// We flip the gvl_waiting_at to negative to mark that the thread is now running and no longer waiting
|
|
1781
|
-
|
|
2305
|
+
long gvl_waiting_at_is_now_running = -gvl_waiting_at;
|
|
1782
2306
|
|
|
1783
|
-
|
|
2307
|
+
thread_context->gvl_waiting_at = gvl_waiting_at_is_now_running;
|
|
1784
2308
|
} else {
|
|
1785
|
-
|
|
1786
|
-
|
|
2309
|
+
thread_context->gvl_waiting_at = 0;
|
|
2310
|
+
|
|
2311
|
+
// Even though the GVL wait itself was below threshold, if the thread had skipped samples
|
|
2312
|
+
// (was suspended for a long time without the GVL), we still need to force a sample now.
|
|
2313
|
+
// Otherwise, the accumulated idle wall-time would be reported against whatever stack the
|
|
2314
|
+
// thread runs next, misrepresenting the time spent idle.
|
|
2315
|
+
if (thread_context->was_skipped_at_last_sample) {
|
|
2316
|
+
should_sample = true;
|
|
2317
|
+
}
|
|
1787
2318
|
}
|
|
1788
2319
|
|
|
1789
|
-
|
|
1790
|
-
|
|
2320
|
+
if (should_sample) {
|
|
2321
|
+
// We prepare the sample here because the postponed job might be called some time later,
|
|
2322
|
+
// possibly after some Ruby calls which change the Ruby stack,
|
|
2323
|
+
// and we want to attribute the time acquiring or without the GVL to the correct Ruby stack.
|
|
2324
|
+
prepare_sample_thread(thread, &thread_context->sampling_buffer);
|
|
2325
|
+
}
|
|
1791
2326
|
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
2327
|
+
return (on_gvl_running_result) {
|
|
2328
|
+
.action = should_sample ? ON_GVL_RUNNING_SAMPLE : ON_GVL_RUNNING_DONT_SAMPLE,
|
|
2329
|
+
.waiting_for_gvl_duration_ns = waiting_for_gvl_duration_ns,
|
|
2330
|
+
};
|
|
1795
2331
|
}
|
|
1796
2332
|
|
|
1797
2333
|
// Why does this method need to exist?
|
|
@@ -1811,7 +2347,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1811
2347
|
//
|
|
1812
2348
|
// Stack:
|
|
1813
2349
|
// If the thread starts working without the end of the Waiting for GVL sample, then by the time the thread is sampled
|
|
1814
|
-
// via the regular cpu/wall-time samples mechanism, the stack can be
|
|
2350
|
+
// via the regular cpu/wall-time samples mechanism, the stack can be inaccurate (e.g. does not correctly pinpoint
|
|
1815
2351
|
// where the waiting happened).
|
|
1816
2352
|
//
|
|
1817
2353
|
// Arguably, the last sample after Waiting for GVL ended (when gvl_waiting_at < 0) should always come from this method
|
|
@@ -1820,17 +2356,20 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1820
2356
|
//
|
|
1821
2357
|
// ---
|
|
1822
2358
|
//
|
|
2359
|
+
// Assumption 1: Always called with the GVL, either from a postponed_job or from tests.
|
|
2360
|
+
// Assumption 2: When called while the profiler is active, `during_sample` MUST be set.
|
|
2361
|
+
//
|
|
1823
2362
|
// NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
|
|
1824
2363
|
// exists to enable testing.
|
|
1825
|
-
VALUE
|
|
1826
|
-
|
|
1827
|
-
TypedData_Get_Struct(self_instance,
|
|
2364
|
+
VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns) {
|
|
2365
|
+
thread_context_collector_state *state;
|
|
2366
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1828
2367
|
|
|
1829
|
-
|
|
2368
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
1830
2369
|
|
|
1831
|
-
|
|
2370
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
1832
2371
|
|
|
1833
|
-
if (gvl_waiting_at >= 0) {
|
|
2372
|
+
if (gvl_waiting_at >= 0 && !thread_context->was_skipped_at_last_sample) {
|
|
1834
2373
|
// @ivoanjo: I'm not sure if this can ever happen. This means that we're not on the same thread
|
|
1835
2374
|
// that ran `thread_context_collector_on_gvl_running` and made the decision to sample OR a regular sample was
|
|
1836
2375
|
// triggered ahead of us.
|
|
@@ -1838,46 +2377,30 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1838
2377
|
return Qfalse;
|
|
1839
2378
|
}
|
|
1840
2379
|
|
|
1841
|
-
struct per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
|
|
1842
|
-
|
|
1843
|
-
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may chose to push an
|
|
1844
|
-
// extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
|
|
1845
|
-
// cpu-time of the thread and let `update_metrics_and_sample` decide what to do with it.
|
|
1846
|
-
long cpu_time_for_thread = cpu_time_now_ns(thread_context);
|
|
1847
|
-
|
|
1848
2380
|
// TODO: Should we update the dynamic sampling rate overhead tracking with this sample as well?
|
|
1849
2381
|
|
|
1850
2382
|
update_metrics_and_sample(
|
|
1851
2383
|
state,
|
|
1852
|
-
|
|
1853
|
-
/* stack_from_thread: */ current_thread,
|
|
2384
|
+
current_thread,
|
|
1854
2385
|
thread_context,
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
1858
|
-
);
|
|
1859
|
-
|
|
1860
|
-
return Qtrue; // To allow this to be called from rb_rescue2
|
|
1861
|
-
}
|
|
2386
|
+
current_monotonic_wall_time_ns,
|
|
2387
|
+
false);
|
|
1862
2388
|
|
|
1863
|
-
|
|
1864
|
-
return thread_context_collector_sample_after_gvl_running_with_thread(self_instance, rb_thread_current());
|
|
2389
|
+
return Qtrue;
|
|
1865
2390
|
}
|
|
1866
2391
|
|
|
1867
2392
|
// This method is intended to be called from update_metrics_and_sample. It exists to handle extra sampling steps we
|
|
1868
2393
|
// need to take when sampling cpu/wall-time for a thread that's in the "Waiting for GVL" state.
|
|
1869
2394
|
__attribute__((warn_unused_result))
|
|
1870
2395
|
static bool handle_gvl_waiting(
|
|
1871
|
-
|
|
2396
|
+
thread_context_collector_state *state,
|
|
1872
2397
|
VALUE thread_being_sampled,
|
|
1873
|
-
|
|
1874
|
-
struct per_thread_context *thread_context,
|
|
1875
|
-
sampling_buffer* sampling_buffer,
|
|
2398
|
+
per_thread_context *thread_context,
|
|
1876
2399
|
long current_cpu_time_ns
|
|
1877
2400
|
) {
|
|
1878
|
-
|
|
2401
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
1879
2402
|
|
|
1880
|
-
bool is_gvl_waiting_state = gvl_waiting_at != 0
|
|
2403
|
+
bool is_gvl_waiting_state = gvl_waiting_at != 0;
|
|
1881
2404
|
|
|
1882
2405
|
if (!is_gvl_waiting_state) return false;
|
|
1883
2406
|
|
|
@@ -1890,17 +2413,17 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1890
2413
|
// ...──────────────┬───────────────────...
|
|
1891
2414
|
// Other state │ Waiting for GVL
|
|
1892
2415
|
// ...──────────────┴───────────────────...
|
|
1893
|
-
// ▲
|
|
2416
|
+
// ▲ ▲
|
|
1894
2417
|
// └─ Previous sample └─ Regular sample (caller)
|
|
1895
2418
|
//
|
|
1896
2419
|
// In this case, we'll want to push two samples: a) one for the current time (handled by the caller), b) an extra sample
|
|
1897
|
-
// to represent the remaining cpu/wall time before the "Waiting for GVL" started:
|
|
2420
|
+
// to represent the remaining cpu/wall time before the "Waiting for GVL" started (for timeline purposes):
|
|
1898
2421
|
//
|
|
1899
2422
|
// time ─────►
|
|
1900
2423
|
// ...──────────────┬───────────────────...
|
|
1901
2424
|
// Other state │ Waiting for GVL
|
|
1902
2425
|
// ...──────────────┴───────────────────...
|
|
1903
|
-
// ▲
|
|
2426
|
+
// ▲ ▲ ▲
|
|
1904
2427
|
// └─ Prev... └─ Extra sample └─ Regular sample (caller)
|
|
1905
2428
|
//
|
|
1906
2429
|
// 2. The current sample is the n-th one after we entered the "Waiting for GVL" state
|
|
@@ -1910,7 +2433,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1910
2433
|
// ...──────────────┬───────────────────────────────────────────────...
|
|
1911
2434
|
// Other state │ Waiting for GVL
|
|
1912
2435
|
// ...──────────────┴───────────────────────────────────────────────...
|
|
1913
|
-
// ▲
|
|
2436
|
+
// ▲ ▲ ▲
|
|
1914
2437
|
// └─ Previous sample └─ Previous sample └─ Regular sample (caller)
|
|
1915
2438
|
//
|
|
1916
2439
|
// In this case, we just report back to the caller that the thread is in the "Waiting for GVL" state.
|
|
@@ -1925,91 +2448,153 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1925
2448
|
|
|
1926
2449
|
if (gvl_waiting_at < 0) {
|
|
1927
2450
|
// Negative means the waiting for GVL just ended, so we clear the state, so next samples no longer represent waiting
|
|
1928
|
-
|
|
2451
|
+
thread_context->gvl_waiting_at = 0;
|
|
1929
2452
|
}
|
|
1930
2453
|
|
|
1931
2454
|
long gvl_waiting_started_wall_time_ns = labs(gvl_waiting_at);
|
|
1932
2455
|
|
|
1933
2456
|
if (thread_context->wall_time_at_previous_sample_ns < gvl_waiting_started_wall_time_ns) { // situation 1 above
|
|
1934
|
-
long cpu_time_elapsed_ns =
|
|
1935
|
-
&thread_context->cpu_time_at_previous_sample_ns,
|
|
1936
|
-
current_cpu_time_ns,
|
|
1937
|
-
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
1938
|
-
IS_NOT_WALL_TIME
|
|
1939
|
-
);
|
|
2457
|
+
long cpu_time_elapsed_ns = update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
1940
2458
|
|
|
1941
|
-
long duration_until_start_of_gvl_waiting_ns =
|
|
1942
|
-
|
|
1943
|
-
gvl_waiting_started_wall_time_ns,
|
|
1944
|
-
INVALID_TIME,
|
|
1945
|
-
IS_WALL_TIME
|
|
1946
|
-
);
|
|
2459
|
+
long duration_until_start_of_gvl_waiting_ns =
|
|
2460
|
+
update_wall_time_since_previous_sample(thread_context, gvl_waiting_started_wall_time_ns);
|
|
1947
2461
|
|
|
1948
2462
|
// Push extra sample
|
|
1949
2463
|
trigger_sample_for_thread(
|
|
1950
2464
|
state,
|
|
1951
2465
|
thread_being_sampled,
|
|
1952
|
-
stack_from_thread,
|
|
1953
2466
|
thread_context,
|
|
1954
|
-
sampling_buffer,
|
|
1955
2467
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = duration_until_start_of_gvl_waiting_ns},
|
|
1956
2468
|
gvl_waiting_started_wall_time_ns,
|
|
1957
2469
|
NULL,
|
|
1958
2470
|
NULL,
|
|
1959
|
-
false // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
|
|
2471
|
+
/* is_gvl_waiting_state: */ false // This is the extra sample before the wait begun; only the next sample will be in the gvl waiting state
|
|
1960
2472
|
);
|
|
1961
2473
|
}
|
|
1962
2474
|
|
|
1963
2475
|
return true;
|
|
1964
2476
|
}
|
|
1965
2477
|
|
|
1966
|
-
static VALUE
|
|
2478
|
+
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
1967
2479
|
ENFORCE_THREAD(thread);
|
|
1968
2480
|
|
|
1969
|
-
|
|
1970
|
-
return Qnil;
|
|
1971
|
-
}
|
|
2481
|
+
debug_enter_unsafe_context();
|
|
1972
2482
|
|
|
1973
|
-
|
|
1974
|
-
|
|
2483
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2484
|
+
VALUE result = thread_context ? LONG2NUM(thread_context->gvl_waiting_at) : Qnil;
|
|
2485
|
+
|
|
2486
|
+
debug_leave_unsafe_context();
|
|
1975
2487
|
|
|
1976
|
-
|
|
1977
|
-
return LONG2NUM(gvl_waiting_at);
|
|
2488
|
+
return result;
|
|
1978
2489
|
}
|
|
1979
2490
|
|
|
1980
|
-
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2491
|
+
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE waiting_for_gvl_threshold_ns) {
|
|
1981
2492
|
ENFORCE_THREAD(thread);
|
|
2493
|
+
ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
|
|
1982
2494
|
|
|
1983
|
-
|
|
1984
|
-
}
|
|
2495
|
+
debug_enter_unsafe_context();
|
|
1985
2496
|
|
|
1986
|
-
|
|
1987
|
-
|
|
2497
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2498
|
+
VALUE result;
|
|
2499
|
+
if (thread_context) {
|
|
2500
|
+
on_gvl_running_result gvl_running_result =
|
|
2501
|
+
thread_context_collector_on_gvl_running(thread, thread_context, NUM2UINT(waiting_for_gvl_threshold_ns));
|
|
2502
|
+
result = gvl_running_result.action == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
|
|
2503
|
+
} else {
|
|
2504
|
+
result = Qfalse;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
debug_leave_unsafe_context();
|
|
1988
2508
|
|
|
1989
|
-
return
|
|
2509
|
+
return result;
|
|
1990
2510
|
}
|
|
1991
2511
|
|
|
1992
|
-
static VALUE
|
|
2512
|
+
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
|
|
1993
2513
|
ENFORCE_THREAD(thread);
|
|
1994
2514
|
|
|
1995
|
-
|
|
1996
|
-
TypedData_Get_Struct(collector_instance, struct thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2515
|
+
debug_enter_unsafe_context();
|
|
1997
2516
|
|
|
1998
|
-
|
|
1999
|
-
|
|
2517
|
+
VALUE result = thread_context_collector_sample_after_gvl_running(
|
|
2518
|
+
collector_instance,
|
|
2519
|
+
thread,
|
|
2520
|
+
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
2521
|
+
);
|
|
2000
2522
|
|
|
2001
|
-
|
|
2523
|
+
debug_leave_unsafe_context();
|
|
2002
2524
|
|
|
2003
|
-
return
|
|
2525
|
+
return result;
|
|
2004
2526
|
}
|
|
2005
2527
|
|
|
2006
2528
|
#else
|
|
2007
2529
|
static bool handle_gvl_waiting(
|
|
2008
|
-
DDTRACE_UNUSED
|
|
2530
|
+
DDTRACE_UNUSED thread_context_collector_state *state,
|
|
2009
2531
|
DDTRACE_UNUSED VALUE thread_being_sampled,
|
|
2010
|
-
DDTRACE_UNUSED
|
|
2011
|
-
DDTRACE_UNUSED struct per_thread_context *thread_context,
|
|
2012
|
-
DDTRACE_UNUSED sampling_buffer* sampling_buffer,
|
|
2532
|
+
DDTRACE_UNUSED per_thread_context *thread_context,
|
|
2013
2533
|
DDTRACE_UNUSED long current_cpu_time_ns
|
|
2014
2534
|
) { return false; }
|
|
2535
|
+
|
|
2015
2536
|
#endif // NO_GVL_INSTRUMENTATION
|
|
2537
|
+
|
|
2538
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns) {
|
|
2539
|
+
ENFORCE_THREAD(thread);
|
|
2540
|
+
|
|
2541
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2542
|
+
if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2543
|
+
|
|
2544
|
+
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
|
2545
|
+
|
|
2546
|
+
return Qtrue;
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
#define MAX_SAFE_LOOKUP_SIZE 16
|
|
2550
|
+
|
|
2551
|
+
typedef struct { VALUE lookup_key; VALUE result; } safe_lookup_hash_state;
|
|
2552
|
+
|
|
2553
|
+
static int safe_lookup_hash_iterate(VALUE key, VALUE value, VALUE state_ptr) {
|
|
2554
|
+
safe_lookup_hash_state *state = (safe_lookup_hash_state *) state_ptr;
|
|
2555
|
+
|
|
2556
|
+
if (key == state->lookup_key) {
|
|
2557
|
+
state->result = value;
|
|
2558
|
+
return ST_STOP;
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
return ST_CONTINUE;
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
// This method exists because we need to look up a hash during sampling, but we don't want to invoke any
|
|
2565
|
+
// Ruby code as a side effect. To be able to look up by hash, `rb_hash_lookup` calls `#hash` on the key,
|
|
2566
|
+
// which we want to avoid.
|
|
2567
|
+
// Thus, instead, we opt to just iterate through the hash and check if we can find what we're looking for.
|
|
2568
|
+
//
|
|
2569
|
+
// To avoid having too much overhead here we only iterate in hashes up to MAX_SAFE_LOOKUP_SIZE.
|
|
2570
|
+
// Note that we don't even try to iterate if the hash is bigger -- this is to avoid flaky behavior where
|
|
2571
|
+
// depending on the internal storage order of the hash we may or not find the key, and instead we always
|
|
2572
|
+
// enforce the size.
|
|
2573
|
+
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key) {
|
|
2574
|
+
if (!RB_TYPE_P(hash, T_HASH) || RHASH_SIZE(hash) > MAX_SAFE_LOOKUP_SIZE) return Qnil;
|
|
2575
|
+
|
|
2576
|
+
safe_lookup_hash_state state = {.lookup_key = key, .result = Qnil};
|
|
2577
|
+
|
|
2578
|
+
rb_hash_foreach(hash, safe_lookup_hash_iterate, (VALUE) &state);
|
|
2579
|
+
|
|
2580
|
+
return state.result;
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
2584
|
+
thread_context_collector_state *state;
|
|
2585
|
+
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2586
|
+
|
|
2587
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2588
|
+
long system_epoch_time_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, current_monotonic_wall_time_ns);
|
|
2589
|
+
|
|
2590
|
+
return LONG2NUM(system_epoch_time_ns);
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self) {
|
|
2594
|
+
return thread_context_collector_prepare_sample_inside_signal_handler() ? Qtrue : Qfalse;
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
static VALUE _native_request_prepare_on_gc_finish(DDTRACE_UNUSED VALUE self) {
|
|
2598
|
+
thread_context_collector_request_prepare_on_gc_finish();
|
|
2599
|
+
return Qnil;
|
|
2600
|
+
}
|