datadog 2.30.0 → 2.45.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 +450 -1
- data/README.md +3 -0
- data/bin/ddprofrb +4 -4
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
- data/ext/datadog_profiling_native_extension/clock_id.h +9 -1
- data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +66 -0
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +368 -151
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +2 -6
- data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +0 -2
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +29 -16
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +5 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +263 -170
- data/ext/datadog_profiling_native_extension/collectors_stack.h +24 -5
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +909 -552
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +25 -9
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +5 -4
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +44 -19
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +7 -12
- data/ext/datadog_profiling_native_extension/extconf.rb +86 -104
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +4 -43
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -47
- data/ext/datadog_profiling_native_extension/heap_recorder.c +393 -279
- data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
- data/ext/datadog_profiling_native_extension/http_transport.c +10 -5
- data/ext/datadog_profiling_native_extension/macos_sampler_thread.h +55 -0
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -27
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +847 -510
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +38 -14
- data/ext/datadog_profiling_native_extension/profiling.c +65 -27
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +37 -107
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -7
- data/ext/datadog_profiling_native_extension/stack_recorder.c +130 -93
- data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
- data/ext/datadog_profiling_native_extension/time_helpers.c +2 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +25 -19
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +4 -2
- data/ext/libdatadog_api/crashtracker.c +7 -8
- data/ext/libdatadog_api/datadog_ruby_common.c +44 -19
- data/ext/libdatadog_api/datadog_ruby_common.h +7 -12
- data/ext/libdatadog_api/di.c +218 -0
- data/ext/libdatadog_api/extconf.rb +40 -26
- data/ext/libdatadog_api/feature_flags.c +34 -0
- data/ext/libdatadog_api/init.c +13 -2
- data/ext/libdatadog_api/otel_thread_context.c +268 -0
- data/ext/libdatadog_api/otel_thread_context.h +5 -0
- data/ext/libdatadog_api/trace_exporter.c +1998 -0
- data/ext/libdatadog_api/trace_exporter.h +5 -0
- data/ext/libdatadog_extconf_helpers.rb +63 -13
- data/lib/datadog/ai_guard/autoload.rb +10 -0
- data/lib/datadog/ai_guard/component.rb +24 -13
- data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
- data/lib/datadog/ai_guard/configuration.rb +119 -2
- data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -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 +54 -23
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
- data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -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 +25 -7
- data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +8 -6
- data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +8 -59
- data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +11 -16
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
- data/lib/datadog/ai_guard/evaluation.rb +75 -17
- data/lib/datadog/ai_guard/ext.rb +16 -0
- data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +15 -11
- 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 +70 -43
- data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +22 -43
- data/lib/datadog/appsec/actions_handler.rb +6 -6
- data/lib/datadog/appsec/anonymizer.rb +1 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +2 -2
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +6 -6
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +1 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +2 -2
- data/lib/datadog/appsec/api_security/route_extractor.rb +26 -14
- data/lib/datadog/appsec/api_security/sampler.rb +5 -5
- data/lib/datadog/appsec/api_security.rb +1 -1
- data/lib/datadog/appsec/assets/blocked.html +1 -108
- data/lib/datadog/appsec/assets.rb +7 -7
- data/lib/datadog/appsec/autoload.rb +4 -4
- data/lib/datadog/appsec/component.rb +29 -22
- data/lib/datadog/appsec/compressed_json.rb +7 -7
- data/lib/datadog/appsec/configuration.rb +436 -1
- data/lib/datadog/appsec/context.rb +10 -8
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +6 -6
- data/lib/datadog/appsec/contrib/active_record/integration.rb +4 -4
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +4 -4
- 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 +1 -1
- data/lib/datadog/appsec/contrib/devise/data_extractor.rb +3 -3
- data/lib/datadog/appsec/contrib/devise/ext.rb +20 -20
- data/lib/datadog/appsec/contrib/devise/integration.rb +4 -4
- data/lib/datadog/appsec/contrib/devise/patcher.rb +8 -8
- data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +14 -13
- data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +8 -8
- data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +24 -9
- data/lib/datadog/appsec/contrib/excon/integration.rb +4 -4
- data/lib/datadog/appsec/contrib/excon/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +76 -21
- data/lib/datadog/appsec/contrib/faraday/integration.rb +4 -4
- data/lib/datadog/appsec/contrib/faraday/patcher.rb +4 -4
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +72 -18
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +4 -4
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +66 -21
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +7 -7
- data/lib/datadog/appsec/contrib/graphql/integration.rb +9 -8
- data/lib/datadog/appsec/contrib/graphql/patcher.rb +3 -3
- data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
- data/lib/datadog/appsec/contrib/rack/ext.rb +29 -29
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +56 -17
- data/lib/datadog/appsec/contrib/rack/gateway/response.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +46 -30
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +79 -0
- data/lib/datadog/appsec/contrib/rack/integration.rb +6 -6
- data/lib/datadog/appsec/contrib/rack/patcher.rb +3 -3
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +11 -8
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +51 -61
- data/lib/datadog/appsec/contrib/rack/response_body.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +43 -4
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +25 -9
- data/lib/datadog/appsec/contrib/rails/integration.rb +5 -5
- data/lib/datadog/appsec/contrib/rails/patcher.rb +17 -17
- data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +2 -2
- data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +3 -3
- 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 +5 -5
- data/lib/datadog/appsec/contrib/rest_client/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +74 -21
- 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 +34 -13
- data/lib/datadog/appsec/contrib/sinatra/integration.rb +5 -5
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +11 -11
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +3 -3
- data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
- data/lib/datadog/appsec/counter_sampler.rb +1 -1
- data/lib/datadog/appsec/default_header_tags.rb +52 -0
- data/lib/datadog/appsec/event.rb +14 -14
- data/lib/datadog/appsec/ext.rb +11 -10
- data/lib/datadog/appsec/extensions.rb +2 -2
- data/lib/datadog/appsec/instrumentation/gateway.rb +1 -14
- data/lib/datadog/appsec/metrics/collector.rb +18 -1
- data/lib/datadog/appsec/metrics/exporter.rb +17 -11
- data/lib/datadog/appsec/metrics/telemetry.rb +15 -3
- data/lib/datadog/appsec/metrics/telemetry_exporter.rb +4 -4
- data/lib/datadog/appsec/metrics.rb +4 -4
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +18 -12
- data/lib/datadog/appsec/monitor.rb +1 -1
- data/lib/datadog/appsec/processor/rule_loader.rb +32 -32
- data/lib/datadog/appsec/rate_limiter.rb +1 -1
- data/lib/datadog/appsec/remote.rb +15 -9
- data/lib/datadog/appsec/response.rb +22 -22
- 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 +40 -40
- data/lib/datadog/appsec/security_engine/runner.rb +3 -3
- data/lib/datadog/appsec/security_event.rb +2 -2
- data/lib/datadog/appsec/trace_keeper.rb +18 -6
- data/lib/datadog/appsec/utils/http/body.rb +11 -8
- data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
- data/lib/datadog/appsec/utils/http/media_range.rb +15 -15
- data/lib/datadog/appsec/utils/http/media_type.rb +5 -6
- data/lib/datadog/appsec/utils/http/url_encoded.rb +79 -24
- data/lib/datadog/appsec.rb +21 -24
- data/lib/datadog/auto_instrument.rb +2 -2
- data/lib/datadog/core/buffer/cruby.rb +1 -1
- data/lib/datadog/core/buffer/thread_safe.rb +1 -1
- data/lib/datadog/core/configuration/agent_settings.rb +1 -1
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +15 -15
- data/lib/datadog/core/configuration/agentless_settings_resolver.rb +2 -2
- data/lib/datadog/core/configuration/base.rb +19 -7
- data/lib/datadog/core/configuration/components.rb +151 -37
- data/lib/datadog/core/configuration/components_state.rb +6 -1
- data/lib/datadog/core/configuration/config_helper.rb +12 -3
- data/lib/datadog/core/configuration/deprecations.rb +6 -6
- data/lib/datadog/core/configuration/ext.rb +11 -11
- data/lib/datadog/core/configuration/option.rb +46 -21
- data/lib/datadog/core/configuration/option_definition.rb +43 -17
- data/lib/datadog/core/configuration/options.rb +42 -8
- data/lib/datadog/core/configuration/settings.rb +133 -105
- data/lib/datadog/core/configuration/stable_config.rb +1 -1
- data/lib/datadog/core/configuration/supported_configurations.rb +33 -1
- data/lib/datadog/core/configuration.rb +9 -9
- data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
- data/lib/datadog/core/contrib/rails/utils.rb +7 -3
- data/lib/datadog/core/crashtracking/component.rb +17 -13
- data/lib/datadog/core/crashtracking/tag_builder.rb +5 -5
- data/lib/datadog/core/deprecations.rb +1 -1
- data/lib/datadog/core/diagnostics/environment_logger.rb +45 -8
- data/lib/datadog/core/diagnostics/health.rb +2 -2
- data/lib/datadog/core/encoding.rb +4 -4
- data/lib/datadog/core/environment/agent_info.rb +2 -2
- data/lib/datadog/core/environment/cgroup.rb +4 -4
- data/lib/datadog/core/environment/container.rb +11 -11
- data/lib/datadog/core/environment/execution.rb +3 -3
- data/lib/datadog/core/environment/ext.rb +21 -15
- data/lib/datadog/core/environment/gc.rb +1 -1
- data/lib/datadog/core/environment/git.rb +2 -2
- data/lib/datadog/core/environment/identity.rb +47 -11
- data/lib/datadog/core/environment/platform.rb +8 -8
- data/lib/datadog/core/environment/process.rb +54 -19
- data/lib/datadog/core/environment/socket.rb +15 -2
- data/lib/datadog/core/environment/variable_helpers.rb +1 -1
- data/lib/datadog/core/environment/vm_cache.rb +1 -1
- data/lib/datadog/core/environment/yjit.rb +2 -2
- data/lib/datadog/core/error.rb +5 -5
- data/lib/datadog/core/evp.rb +11 -0
- data/lib/datadog/core/extensions.rb +1 -1
- data/lib/datadog/core/feature_flags.rb +3 -3
- data/lib/datadog/core/git/ext.rb +4 -4
- data/lib/datadog/core/knuth_sampler.rb +3 -1
- data/lib/datadog/core/logger.rb +3 -3
- data/lib/datadog/core/logging/ext.rb +1 -1
- data/lib/datadog/core/metrics/client.rb +26 -26
- 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 +2 -2
- data/lib/datadog/core/metrics/options.rb +5 -5
- data/lib/datadog/core/pin.rb +1 -1
- data/lib/datadog/core/process_discovery.rb +8 -8
- data/lib/datadog/core/rate_limiter.rb +1 -1
- data/lib/datadog/core/remote/client/capabilities.rb +77 -19
- data/lib/datadog/core/remote/client.rb +18 -18
- data/lib/datadog/core/remote/component.rb +66 -34
- data/lib/datadog/core/remote/configuration/content.rb +3 -3
- data/lib/datadog/core/remote/configuration/digest.rb +1 -1
- data/lib/datadog/core/remote/configuration/path.rb +9 -9
- data/lib/datadog/core/remote/configuration/repository.rb +4 -4
- data/lib/datadog/core/remote/configuration/target.rb +9 -9
- 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 +1 -1
- data/lib/datadog/core/remote/tie/tracing.rb +7 -7
- data/lib/datadog/core/remote/transport/config.rb +4 -4
- data/lib/datadog/core/remote/transport/http/config.rb +10 -10
- data/lib/datadog/core/remote/transport/http/negotiation.rb +4 -4
- data/lib/datadog/core/remote/transport/http.rb +10 -10
- data/lib/datadog/core/remote/transport/negotiation.rb +3 -3
- data/lib/datadog/core/remote/worker.rb +13 -5
- data/lib/datadog/core/runtime/ext.rb +22 -22
- data/lib/datadog/core/runtime/metrics.rb +10 -10
- data/lib/datadog/core/tag_builder.rb +18 -18
- data/lib/datadog/core/tag_normalizer.rb +7 -7
- data/lib/datadog/core/telemetry/component.rb +16 -13
- data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
- data/lib/datadog/core/telemetry/emitter.rb +5 -5
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +10 -9
- data/lib/datadog/core/telemetry/event/app_closing.rb +2 -2
- data/lib/datadog/core/telemetry/event/app_dependencies_loaded.rb +2 -2
- data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +3 -3
- data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
- data/lib/datadog/core/telemetry/event/app_heartbeat.rb +2 -2
- data/lib/datadog/core/telemetry/event/app_integrations_change.rb +2 -2
- data/lib/datadog/core/telemetry/event/app_started.rb +142 -193
- data/lib/datadog/core/telemetry/event/base.rb +1 -1
- data/lib/datadog/core/telemetry/event/distributions.rb +2 -2
- data/lib/datadog/core/telemetry/event/generate_metrics.rb +3 -3
- data/lib/datadog/core/telemetry/event/log.rb +6 -6
- data/lib/datadog/core/telemetry/event/message_batch.rb +1 -1
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +3 -3
- data/lib/datadog/core/telemetry/event.rb +16 -22
- data/lib/datadog/core/telemetry/ext.rb +11 -10
- data/lib/datadog/core/telemetry/logger.rb +1 -1
- data/lib/datadog/core/telemetry/logging.rb +5 -5
- data/lib/datadog/core/telemetry/metric.rb +9 -9
- data/lib/datadog/core/telemetry/metrics_collection.rb +2 -2
- data/lib/datadog/core/telemetry/metrics_manager.rb +1 -1
- data/lib/datadog/core/telemetry/request.rb +6 -8
- data/lib/datadog/core/telemetry/transport/http/telemetry.rb +16 -11
- data/lib/datadog/core/telemetry/transport/http.rb +7 -7
- data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -3
- data/lib/datadog/core/telemetry/worker.rb +26 -6
- data/lib/datadog/core/transport/ext.rb +14 -14
- data/lib/datadog/core/transport/http/adapters/net.rb +4 -4
- data/lib/datadog/core/transport/http/adapters/test.rb +1 -1
- data/lib/datadog/core/transport/http/adapters/unix_socket.rb +4 -4
- data/lib/datadog/core/transport/http/api/endpoint.rb +1 -1
- data/lib/datadog/core/transport/http/api/map.rb +1 -1
- data/lib/datadog/core/transport/http/builder.rb +6 -6
- data/lib/datadog/core/transport/http/client.rb +2 -2
- data/lib/datadog/core/transport/http/response.rb +1 -1
- data/lib/datadog/core/transport/http.rb +9 -7
- data/lib/datadog/core/transport/response.rb +1 -1
- data/lib/datadog/core/transport/transport.rb +2 -2
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +172 -20
- data/lib/datadog/core/utils/base64_codec.rb +23 -0
- data/lib/datadog/core/utils/duration.rb +1 -1
- data/lib/datadog/core/utils/{array.rb → enumerable_compat.rb} +2 -2
- data/lib/datadog/core/utils/forking.rb +3 -1
- data/lib/datadog/core/utils/hash.rb +0 -23
- data/lib/datadog/core/utils/lru_cache.rb +3 -3
- data/lib/datadog/core/utils/network.rb +11 -11
- data/lib/datadog/core/utils/only_once.rb +1 -1
- data/lib/datadog/core/utils/only_once_successful.rb +1 -1
- data/lib/datadog/core/utils/safe_dup.rb +2 -1
- data/lib/datadog/core/utils/sequence.rb +3 -6
- data/lib/datadog/core/utils/spawn_monkey_patch.rb +68 -0
- data/lib/datadog/core/utils/time.rb +18 -52
- data/lib/datadog/core/utils/url.rb +1 -1
- data/lib/datadog/core/utils.rb +5 -5
- data/lib/datadog/core/workers/async.rb +2 -2
- data/lib/datadog/core/workers/polling.rb +2 -2
- data/lib/datadog/core/workers/runtime_metrics.rb +4 -4
- data/lib/datadog/core.rb +7 -5
- data/lib/datadog/data_streams/configuration.rb +40 -1
- data/lib/datadog/data_streams/ext.rb +1 -1
- data/lib/datadog/data_streams/extensions.rb +2 -2
- data/lib/datadog/data_streams/pathway_context.rb +28 -30
- data/lib/datadog/data_streams/processor.rb +93 -62
- data/lib/datadog/data_streams/transport/http/stats.rb +5 -5
- data/lib/datadog/data_streams/transport/http.rb +5 -5
- data/lib/datadog/data_streams/transport/stats.rb +7 -7
- data/lib/datadog/data_streams.rb +5 -5
- data/lib/datadog/di/base.rb +16 -8
- data/lib/datadog/di/boot.rb +25 -24
- 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 +218 -16
- data/lib/datadog/di/component.rb +123 -37
- data/lib/datadog/di/configuration.rb +241 -2
- data/lib/datadog/di/context.rb +9 -3
- data/lib/datadog/di/contrib/active_record.rb +6 -2
- data/lib/datadog/di/contrib/railtie.rb +1 -1
- data/lib/datadog/di/contrib.rb +5 -5
- data/lib/datadog/di/el/compiler.rb +114 -38
- data/lib/datadog/di/el/evaluator.rb +82 -2
- data/lib/datadog/di/el/expression.rb +13 -2
- data/lib/datadog/di/el.rb +3 -3
- data/lib/datadog/di/error.rb +10 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +528 -184
- data/lib/datadog/di/logger.rb +3 -3
- data/lib/datadog/di/preload.rb +1 -1
- data/lib/datadog/di/probe.rb +69 -2
- data/lib/datadog/di/probe_builder.rb +98 -15
- data/lib/datadog/di/probe_file_loader/railtie.rb +1 -1
- data/lib/datadog/di/probe_file_loader.rb +14 -9
- data/lib/datadog/di/probe_manager.rb +83 -20
- data/lib/datadog/di/probe_notification_builder.rb +178 -25
- data/lib/datadog/di/probe_notifier_worker.rb +57 -37
- data/lib/datadog/di/probe_repository.rb +1 -1
- data/lib/datadog/di/proc_responder.rb +1 -1
- data/lib/datadog/di/redactor.rb +16 -1
- data/lib/datadog/di/remote.rb +187 -26
- data/lib/datadog/di/serializer.rb +79 -35
- data/lib/datadog/di/transport/diagnostics.rb +5 -5
- data/lib/datadog/di/transport/http/diagnostics.rb +3 -3
- data/lib/datadog/di/transport/http/input.rb +2 -2
- data/lib/datadog/di/transport/http.rb +10 -10
- data/lib/datadog/di/transport/input.rb +18 -14
- data/lib/datadog/di/utils.rb +43 -15
- data/lib/datadog/di.rb +147 -3
- data/lib/datadog/error_tracking/collector.rb +3 -2
- data/lib/datadog/error_tracking/component.rb +12 -12
- data/lib/datadog/error_tracking/configuration.rb +55 -2
- data/lib/datadog/error_tracking/ext.rb +7 -7
- data/lib/datadog/error_tracking/extensions.rb +2 -2
- data/lib/datadog/error_tracking/filters.rb +4 -4
- data/lib/datadog/error_tracking.rb +6 -6
- data/lib/datadog/kit/appsec/events/v2.rb +91 -33
- data/lib/datadog/kit/appsec/events.rb +25 -25
- data/lib/datadog/kit/enable_core_dumps.rb +3 -3
- data/lib/datadog/kit/identity.rb +18 -18
- data/lib/datadog/kit/tracing/method_tracer.rb +9 -6
- 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 +118 -13
- 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 +140 -1
- data/lib/datadog/open_feature/evaluation_engine.rb +38 -12
- data/lib/datadog/open_feature/exposures/batch_builder.rb +1 -1
- data/lib/datadog/open_feature/exposures/buffer.rb +1 -1
- data/lib/datadog/open_feature/exposures/deduplicator.rb +1 -1
- data/lib/datadog/open_feature/exposures/event.rb +17 -10
- data/lib/datadog/open_feature/exposures/reporter.rb +3 -3
- data/lib/datadog/open_feature/exposures/worker.rb +6 -6
- data/lib/datadog/open_feature/ext.rb +32 -6
- 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 +43 -7
- data/lib/datadog/open_feature/noop_evaluator.rb +8 -3
- data/lib/datadog/open_feature/provider.rb +115 -21
- data/lib/datadog/open_feature/remote.rb +8 -8
- data/lib/datadog/open_feature/resolution_details.rb +4 -2
- data/lib/datadog/open_feature/transport.rb +71 -12
- data/lib/datadog/open_feature.rb +2 -2
- data/lib/datadog/opentelemetry/api/baggage.rb +3 -3
- data/lib/datadog/opentelemetry/api/context.rb +4 -4
- data/lib/datadog/opentelemetry/configuration/settings.rb +95 -28
- data/lib/datadog/opentelemetry/ext.rb +9 -0
- data/lib/datadog/opentelemetry/logs.rb +98 -0
- data/lib/datadog/opentelemetry/metrics.rb +17 -44
- data/lib/datadog/opentelemetry/sdk/configurator.rb +46 -6
- 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 +9 -16
- data/lib/datadog/opentelemetry/sdk/propagator.rb +17 -11
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +18 -15
- data/lib/datadog/opentelemetry/sdk/trace/span.rb +32 -32
- data/lib/datadog/opentelemetry/sdk.rb +19 -0
- data/lib/datadog/opentelemetry/signal_configuration.rb +57 -0
- data/lib/datadog/opentelemetry/trace.rb +4 -1
- data/lib/datadog/opentelemetry.rb +8 -7
- data/lib/datadog/profiling/collectors/code_provenance.rb +36 -11
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +45 -3
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +10 -4
- data/lib/datadog/profiling/collectors/info.rb +19 -10
- data/lib/datadog/profiling/collectors/thread_context.rb +16 -22
- data/lib/datadog/profiling/component.rb +43 -37
- data/lib/datadog/profiling/exporter.rb +43 -16
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +6 -5
- data/lib/datadog/profiling/ext.rb +0 -2
- data/lib/datadog/profiling/flush.rb +21 -12
- data/lib/datadog/profiling/http_transport.rb +13 -2
- data/lib/datadog/profiling/load_native_extension.rb +2 -2
- data/lib/datadog/profiling/profiler.rb +13 -5
- data/lib/datadog/profiling/scheduler.rb +2 -2
- data/lib/datadog/profiling/sequence_tracker.rb +1 -1
- data/lib/datadog/profiling/stack_recorder.rb +0 -8
- data/lib/datadog/profiling/tag_builder.rb +19 -19
- data/lib/datadog/profiling/tasks/exec.rb +9 -4
- data/lib/datadog/profiling/tasks/help.rb +1 -0
- data/lib/datadog/profiling/tasks/setup.rb +2 -2
- data/lib/datadog/profiling.rb +32 -29
- data/lib/datadog/ruby_version.rb +25 -0
- data/lib/datadog/single_step_instrument.rb +2 -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 +1 -1
- data/lib/datadog/tracing/buffer.rb +8 -8
- data/lib/datadog/tracing/client_ip.rb +4 -4
- data/lib/datadog/tracing/component.rb +41 -7
- data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
- data/lib/datadog/tracing/configuration/dynamic.rb +13 -11
- data/lib/datadog/tracing/configuration/ext.rb +53 -38
- data/lib/datadog/tracing/configuration/http.rb +2 -2
- data/lib/datadog/tracing/configuration/settings.rb +58 -17
- data/lib/datadog/tracing/context.rb +25 -3
- data/lib/datadog/tracing/context_provider.rb +11 -7
- data/lib/datadog/tracing/contrib/action_cable/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/action_cable/event.rb +4 -4
- 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 +7 -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 +7 -7
- data/lib/datadog/tracing/contrib/action_mailer/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +15 -12
- data/lib/datadog/tracing/contrib/action_pack/action_controller/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +31 -3
- 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 -10
- data/lib/datadog/tracing/contrib/action_pack/integration.rb +7 -7
- data/lib/datadog/tracing/contrib/action_pack/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -1
- 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 +11 -8
- 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 +11 -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 +3 -3
- data/lib/datadog/tracing/contrib/active_job/events/discard.rb +10 -7
- data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +10 -7
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +10 -7
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +10 -7
- data/lib/datadog/tracing/contrib/active_job/events/perform.rb +10 -7
- data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +10 -7
- 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 +7 -7
- data/lib/datadog/tracing/contrib/active_job/patcher.rb +17 -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 +7 -7
- data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +8 -9
- 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 -5
- 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 +9 -9
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +12 -14
- 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 +11 -11
- data/lib/datadog/tracing/contrib/active_record/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/active_record/utils.rb +4 -4
- data/lib/datadog/tracing/contrib/active_support/cache/event.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +14 -17
- data/lib/datadog/tracing/contrib/active_support/cache/events.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +5 -8
- data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +5 -5
- data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +4 -4
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/active_support/ext.rb +16 -16
- data/lib/datadog/tracing/contrib/active_support/integration.rb +8 -8
- data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +2 -2
- data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +3 -3
- 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 +4 -4
- data/lib/datadog/tracing/contrib/aws/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/aws/ext.rb +26 -26
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +7 -11
- data/lib/datadog/tracing/contrib/aws/integration.rb +9 -9
- data/lib/datadog/tracing/contrib/aws/parsed_context.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/patcher.rb +5 -5
- 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 +1 -1
- data/lib/datadog/tracing/contrib/concurrent_ruby/async_patch.rb +1 -1
- data/lib/datadog/tracing/contrib/concurrent_ruby/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +1 -1
- data/lib/datadog/tracing/contrib/concurrent_ruby/ext.rb +2 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/future_patch.rb +1 -1
- data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/concurrent_ruby/patcher.rb +4 -4
- data/lib/datadog/tracing/contrib/concurrent_ruby/promises_future_patch.rb +1 -1
- data/lib/datadog/tracing/contrib/configurable.rb +20 -5
- data/lib/datadog/tracing/contrib/configuration/resolver.rb +14 -4
- data/lib/datadog/tracing/contrib/configuration/resolvers/pattern_resolver.rb +1 -1
- data/lib/datadog/tracing/contrib/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/dalli/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/dalli/ext.rb +13 -13
- data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +6 -10
- data/lib/datadog/tracing/contrib/dalli/integration.rb +7 -7
- data/lib/datadog/tracing/contrib/dalli/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/dalli/quantize.rb +3 -3
- data/lib/datadog/tracing/contrib/delayed_job/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/delayed_job/ext.rb +14 -14
- data/lib/datadog/tracing/contrib/delayed_job/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/delayed_job/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +7 -5
- data/lib/datadog/tracing/contrib/delayed_job/server_internal_tracer/worker.rb +1 -0
- data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +15 -15
- data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +12 -16
- data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +4 -4
- data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +9 -13
- data/lib/datadog/tracing/contrib/ethon/ext.rb +13 -13
- 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 +5 -5
- data/lib/datadog/tracing/contrib/excon/ext.rb +11 -11
- data/lib/datadog/tracing/contrib/excon/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/excon/middleware.rb +10 -14
- data/lib/datadog/tracing/contrib/excon/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/ext.rb +20 -18
- data/lib/datadog/tracing/contrib/extensions.rb +11 -2
- data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +4 -4
- data/lib/datadog/tracing/contrib/faraday/ext.rb +11 -11
- data/lib/datadog/tracing/contrib/faraday/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +10 -14
- data/lib/datadog/tracing/contrib/faraday/patcher.rb +6 -6
- data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +4 -4
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +59 -29
- data/lib/datadog/tracing/contrib/grape/ext.rb +15 -15
- data/lib/datadog/tracing/contrib/grape/instrumentation.rb +3 -3
- data/lib/datadog/tracing/contrib/grape/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/grape/patcher.rb +5 -5
- data/lib/datadog/tracing/contrib/graphql/configuration/error_extension_env_parser.rb +1 -1
- data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/graphql/ext.rb +9 -9
- data/lib/datadog/tracing/contrib/graphql/integration.rb +9 -9
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +7 -7
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +38 -37
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +11 -15
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +11 -15
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor.rb +6 -6
- data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +11 -9
- data/lib/datadog/tracing/contrib/grpc/ext.rb +14 -14
- 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 +4 -3
- data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +6 -4
- data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +1 -1
- data/lib/datadog/tracing/contrib/http/configuration/settings.rb +4 -4
- data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +3 -3
- data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +10 -8
- data/lib/datadog/tracing/contrib/http/ext.rb +11 -11
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +10 -14
- data/lib/datadog/tracing/contrib/http/integration.rb +7 -7
- 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 +4 -4
- data/lib/datadog/tracing/contrib/httpclient/ext.rb +11 -11
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +12 -12
- 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 +4 -4
- data/lib/datadog/tracing/contrib/httprb/ext.rb +11 -11
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +10 -14
- data/lib/datadog/tracing/contrib/httprb/integration.rb +7 -7
- 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 +4 -3
- data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +7 -3
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +4 -4
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +4 -4
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +5 -5
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/join_group.rb +5 -5
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/leave_group.rb +5 -5
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/sync_group.rb +5 -5
- data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +10 -5
- data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +10 -5
- 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 +4 -4
- data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +6 -6
- data/lib/datadog/tracing/contrib/kafka/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/kafka/patcher.rb +5 -5
- data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +11 -9
- data/lib/datadog/tracing/contrib/karafka/ext.rb +13 -13
- data/lib/datadog/tracing/contrib/karafka/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/karafka/monitor.rb +14 -14
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +9 -9
- data/lib/datadog/tracing/contrib/karafka.rb +7 -6
- 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/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +9 -9
- data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/mongodb/ext.rb +18 -18
- data/lib/datadog/tracing/contrib/mongodb/instrumentation.rb +3 -3
- data/lib/datadog/tracing/contrib/mongodb/integration.rb +7 -7
- data/lib/datadog/tracing/contrib/mongodb/parsers.rb +9 -9
- data/lib/datadog/tracing/contrib/mongodb/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +18 -21
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/mysql2/ext.rb +11 -11
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +7 -11
- 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 +2 -2
- data/lib/datadog/tracing/contrib/opensearch/ext.rb +23 -23
- data/lib/datadog/tracing/contrib/opensearch/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +15 -19
- data/lib/datadog/tracing/contrib/opensearch/quantize.rb +5 -5
- data/lib/datadog/tracing/contrib/patchable.rb +1 -1
- data/lib/datadog/tracing/contrib/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/pg/ext.rb +19 -19
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +19 -23
- 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 +21 -21
- data/lib/datadog/tracing/contrib/presto/instrumentation.rb +8 -10
- data/lib/datadog/tracing/contrib/presto/integration.rb +6 -6
- 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 +19 -16
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +3 -3
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +5 -5
- data/lib/datadog/tracing/contrib/que/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/que/ext.rb +19 -19
- 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 +5 -9
- data/lib/datadog/tracing/contrib/racecar/events/batch.rb +3 -3
- data/lib/datadog/tracing/contrib/racecar/events/consume.rb +3 -3
- data/lib/datadog/tracing/contrib/racecar/events/message.rb +3 -3
- 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 +9 -3
- data/lib/datadog/tracing/contrib/rack/ext.rb +43 -16
- data/lib/datadog/tracing/contrib/rack/header_collection.rb +3 -3
- data/lib/datadog/tracing/contrib/rack/header_tagging.rb +24 -1
- data/lib/datadog/tracing/contrib/rack/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +28 -25
- data/lib/datadog/tracing/contrib/rack/patcher.rb +5 -5
- data/lib/datadog/tracing/contrib/rack/request_queue.rb +4 -4
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +12 -10
- data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +124 -6
- data/lib/datadog/tracing/contrib/rails/auto_instrument_railtie.rb +1 -1
- data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/rails/ext.rb +15 -15
- data/lib/datadog/tracing/contrib/rails/framework.rb +11 -11
- data/lib/datadog/tracing/contrib/rails/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
- data/lib/datadog/tracing/contrib/rails/middlewares.rb +1 -1
- data/lib/datadog/tracing/contrib/rails/patcher.rb +8 -9
- data/lib/datadog/tracing/contrib/rails/railtie.rb +4 -4
- data/lib/datadog/tracing/contrib/rails/runner.rb +4 -2
- 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 +9 -7
- 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 +3 -3
- data/lib/datadog/tracing/contrib/redis/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/redis/ext.rb +16 -16
- 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 +10 -10
- data/lib/datadog/tracing/contrib/redis/quantize.rb +40 -6
- data/lib/datadog/tracing/contrib/redis/tags.rb +6 -11
- data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +16 -8
- 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 +6 -5
- data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/rest_client/ext.rb +10 -10
- data/lib/datadog/tracing/contrib/rest_client/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/rest_client/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +6 -10
- 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 +8 -5
- 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/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 +7 -10
- data/lib/datadog/tracing/contrib/sequel/dataset.rb +6 -5
- 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 +9 -8
- data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +12 -10
- data/lib/datadog/tracing/contrib/sidekiq/ext.rb +30 -30
- 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 +15 -14
- 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 +3 -3
- data/lib/datadog/tracing/contrib/sinatra/env.rb +6 -6
- data/lib/datadog/tracing/contrib/sinatra/ext.rb +17 -17
- 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 +9 -8
- 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 +7 -7
- 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 +2 -2
- data/lib/datadog/tracing/contrib/status_range_env_parser.rb +2 -2
- data/lib/datadog/tracing/contrib/status_range_matcher.rb +4 -0
- 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 +4 -4
- 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/trilogy/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/trilogy/ext.rb +11 -11
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +7 -11
- 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} +3 -3
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +16 -13
- data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +11 -9
- data/lib/datadog/tracing/contrib/waterdrop/ext.rb +4 -4
- data/lib/datadog/tracing/contrib/waterdrop/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +2 -2
- data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +7 -7
- data/lib/datadog/tracing/contrib/waterdrop/producer.rb +3 -3
- data/lib/datadog/tracing/contrib/waterdrop.rb +7 -6
- data/lib/datadog/tracing/contrib.rb +62 -54
- data/lib/datadog/tracing/correlation.rb +12 -12
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +15 -12
- 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 +8 -8
- data/lib/datadog/tracing/distributed/b3_single.rb +4 -4
- data/lib/datadog/tracing/distributed/baggage.rb +81 -28
- data/lib/datadog/tracing/distributed/datadog.rb +34 -32
- data/lib/datadog/tracing/distributed/fetcher.rb +1 -1
- data/lib/datadog/tracing/distributed/helpers.rb +3 -3
- data/lib/datadog/tracing/distributed/propagation.rb +46 -11
- data/lib/datadog/tracing/distributed/propagation_policy.rb +5 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +29 -263
- 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 +2 -2
- data/lib/datadog/tracing/ext.rb +9 -0
- data/lib/datadog/tracing/metadata/analytics.rb +2 -2
- data/lib/datadog/tracing/metadata/errors.rb +4 -4
- data/lib/datadog/tracing/metadata/ext.rb +79 -66
- data/lib/datadog/tracing/metadata/metastruct.rb +1 -1
- data/lib/datadog/tracing/metadata/metastruct_tagging.rb +1 -1
- data/lib/datadog/tracing/metadata/tagging.rb +5 -5
- data/lib/datadog/tracing/metadata.rb +4 -4
- data/lib/datadog/tracing/otel_thread_context.rb +99 -0
- data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
- data/lib/datadog/tracing/pipeline.rb +3 -3
- data/lib/datadog/tracing/remote.rb +227 -21
- data/lib/datadog/tracing/sampling/all_sampler.rb +1 -1
- data/lib/datadog/tracing/sampling/ext.rb +9 -7
- data/lib/datadog/tracing/sampling/matcher.rb +4 -4
- data/lib/datadog/tracing/sampling/priority_sampler.rb +17 -4
- 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 +2 -2
- data/lib/datadog/tracing/sampling/rule.rb +5 -5
- data/lib/datadog/tracing/sampling/rule_sampler.rb +68 -37
- 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 -1
- data/lib/datadog/tracing/sampling/span/rule_parser.rb +10 -10
- data/lib/datadog/tracing/span.rb +9 -9
- data/lib/datadog/tracing/span_event.rb +5 -5
- data/lib/datadog/tracing/span_operation.rb +39 -26
- data/lib/datadog/tracing/sync_writer.rb +12 -7
- data/lib/datadog/tracing/trace_digest.rb +29 -4
- data/lib/datadog/tracing/trace_operation.rb +112 -31
- data/lib/datadog/tracing/trace_segment.rb +10 -10
- data/lib/datadog/tracing/tracer.rb +83 -29
- data/lib/datadog/tracing/transport/http/client.rb +4 -4
- data/lib/datadog/tracing/transport/http/statistics.rb +2 -2
- data/lib/datadog/tracing/transport/http/traces.rb +8 -8
- data/lib/datadog/tracing/transport/http.rb +10 -10
- data/lib/datadog/tracing/transport/io/client.rb +3 -3
- data/lib/datadog/tracing/transport/io/response.rb +1 -1
- data/lib/datadog/tracing/transport/io/traces.rb +5 -5
- 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 +380 -0
- data/lib/datadog/tracing/transport/serializable_trace.rb +19 -19
- data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
- data/lib/datadog/tracing/transport/statistics.rb +2 -2
- data/lib/datadog/tracing/transport/trace_formatter.rb +23 -13
- data/lib/datadog/tracing/transport/traces.rb +12 -32
- data/lib/datadog/tracing/utils.rb +2 -2
- data/lib/datadog/tracing/workers.rb +4 -3
- data/lib/datadog/tracing/writer.rb +22 -9
- data/lib/datadog/tracing.rb +3 -3
- data/lib/datadog/version.rb +4 -4
- data/lib/datadog.rb +15 -11
- metadata +109 -23
- data/ext/datadog_profiling_native_extension/clock_id_noop.c +0 -21
- data/lib/datadog/ai_guard/configuration/settings.rb +0 -113
- data/lib/datadog/appsec/configuration/settings.rb +0 -423
- data/lib/datadog/core/utils/base64.rb +0 -22
- data/lib/datadog/data_streams/configuration/settings.rb +0 -49
- data/lib/datadog/di/configuration/settings.rb +0 -243
- data/lib/datadog/error_tracking/configuration/settings.rb +0 -63
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -82
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include <ruby.h>
|
|
2
|
+
#include <ruby/debug.h>
|
|
2
3
|
|
|
3
4
|
#include "datadog_ruby_common.h"
|
|
4
5
|
#include "collectors_thread_context.h"
|
|
@@ -41,12 +42,10 @@
|
|
|
41
42
|
//
|
|
42
43
|
// When `thread_context_collector_on_gc_start` gets called, the current cpu and wall-time get recorded to the thread
|
|
43
44
|
// context: `cpu_time_at_gc_start_ns` and `wall_time_at_gc_start_ns`.
|
|
45
|
+
// (Ruby won't switch threads during GC, so `gc_start` and `gc_finish` will happen on the same thread)
|
|
44
46
|
//
|
|
45
|
-
// While `cpu_time_at_gc_start_ns` is set,
|
|
46
|
-
//
|
|
47
|
-
// Wall-time accounting is not affected by this (e.g. we still record 60 seconds every 60 seconds).
|
|
48
|
-
//
|
|
49
|
-
// (Regular samples can still account for the cpu-time between the previous sample and the start of GC.)
|
|
47
|
+
// While `cpu_time_at_gc_start_ns` is set, we don't expect the thread to be sampled: the VM is doing GC
|
|
48
|
+
// on the thread holding the GVL so no other samples can/will be triggered until GC finishes.
|
|
50
49
|
//
|
|
51
50
|
// When `thread_context_collector_on_gc_finish` gets called, the cpu-time and wall-time spent during GC gets recorded
|
|
52
51
|
// into the global gc_tracking structure, and further samples are not affected. (The `cpu_time_at_previous_sample_ns`
|
|
@@ -75,15 +74,19 @@
|
|
|
75
74
|
|
|
76
75
|
#define THREAD_ID_LIMIT_CHARS 44 // Why 44? "#{2**64} (#{2**64})".size + 1 for \0
|
|
77
76
|
#define THREAD_INVOKE_LOCATION_LIMIT_CHARS 512
|
|
78
|
-
#define IS_WALL_TIME true
|
|
79
|
-
#define IS_NOT_WALL_TIME false
|
|
80
77
|
#define MISSING_TRACER_CONTEXT_KEY 0
|
|
78
|
+
|
|
79
|
+
// Used as markers in the code
|
|
80
|
+
#define OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED INT2FIX(1)
|
|
81
|
+
#define OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED INT2FIX(2)
|
|
82
|
+
|
|
81
83
|
#define TIME_BETWEEN_GC_EVENTS_NS MILLIS_AS_NS(10)
|
|
84
|
+
#define GVL_SUSPENDED ((uint64_t)1)
|
|
85
|
+
#define GVL_RUNNING ((uint64_t)0)
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
#define GVL_WAITING_ENABLED_EMPTY RUBY_FIXNUM_MAX
|
|
87
|
+
#define MAX(a, b) ((a) < (b) ? (b) : (a))
|
|
88
|
+
|
|
89
|
+
static ID dd_per_thread_context_id; // Hidden ivar (no @ prefix, inaccessible from Ruby)
|
|
87
90
|
|
|
88
91
|
static ID at_active_span_id; // id of :@active_span in Ruby
|
|
89
92
|
static ID at_active_trace_id; // id of :@active_trace in Ruby
|
|
@@ -107,12 +110,20 @@ static ID server_id; // id of :server in Ruby
|
|
|
107
110
|
static ID otel_context_storage_id; // id of :__opentelemetry_context_storage__ in Ruby
|
|
108
111
|
static ID otel_fiber_context_storage_id; // id of :@opentelemetry_context in Ruby
|
|
109
112
|
|
|
110
|
-
// This is
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
113
|
+
// This is mutable and gets set last-writer-wins style by
|
|
114
|
+
// `thread_context_collector_reset_all_per_thread_contexts`, which is called whenever
|
|
115
|
+
// profiling is starting or restarting.
|
|
116
|
+
//
|
|
117
|
+
// Note: We must be careful to not change this value while the profiler is still running,
|
|
118
|
+
// otherwise new threads can get the new value and cause profiling to stop with an
|
|
119
|
+
// exception due to the mismatched size.
|
|
120
|
+
//
|
|
121
|
+
// The initial value should be kept in sync with the default for DD_PROFILING_MAX_FRAMES
|
|
122
|
+
// in settings.rb. See `initialize_context` for details on why this is needed/used.
|
|
123
|
+
static uint16_t latest_max_frames = 400;
|
|
124
|
+
|
|
125
|
+
// Global tracepoint for RUBY_EVENT_THREAD_BEGIN. Created and enabled once when the first ThreadContext collector is initialized.
|
|
126
|
+
static VALUE thread_begin_tracepoint = Qnil;
|
|
116
127
|
|
|
117
128
|
typedef enum { OTEL_CONTEXT_ENABLED_FALSE, OTEL_CONTEXT_ENABLED_ONLY, OTEL_CONTEXT_ENABLED_BOTH } otel_context_enabled;
|
|
118
129
|
typedef enum { OTEL_CONTEXT_SOURCE_UNKNOWN, OTEL_CONTEXT_SOURCE_FIBER_IVAR, OTEL_CONTEXT_SOURCE_FIBER_LOCAL } otel_context_source;
|
|
@@ -122,28 +133,18 @@ typedef struct {
|
|
|
122
133
|
// Note: Places in this file that usually need to be changed when this struct is changed are tagged with
|
|
123
134
|
// "Update this when modifying state struct"
|
|
124
135
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
uint16_t max_frames;
|
|
128
|
-
// Hashmap <Thread Object, per_thread_context>
|
|
129
|
-
// Note: Be very careful when mutating this map, as it gets read e.g. in the middle of GC and signal handlers.
|
|
130
|
-
st_table *hash_map_per_thread_context;
|
|
136
|
+
// Output buffer for stack traces, passed to sample_thread()
|
|
137
|
+
sample_locations locations;
|
|
131
138
|
// Datadog::Profiling::StackRecorder instance
|
|
132
139
|
VALUE recorder_instance;
|
|
133
140
|
// If the tracer is available and enabled, this will be the fiber-local symbol for accessing its running context,
|
|
134
141
|
// to enable code hotspots and endpoint aggregation.
|
|
135
142
|
// When not available, this is set to MISSING_TRACER_CONTEXT_KEY.
|
|
136
143
|
ID tracer_context_key;
|
|
137
|
-
// Track how many regular samples we've taken. Does not include garbage collection samples.
|
|
138
|
-
// Currently **outside** of stats struct because we also use it to decide when to clean the contexts, and thus this
|
|
139
|
-
// is not (just) a stat.
|
|
140
|
-
unsigned int sample_count;
|
|
141
144
|
// Reusable array to get list of threads
|
|
142
145
|
VALUE thread_list_buffer;
|
|
143
146
|
// Used to omit endpoint names (retrieved from tracer) from collected data
|
|
144
147
|
bool endpoint_collection_enabled;
|
|
145
|
-
// Used to omit timestamps / timeline events from collected data
|
|
146
|
-
bool timeline_enabled;
|
|
147
148
|
// Used to control context collection
|
|
148
149
|
otel_context_enabled otel_context_enabled;
|
|
149
150
|
// Used to remember where otel context is being stored after we observe it the first time
|
|
@@ -153,19 +154,32 @@ typedef struct {
|
|
|
153
154
|
// Used to identify the main thread, to give it a fallback name
|
|
154
155
|
VALUE main_thread;
|
|
155
156
|
// Used when extracting trace identifiers from otel spans. Lazily initialized.
|
|
156
|
-
//
|
|
157
|
-
//
|
|
157
|
+
// * `OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED`: we've not needed it yet
|
|
158
|
+
// * `OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED`: a sample needed it, and it's waiting to be extracted
|
|
159
|
+
// * anything else: the extracted value, or Qnil if we tried to extract it and failed
|
|
158
160
|
VALUE otel_current_span_key;
|
|
159
161
|
// Used to enable native filenames in stack traces
|
|
160
162
|
bool native_filenames_enabled;
|
|
163
|
+
// Used to include class/module names in method names (e.g. "Foo#bar" instead of "bar")
|
|
164
|
+
bool show_classes;
|
|
161
165
|
// Used to cache native filename lookup results (Map[void *function_pointer, char *filename])
|
|
162
166
|
st_table *native_filenames_cache;
|
|
167
|
+
// Used to attribute overhead during sampling to this component
|
|
168
|
+
VALUE overhead_filename;
|
|
163
169
|
|
|
164
170
|
struct stats {
|
|
171
|
+
// Track how many regular samples we've taken. Does not include garbage collection samples.
|
|
172
|
+
unsigned int sample_count;
|
|
165
173
|
// Track how many garbage collection samples we've taken.
|
|
166
174
|
unsigned int gc_samples;
|
|
167
175
|
// See thread_context_collector_on_gc_start for details
|
|
168
176
|
unsigned int gc_samples_missed_due_to_missing_context;
|
|
177
|
+
// See thread_context_collector_sample_after_gc for details
|
|
178
|
+
unsigned int gc_samples_skipped_nothing_to_flush;
|
|
179
|
+
// How many per-thread samples were skipped because the thread has been continuously suspended
|
|
180
|
+
// (no GVL) since its previous sample, so its Ruby stack cannot have changed.
|
|
181
|
+
unsigned int inactive_thread_samples_skipped;
|
|
182
|
+
unsigned int profiler_thread_samples_skipped;
|
|
169
183
|
} stats;
|
|
170
184
|
|
|
171
185
|
struct {
|
|
@@ -177,16 +191,90 @@ typedef struct {
|
|
|
177
191
|
} gc_tracking;
|
|
178
192
|
} thread_context_collector_state;
|
|
179
193
|
|
|
180
|
-
// Tracks per-thread state
|
|
181
|
-
|
|
194
|
+
// Tracks per-thread state.
|
|
195
|
+
// This state is global and lives forever on the Ruby Thread (until the Thread is GC'd).
|
|
196
|
+
// The state is created early on for all threads on the main Ractor
|
|
197
|
+
// (enabling a TracePoint only enables it for the current Ractor).
|
|
198
|
+
// The state is either created when the Thread starts running (via the RUBY_EVENT_THREAD_BEGIN TracePoint),
|
|
199
|
+
// or via `thread_context_collector_reset_all_per_thread_contexts` when the profiler starts.
|
|
200
|
+
//
|
|
201
|
+
// Unfortunately that RUBY_EVENT_THREAD_BEGIN TracePoint still fires after some other events:
|
|
202
|
+
// * RUBY_INTERNAL_THREAD_EVENT_RESUMED for the Thread acquiring the GVL for the first time
|
|
203
|
+
// * an early SIGPROF calling handle_sampling_signal()
|
|
204
|
+
// * Ruby might check for interrupts and run postponed jobs (e.g. thread_context_collector_sample)
|
|
205
|
+
// * another RUBY_EVENT_THREAD_BEGIN TracePoint which might run before ours
|
|
206
|
+
// * etc
|
|
207
|
+
// For those cases we have to ignore those events and we cannot assume the state is always set,
|
|
208
|
+
// however this only matters for a very short period when a thread starts.
|
|
209
|
+
struct per_thread_context {
|
|
182
210
|
sampling_buffer sampling_buffer;
|
|
183
211
|
char thread_id[THREAD_ID_LIMIT_CHARS];
|
|
184
212
|
ddog_CharSlice thread_id_char_slice;
|
|
185
213
|
char thread_invoke_location[THREAD_INVOKE_LOCATION_LIMIT_CHARS];
|
|
186
214
|
ddog_CharSlice thread_invoke_location_char_slice;
|
|
187
215
|
thread_cpu_time_id thread_cpu_time_id;
|
|
188
|
-
long cpu_time_at_previous_sample_ns;
|
|
189
|
-
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 prepare_serialize.
|
|
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;
|
|
190
278
|
|
|
191
279
|
struct {
|
|
192
280
|
// Both of these fields are set by on_gc_start and kept until on_gc_finish is called.
|
|
@@ -194,7 +282,7 @@ typedef struct {
|
|
|
194
282
|
long cpu_time_at_start_ns;
|
|
195
283
|
long wall_time_at_start_ns;
|
|
196
284
|
} gc_tracking;
|
|
197
|
-
}
|
|
285
|
+
};
|
|
198
286
|
|
|
199
287
|
// Used to correlate profiles with traces
|
|
200
288
|
typedef struct {
|
|
@@ -212,50 +300,42 @@ typedef struct {
|
|
|
212
300
|
|
|
213
301
|
static void thread_context_collector_typed_data_mark(void *state_ptr);
|
|
214
302
|
static void thread_context_collector_typed_data_free(void *state_ptr);
|
|
215
|
-
static
|
|
216
|
-
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);
|
|
217
305
|
static VALUE _native_new(VALUE klass);
|
|
218
306
|
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
|
219
|
-
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE
|
|
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
|
-
VALUE stack_from_thread,
|
|
227
314
|
per_thread_context *thread_context,
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
long current_monotonic_wall_time_ns
|
|
315
|
+
long current_monotonic_wall_time_ns,
|
|
316
|
+
bool force_sample
|
|
231
317
|
);
|
|
232
318
|
static void trigger_sample_for_thread(
|
|
233
319
|
thread_context_collector_state *state,
|
|
234
|
-
VALUE
|
|
235
|
-
VALUE stack_from_thread,
|
|
320
|
+
VALUE thread_being_sampled,
|
|
236
321
|
per_thread_context *thread_context,
|
|
237
|
-
sampling_buffer* sampling_buffer,
|
|
238
322
|
sample_values values,
|
|
239
323
|
long current_monotonic_wall_time_ns,
|
|
240
324
|
ddog_CharSlice *ruby_vm_type,
|
|
241
325
|
ddog_CharSlice *class_name,
|
|
242
|
-
bool is_gvl_waiting_state
|
|
243
|
-
bool is_safe_to_allocate_objects
|
|
326
|
+
bool is_gvl_waiting_state
|
|
244
327
|
);
|
|
245
328
|
static VALUE _native_thread_list(VALUE self);
|
|
246
|
-
static
|
|
247
|
-
static per_thread_context *
|
|
248
|
-
static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
249
|
-
static void free_context(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, long current_monotonic_wall_time_ns);
|
|
331
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context, long current_monotonic_wall_time_ns);
|
|
250
332
|
static VALUE _native_inspect(VALUE self, VALUE collector_instance);
|
|
251
|
-
static VALUE
|
|
252
|
-
static
|
|
253
|
-
static VALUE stats_as_ruby_hash(thread_context_collector_state *state);
|
|
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);
|
|
254
335
|
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state);
|
|
255
|
-
static void remove_context_for_dead_threads(thread_context_collector_state *state);
|
|
256
|
-
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, st_data_t _argument);
|
|
257
336
|
static VALUE _native_per_thread_context(VALUE self, VALUE collector_instance);
|
|
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);
|
|
259
339
|
static long cpu_time_now_ns(per_thread_context *thread_context);
|
|
260
340
|
static long thread_id_for(VALUE thread);
|
|
261
341
|
static VALUE _native_stats(VALUE self, VALUE collector_instance);
|
|
@@ -263,8 +343,7 @@ static VALUE _native_gc_tracking(VALUE self, VALUE collector_instance);
|
|
|
263
343
|
static void trace_identifiers_for(
|
|
264
344
|
thread_context_collector_state *state,
|
|
265
345
|
VALUE thread,
|
|
266
|
-
trace_identifiers *trace_identifiers_result
|
|
267
|
-
bool is_safe_to_allocate_objects
|
|
346
|
+
trace_identifiers *trace_identifiers_result
|
|
268
347
|
);
|
|
269
348
|
static bool should_collect_resource(VALUE root_span);
|
|
270
349
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
@@ -278,40 +357,46 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
278
357
|
VALUE *root_span,
|
|
279
358
|
VALUE *numeric_span_id,
|
|
280
359
|
VALUE active_span,
|
|
281
|
-
VALUE otel_values
|
|
282
|
-
bool is_safe_to_allocate_objects
|
|
360
|
+
VALUE otel_values
|
|
283
361
|
);
|
|
284
362
|
static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE skipped_samples);
|
|
285
363
|
static bool handle_gvl_waiting(
|
|
286
364
|
thread_context_collector_state *state,
|
|
287
365
|
VALUE thread_being_sampled,
|
|
288
|
-
VALUE stack_from_thread,
|
|
289
366
|
per_thread_context *thread_context,
|
|
290
|
-
sampling_buffer* sampling_buffer,
|
|
291
367
|
long current_cpu_time_ns
|
|
292
368
|
);
|
|
369
|
+
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
370
|
+
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
293
371
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
294
|
-
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
295
372
|
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
296
|
-
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
297
|
-
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread
|
|
298
|
-
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns);
|
|
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);
|
|
299
375
|
#endif
|
|
376
|
+
static VALUE _native_apply_delta_to_time_at_previous_sample_ns(int argc, VALUE *argv, DDTRACE_UNUSED VALUE self);
|
|
377
|
+
static VALUE _native_apply_delta_to_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns);
|
|
300
378
|
static void otel_without_ddtrace_trace_identifiers_for(
|
|
301
379
|
thread_context_collector_state *state,
|
|
302
380
|
VALUE thread,
|
|
303
|
-
trace_identifiers *trace_identifiers_result
|
|
304
|
-
bool is_safe_to_allocate_objects
|
|
381
|
+
trace_identifiers *trace_identifiers_result
|
|
305
382
|
);
|
|
306
383
|
static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
|
|
384
|
+
static bool is_otel_current_span_key_needed(thread_context_collector_state *state);
|
|
307
385
|
static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
|
|
308
386
|
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
|
|
309
387
|
static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
310
|
-
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self
|
|
388
|
+
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
389
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
390
|
+
static VALUE _native_prepare_serialize(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
391
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
392
|
+
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
393
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample);
|
|
394
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused);
|
|
311
395
|
|
|
312
396
|
void collectors_thread_context_init(VALUE profiling_module) {
|
|
313
397
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
|
314
398
|
VALUE collectors_thread_context_class = rb_define_class_under(collectors_module, "ThreadContext", rb_cObject);
|
|
399
|
+
|
|
315
400
|
// Hosts methods used for testing the native code using RSpec
|
|
316
401
|
VALUE testing_module = rb_define_module_under(collectors_thread_context_class, "Testing");
|
|
317
402
|
|
|
@@ -328,11 +413,11 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
328
413
|
rb_define_singleton_method(collectors_thread_context_class, "_native_initialize", _native_initialize, -1);
|
|
329
414
|
rb_define_singleton_method(collectors_thread_context_class, "_native_inspect", _native_inspect, 1);
|
|
330
415
|
rb_define_singleton_method(collectors_thread_context_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
|
331
|
-
rb_define_singleton_method(testing_module, "_native_sample", _native_sample,
|
|
416
|
+
rb_define_singleton_method(testing_module, "_native_sample", _native_sample, 2);
|
|
332
417
|
rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
|
|
333
418
|
rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
|
|
334
419
|
rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
|
|
335
|
-
rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc,
|
|
420
|
+
rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 1);
|
|
336
421
|
rb_define_singleton_method(testing_module, "_native_thread_list", _native_thread_list, 0);
|
|
337
422
|
rb_define_singleton_method(testing_module, "_native_per_thread_context", _native_per_thread_context, 1);
|
|
338
423
|
rb_define_singleton_method(testing_module, "_native_stats", _native_stats, 1);
|
|
@@ -340,14 +425,20 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
340
425
|
rb_define_singleton_method(testing_module, "_native_new_empty_thread", _native_new_empty_thread, 0);
|
|
341
426
|
rb_define_singleton_method(testing_module, "_native_sample_skipped_allocation_samples", _native_sample_skipped_allocation_samples, 2);
|
|
342
427
|
rb_define_singleton_method(testing_module, "_native_system_epoch_time_now_ns", _native_system_epoch_time_now_ns, 1);
|
|
343
|
-
rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler,
|
|
428
|
+
rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler, 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_prepare_serialize", _native_prepare_serialize, 1);
|
|
433
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
434
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_released", _native_on_gvl_released, 1);
|
|
344
435
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
345
|
-
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
346
436
|
rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
|
|
347
|
-
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running,
|
|
348
|
-
rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running,
|
|
349
|
-
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);
|
|
437
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 2);
|
|
438
|
+
rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 2);
|
|
350
439
|
#endif
|
|
440
|
+
rb_define_singleton_method(testing_module, "_native_apply_delta_to_time_at_previous_sample_ns", _native_apply_delta_to_time_at_previous_sample_ns, -1);
|
|
441
|
+
rb_define_singleton_method(testing_module, "_native_apply_delta_to_gvl_waiting_at_for", _native_apply_delta_to_gvl_waiting_at_for, 2);
|
|
351
442
|
|
|
352
443
|
at_active_span_id = rb_intern_const("@active_span");
|
|
353
444
|
at_active_trace_id = rb_intern_const("@active_trace");
|
|
@@ -368,10 +459,12 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
368
459
|
otel_context_storage_id = rb_intern_const("__opentelemetry_context_storage__");
|
|
369
460
|
otel_fiber_context_storage_id = rb_intern_const("@opentelemetry_context");
|
|
370
461
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
462
|
+
dd_per_thread_context_id = rb_intern_const("dd_per_thread_context");
|
|
463
|
+
|
|
464
|
+
// This will raise if Ruby already ran out of thread-local keys
|
|
465
|
+
per_thread_context_tls_init();
|
|
466
|
+
|
|
467
|
+
rb_global_variable(&thread_begin_tracepoint);
|
|
375
468
|
|
|
376
469
|
gc_profiling_init();
|
|
377
470
|
}
|
|
@@ -396,10 +489,10 @@ static void thread_context_collector_typed_data_mark(void *state_ptr) {
|
|
|
396
489
|
|
|
397
490
|
// Update this when modifying state struct
|
|
398
491
|
rb_gc_mark(state->recorder_instance);
|
|
399
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_mark, 0 /* unused */);
|
|
400
492
|
rb_gc_mark(state->thread_list_buffer);
|
|
401
493
|
rb_gc_mark(state->main_thread);
|
|
402
494
|
rb_gc_mark(state->otel_current_span_key);
|
|
495
|
+
rb_gc_mark(state->overhead_filename);
|
|
403
496
|
}
|
|
404
497
|
|
|
405
498
|
static void thread_context_collector_typed_data_free(void *state_ptr) {
|
|
@@ -409,36 +502,54 @@ static void thread_context_collector_typed_data_free(void *state_ptr) {
|
|
|
409
502
|
|
|
410
503
|
// Important: Remember that we're only guaranteed to see here what's been set in _native_new, aka
|
|
411
504
|
// pointers that have been set NULL there may still be NULL here.
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
// Free each entry in the map
|
|
415
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
|
416
|
-
// ...and then the map
|
|
417
|
-
st_free_table(state->hash_map_per_thread_context);
|
|
505
|
+
sample_locations_free(&state->locations);
|
|
418
506
|
|
|
419
507
|
st_free_table(state->native_filenames_cache);
|
|
420
508
|
|
|
421
509
|
ruby_xfree(state);
|
|
422
510
|
}
|
|
423
511
|
|
|
424
|
-
//
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
512
|
+
// per_thread_context is wrapped in a TypedData Ruby object stored as an ivar on each Ruby Thread.
|
|
513
|
+
// This gives us automatic GC marking (for sampling_buffer iseq VALUEs) and lifecycle management.
|
|
514
|
+
static const rb_data_type_t per_thread_context_typed_data = {
|
|
515
|
+
.wrap_struct_name = "Datadog::Profiling::PerThreadContext",
|
|
516
|
+
.function = {
|
|
517
|
+
.dmark = per_thread_context_typed_data_mark,
|
|
518
|
+
.dfree = per_thread_context_typed_data_free,
|
|
519
|
+
.dsize = NULL,
|
|
520
|
+
},
|
|
521
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
522
|
+
};
|
|
428
523
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
524
|
+
static void per_thread_context_typed_data_mark(void *ctx_ptr) {
|
|
525
|
+
per_thread_context *ctx = (per_thread_context *) ctx_ptr;
|
|
526
|
+
if (sampling_buffer_needs_marking(&ctx->sampling_buffer)) {
|
|
527
|
+
sampling_buffer_mark(&ctx->sampling_buffer);
|
|
432
528
|
}
|
|
529
|
+
}
|
|
433
530
|
|
|
434
|
-
|
|
531
|
+
static void per_thread_context_typed_data_free(void *ctx_ptr) {
|
|
532
|
+
per_thread_context *ctx = (per_thread_context *) ctx_ptr;
|
|
533
|
+
sampling_buffer_free(&ctx->sampling_buffer);
|
|
534
|
+
free(ctx);
|
|
435
535
|
}
|
|
436
536
|
|
|
437
|
-
//
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
537
|
+
// Only for testing: removes the per-thread context without recreating it, so the thread has no context.
|
|
538
|
+
// This simulates situations where we observe a thread running before our RUBY_EVENT_THREAD_BEGIN tracepoint fires,
|
|
539
|
+
// so that we can test our `get_per_thread_context(...) => null` code paths.
|
|
540
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
541
|
+
check_frozen_thread(thread);
|
|
542
|
+
per_thread_context *ctx = get_per_thread_context(thread);
|
|
543
|
+
if (ctx != NULL) {
|
|
544
|
+
set_per_thread_context(thread, NULL);
|
|
545
|
+
rb_ivar_set(thread, dd_per_thread_context_id, Qnil);
|
|
546
|
+
}
|
|
547
|
+
return Qnil;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
551
|
+
thread_context_collector_reset_all_per_thread_contexts(collector_instance);
|
|
552
|
+
return Qnil;
|
|
442
553
|
}
|
|
443
554
|
|
|
444
555
|
static VALUE _native_new(VALUE klass) {
|
|
@@ -448,25 +559,20 @@ static VALUE _native_new(VALUE klass) {
|
|
|
448
559
|
// being leaked.
|
|
449
560
|
|
|
450
561
|
// Update this when modifying state struct
|
|
451
|
-
state->locations = NULL;
|
|
452
|
-
state->max_frames = 0;
|
|
453
|
-
state->hash_map_per_thread_context =
|
|
454
|
-
// "numtable" is an awful name, but TL;DR it's what should be used when keys are `VALUE`s.
|
|
455
|
-
st_init_numtable();
|
|
456
562
|
state->recorder_instance = Qnil;
|
|
457
563
|
state->tracer_context_key = MISSING_TRACER_CONTEXT_KEY;
|
|
458
564
|
VALUE thread_list_buffer = rb_ary_new();
|
|
459
565
|
state->thread_list_buffer = thread_list_buffer;
|
|
460
566
|
state->endpoint_collection_enabled = true;
|
|
461
|
-
state->timeline_enabled = true;
|
|
462
567
|
state->native_filenames_enabled = false;
|
|
568
|
+
state->show_classes = false;
|
|
463
569
|
state->native_filenames_cache = st_init_numtable();
|
|
464
570
|
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
|
|
465
571
|
state->otel_context_source = OTEL_CONTEXT_SOURCE_UNKNOWN;
|
|
466
572
|
state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
|
|
467
573
|
VALUE main_thread = rb_thread_main();
|
|
468
574
|
state->main_thread = main_thread;
|
|
469
|
-
state->otel_current_span_key =
|
|
575
|
+
state->otel_current_span_key = OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED;
|
|
470
576
|
state->gc_tracking.wall_time_at_previous_gc_ns = INVALID_TIME;
|
|
471
577
|
state->gc_tracking.wall_time_at_last_flushed_gc_event_ns = 0;
|
|
472
578
|
|
|
@@ -492,28 +598,29 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
492
598
|
VALUE max_frames = rb_hash_fetch(options, ID2SYM(rb_intern("max_frames")));
|
|
493
599
|
VALUE tracer_context_key = rb_hash_fetch(options, ID2SYM(rb_intern("tracer_context_key")));
|
|
494
600
|
VALUE endpoint_collection_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("endpoint_collection_enabled")));
|
|
495
|
-
VALUE timeline_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("timeline_enabled")));
|
|
496
|
-
VALUE waiting_for_gvl_threshold_ns = rb_hash_fetch(options, ID2SYM(rb_intern("waiting_for_gvl_threshold_ns")));
|
|
497
601
|
VALUE otel_context_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("otel_context_enabled")));
|
|
498
602
|
VALUE native_filenames_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("native_filenames_enabled")));
|
|
603
|
+
VALUE show_classes = rb_hash_fetch(options, ID2SYM(rb_intern("show_classes")));
|
|
604
|
+
VALUE overhead_filename = rb_hash_fetch(options, ID2SYM(rb_intern("overhead_filename")));
|
|
499
605
|
|
|
500
606
|
ENFORCE_TYPE(max_frames, T_FIXNUM);
|
|
501
607
|
ENFORCE_BOOLEAN(endpoint_collection_enabled);
|
|
502
|
-
ENFORCE_BOOLEAN(timeline_enabled);
|
|
503
|
-
ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
|
|
504
608
|
ENFORCE_BOOLEAN(native_filenames_enabled);
|
|
609
|
+
ENFORCE_BOOLEAN(show_classes);
|
|
610
|
+
ENFORCE_TYPE(overhead_filename, T_STRING);
|
|
611
|
+
|
|
612
|
+
uint16_t max_frame_int = sampling_buffer_check_max_frames(NUM2INT(max_frames));
|
|
505
613
|
|
|
506
614
|
thread_context_collector_state *state;
|
|
507
615
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
508
616
|
|
|
509
617
|
// Update this when modifying state struct
|
|
510
|
-
state->
|
|
511
|
-
state->locations = ruby_xcalloc(state->max_frames, sizeof(ddog_prof_Location));
|
|
512
|
-
// hash_map_per_thread_context is already initialized, nothing to do here
|
|
618
|
+
sample_locations_initialize(&state->locations, max_frame_int, show_classes == Qtrue);
|
|
513
619
|
state->recorder_instance = enforce_recorder_instance(recorder_instance);
|
|
514
620
|
state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
|
|
515
|
-
state->timeline_enabled = (timeline_enabled == Qtrue);
|
|
516
621
|
state->native_filenames_enabled = (native_filenames_enabled == Qtrue);
|
|
622
|
+
state->show_classes = (show_classes == Qtrue);
|
|
623
|
+
state->overhead_filename = overhead_filename;
|
|
517
624
|
if (otel_context_enabled == Qfalse || otel_context_enabled == Qnil) {
|
|
518
625
|
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
|
|
519
626
|
} else if (otel_context_enabled == ID2SYM(rb_intern("only"))) {
|
|
@@ -524,8 +631,6 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
524
631
|
raise_error(rb_eArgError, "Unexpected value for otel_context_enabled: %+" PRIsVALUE, otel_context_enabled);
|
|
525
632
|
}
|
|
526
633
|
|
|
527
|
-
global_waiting_for_gvl_threshold_ns = NUM2UINT(waiting_for_gvl_threshold_ns);
|
|
528
|
-
|
|
529
634
|
if (RTEST(tracer_context_key)) {
|
|
530
635
|
ENFORCE_TYPE(tracer_context_key, T_SYMBOL);
|
|
531
636
|
// Note about rb_to_id and dynamic symbols: calling `rb_to_id` prevents symbols from ever being garbage collected.
|
|
@@ -534,22 +639,29 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
534
639
|
state->tracer_context_key = rb_to_id(tracer_context_key);
|
|
535
640
|
}
|
|
536
641
|
|
|
642
|
+
if (thread_begin_tracepoint == Qnil) {
|
|
643
|
+
thread_begin_tracepoint = rb_tracepoint_new(Qnil, RUBY_EVENT_THREAD_BEGIN, on_thread_begin_event, NULL);
|
|
644
|
+
rb_tracepoint_enable(thread_begin_tracepoint);
|
|
645
|
+
}
|
|
646
|
+
|
|
537
647
|
return Qtrue;
|
|
538
648
|
}
|
|
539
649
|
|
|
540
650
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
541
651
|
// It SHOULD NOT be used for other purposes.
|
|
542
|
-
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE
|
|
652
|
+
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE allow_exception) {
|
|
543
653
|
ENFORCE_BOOLEAN(allow_exception);
|
|
544
654
|
|
|
545
|
-
if (!is_thread_alive(profiler_overhead_stack_thread)) raise_error(rb_eArgError, "Unexpected: profiler_overhead_stack_thread is not alive");
|
|
546
|
-
|
|
547
655
|
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
548
656
|
|
|
549
|
-
|
|
657
|
+
bool needs_otel_span_key =
|
|
658
|
+
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns());
|
|
550
659
|
|
|
551
660
|
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
|
552
661
|
|
|
662
|
+
// Same as the CpuAndWallTimeWorker does: this can't happen during a sample (and thus not inside the unsafe context)
|
|
663
|
+
if (needs_otel_span_key) thread_context_collector_resolve_otel_span_key_may_lose_gvl(collector_instance);
|
|
664
|
+
|
|
553
665
|
return Qtrue;
|
|
554
666
|
}
|
|
555
667
|
|
|
@@ -577,18 +689,54 @@ static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_ins
|
|
|
577
689
|
return Qtrue;
|
|
578
690
|
}
|
|
579
691
|
|
|
580
|
-
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
692
|
+
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
693
|
+
debug_enter_unsafe_context();
|
|
584
694
|
|
|
585
695
|
thread_context_collector_sample_after_gc(collector_instance);
|
|
586
696
|
|
|
587
|
-
|
|
697
|
+
debug_leave_unsafe_context();
|
|
588
698
|
|
|
589
699
|
return Qtrue;
|
|
590
700
|
}
|
|
591
701
|
|
|
702
|
+
// Record profiler sampling overhead as a placeholder stack
|
|
703
|
+
static void record_sampling_overhead(thread_context_collector_state *state, per_thread_context *current_thread_context) {
|
|
704
|
+
long wall_time_after_sampling = monotonic_wall_time_now_ns();
|
|
705
|
+
long cpu_time_after_sampling = cpu_time_now_ns(current_thread_context);
|
|
706
|
+
|
|
707
|
+
long overhead_cpu_time_ns = update_cpu_time_since_previous_sample(current_thread_context, cpu_time_after_sampling);
|
|
708
|
+
long overhead_wall_time_ns = update_wall_time_since_previous_sample(current_thread_context, wall_time_after_sampling);
|
|
709
|
+
|
|
710
|
+
ddog_prof_Label overhead_labels[] = {
|
|
711
|
+
{.key = DDOG_CHARSLICE_C("thread id"), .str = DDOG_CHARSLICE_C("0"), .num = 0},
|
|
712
|
+
{.key = DDOG_CHARSLICE_C("thread name"), .str = DDOG_CHARSLICE_C("Datadog::Profiling::Sampling"), .num = 0},
|
|
713
|
+
{.key = DDOG_CHARSLICE_C("state"), .str = DDOG_CHARSLICE_C("had cpu"), .num = 0},
|
|
714
|
+
{.key = DDOG_CHARSLICE_C("profiler overhead"), .num = 1},
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
int64_t end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, wall_time_after_sampling);
|
|
718
|
+
|
|
719
|
+
ddog_prof_Location overhead_location = {
|
|
720
|
+
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
|
721
|
+
.function = {
|
|
722
|
+
.name = DDOG_CHARSLICE_C("sampling"),
|
|
723
|
+
.filename = char_slice_from_ruby_string(state->overhead_filename),
|
|
724
|
+
},
|
|
725
|
+
.line = 0,
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
record_sample(
|
|
729
|
+
state->recorder_instance,
|
|
730
|
+
(ddog_prof_Slice_Location) {.ptr = &overhead_location, .len = 1},
|
|
731
|
+
(sample_values) {.cpu_time_ns = overhead_cpu_time_ns, .cpu_or_wall_samples = 1, .wall_time_ns = overhead_wall_time_ns},
|
|
732
|
+
(sample_labels) {
|
|
733
|
+
.labels = (ddog_prof_Slice_Label) {.ptr = overhead_labels, .len = sizeof(overhead_labels) / sizeof(overhead_labels[0])},
|
|
734
|
+
.state_label = NULL,
|
|
735
|
+
.end_timestamp_ns = end_timestamp_ns,
|
|
736
|
+
}
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
592
740
|
// This function gets called from the Collectors::CpuAndWallTimeWorker to trigger the actual sampling.
|
|
593
741
|
//
|
|
594
742
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
@@ -596,118 +744,134 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_
|
|
|
596
744
|
// Assumption 3: This function IS NOT called from a signal handler. This function is not async-signal-safe.
|
|
597
745
|
// Assumption 4: This function IS NOT called in a reentrant way.
|
|
598
746
|
// Assumption 5: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
747
|
+
// Assumption 6: When called while the profiler is active, `during_sample` MUST be set.
|
|
599
748
|
//
|
|
600
|
-
|
|
601
|
-
// (belonging to ddtrace), so that the overhead is visible in the profile rather than blamed on user code.
|
|
602
|
-
void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns, VALUE profiler_overhead_stack_thread) {
|
|
749
|
+
bool thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns) {
|
|
603
750
|
thread_context_collector_state *state;
|
|
604
751
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
605
752
|
|
|
606
753
|
VALUE current_thread = rb_thread_current();
|
|
607
|
-
per_thread_context *current_thread_context = get_or_create_context_for(current_thread,
|
|
608
|
-
|
|
754
|
+
per_thread_context *current_thread_context = get_or_create_context_for(current_thread, current_monotonic_wall_time_ns);
|
|
755
|
+
|
|
756
|
+
// Sample the current thread (which will use the current CPU-time) first.
|
|
757
|
+
// This ensures any CPU-time the profiler spends sampling (e.g. the rest of the function) is not wrongly blamed on this thread,
|
|
758
|
+
// and instead is separately accounted by `record_sampling_overhead()` below.
|
|
759
|
+
update_metrics_and_sample(
|
|
760
|
+
state,
|
|
761
|
+
current_thread,
|
|
762
|
+
current_thread_context,
|
|
763
|
+
current_monotonic_wall_time_ns,
|
|
764
|
+
false);
|
|
609
765
|
|
|
610
766
|
VALUE threads = thread_list(state);
|
|
611
767
|
|
|
612
768
|
const long thread_count = RARRAY_LEN(threads);
|
|
613
769
|
for (long i = 0; i < thread_count; i++) {
|
|
614
|
-
VALUE thread =
|
|
615
|
-
|
|
770
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
771
|
+
ENFORCE_THREAD(thread);
|
|
616
772
|
|
|
617
|
-
|
|
618
|
-
// blaming the time the profiler took on whatever's running on the thread right now
|
|
619
|
-
long current_cpu_time_ns = thread != current_thread ? cpu_time_now_ns(thread_context) : cpu_time_at_sample_start_for_current_thread;
|
|
773
|
+
if (thread == current_thread) continue; // Already sampled above
|
|
620
774
|
|
|
621
775
|
update_metrics_and_sample(
|
|
622
776
|
state,
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
current_cpu_time_ns,
|
|
628
|
-
current_monotonic_wall_time_ns
|
|
629
|
-
);
|
|
777
|
+
thread,
|
|
778
|
+
get_or_create_context_for(thread, current_monotonic_wall_time_ns),
|
|
779
|
+
current_monotonic_wall_time_ns,
|
|
780
|
+
false);
|
|
630
781
|
}
|
|
631
782
|
|
|
632
|
-
state->sample_count++;
|
|
783
|
+
state->stats.sample_count++;
|
|
633
784
|
|
|
634
|
-
//
|
|
635
|
-
//
|
|
636
|
-
|
|
785
|
+
// If the current thread is a profiler-internal thread, we don't use record_sampling_overhead()
|
|
786
|
+
// and accept the sampling overhead is attributed to the profiler-internal thread
|
|
787
|
+
// (both are under the same datadog group in the flamegraph).
|
|
788
|
+
// The reason we need to do this is profiler-internal threads are skipped during per-tick sampling
|
|
789
|
+
// so their timestamps are not updated. record_sampling_overhead() would see the stale previous-sample timestamps and
|
|
790
|
+
// attribute the entire sleep interval incorrectly as overhead instead of just the sampling work.
|
|
791
|
+
if (!current_thread_context->is_profiler_internal_thread) {
|
|
792
|
+
record_sampling_overhead(state, current_thread_context);
|
|
793
|
+
}
|
|
637
794
|
|
|
638
|
-
|
|
639
|
-
state,
|
|
640
|
-
/* thread_being_sampled: */ current_thread,
|
|
641
|
-
/* stack_from_thread: */ profiler_overhead_stack_thread,
|
|
642
|
-
current_thread_context,
|
|
643
|
-
// Here we use the overhead thread's sampling buffer so as to not invalidate the cache in the buffer of the thread being sampled
|
|
644
|
-
&get_or_create_context_for(profiler_overhead_stack_thread, state)->sampling_buffer,
|
|
645
|
-
cpu_time_now_ns(current_thread_context),
|
|
646
|
-
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
647
|
-
);
|
|
795
|
+
return is_otel_current_span_key_needed(state);
|
|
648
796
|
}
|
|
649
797
|
|
|
650
798
|
static void update_metrics_and_sample(
|
|
651
799
|
thread_context_collector_state *state,
|
|
652
800
|
VALUE thread_being_sampled,
|
|
653
|
-
VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
|
|
654
801
|
per_thread_context *thread_context,
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
long current_monotonic_wall_time_ns
|
|
802
|
+
long current_monotonic_wall_time_ns,
|
|
803
|
+
bool force_sample
|
|
658
804
|
) {
|
|
805
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
659
806
|
bool is_gvl_waiting_state =
|
|
660
|
-
handle_gvl_waiting(state, thread_being_sampled,
|
|
807
|
+
handle_gvl_waiting(state, thread_being_sampled, thread_context, current_cpu_time_ns);
|
|
661
808
|
|
|
662
|
-
|
|
663
|
-
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_time_since_previous_sample(
|
|
664
|
-
&thread_context->cpu_time_at_previous_sample_ns,
|
|
665
|
-
current_cpu_time_ns,
|
|
666
|
-
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
667
|
-
IS_NOT_WALL_TIME
|
|
668
|
-
);
|
|
669
|
-
|
|
670
|
-
long wall_time_elapsed_ns = update_time_since_previous_sample(
|
|
671
|
-
&thread_context->wall_time_at_previous_sample_ns,
|
|
672
|
-
current_monotonic_wall_time_ns,
|
|
673
|
-
// We explicitly pass in `INVALID_TIME` as an argument for `gc_start_time_ns` here because we don't want wall-time
|
|
674
|
-
// accounting to change during GC.
|
|
675
|
-
// E.g. if 60 seconds pass in the real world, 60 seconds of wall-time are recorded, regardless of the thread doing
|
|
676
|
-
// GC or not.
|
|
677
|
-
INVALID_TIME,
|
|
678
|
-
IS_WALL_TIME
|
|
679
|
-
);
|
|
809
|
+
if (skip_sample(state, thread_context, is_gvl_waiting_state, force_sample)) return;
|
|
680
810
|
|
|
681
811
|
// A thread enters "Waiting for GVL", well, as the name implies, without the GVL.
|
|
682
812
|
//
|
|
683
|
-
// As a consequence, it's possible
|
|
684
|
-
// on sampling, and thus for the
|
|
685
|
-
// to be
|
|
813
|
+
// As a consequence, it's possible for another thread to enter "Waiting for GVL" in parallel with the current thread working
|
|
814
|
+
// on sampling, and thus for the `current_monotonic_wall_time_ns` (which is recorded at the start of sampling)
|
|
815
|
+
// to be <= the time we observe for a "Waiting for GVL".
|
|
686
816
|
//
|
|
687
|
-
//
|
|
688
|
-
//
|
|
689
|
-
//
|
|
817
|
+
// When that happens, `handle_gvl_waiting` has already accounted for all wall-time up to the (later) start of the
|
|
818
|
+
// wait via the extra sample it pushes (see comments on that function), so there may be no remaining time
|
|
819
|
+
// to account for here.
|
|
820
|
+
// Thus, in this case, we don't want to produce a sample representing "Waiting for GVL" with a wall-time of 0 (nor negative!), and
|
|
690
821
|
// thus we skip creating such a sample.
|
|
691
|
-
if (is_gvl_waiting_state &&
|
|
822
|
+
if (is_gvl_waiting_state && current_monotonic_wall_time_ns <= thread_context->wall_time_at_previous_sample_ns) return;
|
|
692
823
|
// ...you may also wonder: is there any other situation where it makes sense to produce a sample with
|
|
693
824
|
// wall_time_elapsed_ns == 0? I believe that yes, because the sample still includes a timestamp and a stack, but we
|
|
694
825
|
// may revisit/change our minds on this in the future.
|
|
695
826
|
|
|
827
|
+
// Don't assign/update cpu during "Waiting for GVL"
|
|
828
|
+
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
829
|
+
|
|
830
|
+
long wall_time_elapsed_ns = update_wall_time_since_previous_sample(thread_context, current_monotonic_wall_time_ns);
|
|
831
|
+
|
|
696
832
|
trigger_sample_for_thread(
|
|
697
833
|
state,
|
|
698
834
|
thread_being_sampled,
|
|
699
|
-
stack_from_thread,
|
|
700
835
|
thread_context,
|
|
701
|
-
sampling_buffer,
|
|
702
836
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = wall_time_elapsed_ns},
|
|
703
837
|
current_monotonic_wall_time_ns,
|
|
704
838
|
NULL,
|
|
705
839
|
NULL,
|
|
706
|
-
is_gvl_waiting_state
|
|
707
|
-
/* is_safe_to_allocate_objects: */ true // We called from a context that's safe to run any regular code, including allocations
|
|
840
|
+
is_gvl_waiting_state
|
|
708
841
|
);
|
|
709
842
|
}
|
|
710
843
|
|
|
844
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample) {
|
|
845
|
+
if (!force_sample && thread_context->is_profiler_internal_thread) {
|
|
846
|
+
state->stats.profiler_thread_samples_skipped++;
|
|
847
|
+
return true;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Racy read but harmless, can only cause an extra sample
|
|
851
|
+
uint64_t gvl_state_change_count = thread_context->gvl_state_change_count;
|
|
852
|
+
|
|
853
|
+
// Skip this per-tick sample entirely when the thread does not have the GVL and did not acquire
|
|
854
|
+
// it since the previous sample: its Ruby-level stack has not changed. The skipped wall-time will
|
|
855
|
+
// be picked up by either by an extra sample when the thread acquires the GVL, or by
|
|
856
|
+
// prepare_serialize (using was_skipped_at_last_sample).
|
|
857
|
+
// The check is gated by `!is_gvl_waiting_state` so the existing "Waiting for GVL" machinery
|
|
858
|
+
// in handle_gvl_waiting (situation 1 extra sample, situation 2 regular sample) keeps running.
|
|
859
|
+
// TODO: we could probably also skip while "Waiting for GVL"
|
|
860
|
+
if (!is_gvl_waiting_state &&
|
|
861
|
+
!force_sample &&
|
|
862
|
+
(gvl_state_change_count & GVL_SUSPENDED) &&
|
|
863
|
+
gvl_state_change_count == thread_context->gvl_state_change_count_at_previous_sample) {
|
|
864
|
+
state->stats.inactive_thread_samples_skipped++;
|
|
865
|
+
thread_context->was_skipped_at_last_sample = true;
|
|
866
|
+
return true; // Do NOT update wall_time_at_previous_sample_ns or cpu_time_at_previous_sample_ns
|
|
867
|
+
} else {
|
|
868
|
+
// We are going to sample, update the state accordingly:
|
|
869
|
+
thread_context->gvl_state_change_count_at_previous_sample = gvl_state_change_count;
|
|
870
|
+
thread_context->was_skipped_at_last_sample = false;
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
711
875
|
// This function gets called when Ruby is about to start running the Garbage Collector on the current thread.
|
|
712
876
|
// It updates the per_thread_context of the current thread to include the current cpu/wall times, to be used to later
|
|
713
877
|
// create an event including the cpu/wall time spent in garbage collector work.
|
|
@@ -721,26 +885,27 @@ static void update_metrics_and_sample(
|
|
|
721
885
|
void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
722
886
|
thread_context_collector_state *state;
|
|
723
887
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return;
|
|
724
|
-
// This should never fail
|
|
888
|
+
// This should never fail when the above check passes
|
|
725
889
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
726
890
|
|
|
727
|
-
per_thread_context *thread_context =
|
|
891
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
728
892
|
|
|
729
|
-
//
|
|
730
|
-
//
|
|
731
|
-
// GC" is a rare enough case that we can just ignore it.
|
|
732
|
-
// We can always improve this later if we find that this happens often (and we have the counter to help us figure that out)!
|
|
893
|
+
// Context is created eagerly via on_thread_begin_event, so this should not normally be NULL.
|
|
894
|
+
// We can't get_or_create_context_for() here since we can't allocate (GC context).
|
|
733
895
|
if (thread_context == NULL) {
|
|
734
896
|
state->stats.gc_samples_missed_due_to_missing_context++;
|
|
735
897
|
return;
|
|
736
898
|
}
|
|
737
899
|
|
|
738
900
|
// 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
|
|
739
|
-
thread_context->gc_tracking.wall_time_at_start_ns = monotonic_wall_time_now_ns(
|
|
901
|
+
thread_context->gc_tracking.wall_time_at_start_ns = monotonic_wall_time_now_ns();
|
|
740
902
|
thread_context->gc_tracking.cpu_time_at_start_ns = cpu_time_now_ns(thread_context);
|
|
741
903
|
}
|
|
742
904
|
|
|
743
905
|
// This function gets called when Ruby has finished running the Garbage Collector on the current thread.
|
|
906
|
+
// (Note: The `GC_EXIT` event gets emitted by the VM while `during_gc` is still set to true -- the flag
|
|
907
|
+
// is only cleared after the event gets emitted -- see top of the file for more details)
|
|
908
|
+
//
|
|
744
909
|
// It records the cpu/wall-time observed during GC, which will be used to later
|
|
745
910
|
// create an event including the cpu/wall time spent from the start of garbage collector work until now.
|
|
746
911
|
//
|
|
@@ -754,19 +919,19 @@ __attribute__((warn_unused_result))
|
|
|
754
919
|
bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
755
920
|
thread_context_collector_state *state;
|
|
756
921
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
|
757
|
-
// This should never fail
|
|
922
|
+
// This should never fail when the above check passes
|
|
758
923
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
759
924
|
|
|
760
|
-
per_thread_context *thread_context =
|
|
925
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
761
926
|
|
|
762
|
-
//
|
|
763
|
-
// how often this happens -- see on_gc_start.
|
|
927
|
+
// Context is created eagerly, so this should not normally be NULL (see on_gc_start).
|
|
764
928
|
if (thread_context == NULL) return false;
|
|
765
929
|
|
|
766
930
|
long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
|
|
767
931
|
long wall_time_at_start_ns = thread_context->gc_tracking.wall_time_at_start_ns;
|
|
768
932
|
|
|
769
|
-
|
|
933
|
+
// Both fields are always set and cleared together, so checking one is enough
|
|
934
|
+
if (cpu_time_at_start_ns == INVALID_TIME) {
|
|
770
935
|
// If this happened, it means that on_gc_start was either never called for the thread OR it was called but no thread
|
|
771
936
|
// context existed at the time. The former can be the result of a bug, but since we can't distinguish them, we just
|
|
772
937
|
// do nothing.
|
|
@@ -779,16 +944,13 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
779
944
|
|
|
780
945
|
// Here we record the wall-time second and in on_gc_start we record it first to try to avoid having wall-time be slightly < cpu-time
|
|
781
946
|
long cpu_time_at_finish_ns = cpu_time_now_ns(thread_context);
|
|
782
|
-
long wall_time_at_finish_ns = monotonic_wall_time_now_ns(
|
|
783
|
-
|
|
784
|
-
// If our end timestamp is not OK, we bail out
|
|
785
|
-
if (wall_time_at_finish_ns == 0) return false;
|
|
947
|
+
long wall_time_at_finish_ns = monotonic_wall_time_now_ns();
|
|
786
948
|
|
|
787
949
|
long gc_cpu_time_elapsed_ns = cpu_time_at_finish_ns - cpu_time_at_start_ns;
|
|
788
950
|
long gc_wall_time_elapsed_ns = wall_time_at_finish_ns - wall_time_at_start_ns;
|
|
789
951
|
|
|
790
|
-
//
|
|
791
|
-
//
|
|
952
|
+
// @ivoanjo: We don't expect these clocks to ever go backwards (and in other code paths we enforce that with an exception)
|
|
953
|
+
// but during GC it's really really awkward to signal back failures so for now I left the clamping.
|
|
792
954
|
gc_cpu_time_elapsed_ns = long_max_of(gc_cpu_time_elapsed_ns, 0);
|
|
793
955
|
gc_wall_time_elapsed_ns = long_max_of(gc_wall_time_elapsed_ns, 0);
|
|
794
956
|
|
|
@@ -801,9 +963,9 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
801
963
|
state->gc_tracking.accumulated_wall_time_ns += gc_wall_time_elapsed_ns;
|
|
802
964
|
state->gc_tracking.wall_time_at_previous_gc_ns = wall_time_at_finish_ns;
|
|
803
965
|
|
|
804
|
-
// Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample
|
|
805
|
-
// We don't
|
|
806
|
-
//
|
|
966
|
+
// Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample.
|
|
967
|
+
// We don't do the same for wall-time, because GC is just like any other reason a thread didn't make
|
|
968
|
+
// progress -- time always goes forward regardless of the thread making progress on what it wanted.
|
|
807
969
|
if (thread_context->cpu_time_at_previous_sample_ns != INVALID_TIME) {
|
|
808
970
|
thread_context->cpu_time_at_previous_sample_ns += gc_cpu_time_elapsed_ns;
|
|
809
971
|
}
|
|
@@ -811,10 +973,10 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
811
973
|
// Let the caller know if it should schedule a flush or not. Returning true every time would cause a lot of overhead
|
|
812
974
|
// on the application (see GC tracking introduction at the top of the file), so instead we try to accumulate a few
|
|
813
975
|
// samples first.
|
|
814
|
-
bool
|
|
976
|
+
bool over_flush_time_threshold =
|
|
815
977
|
(wall_time_at_finish_ns - state->gc_tracking.wall_time_at_last_flushed_gc_event_ns) >= TIME_BETWEEN_GC_EVENTS_NS;
|
|
816
978
|
|
|
817
|
-
if (
|
|
979
|
+
if (over_flush_time_threshold) {
|
|
818
980
|
return true;
|
|
819
981
|
} else {
|
|
820
982
|
return gc_profiling_has_major_gc_finished();
|
|
@@ -829,12 +991,20 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
829
991
|
// Assumption 2: This function is allowed to raise exceptions. Caller is responsible for handling them, if needed.
|
|
830
992
|
// Assumption 3: Unlike `on_gc_start` and `on_gc_finish`, this method is allowed to allocate memory as needed.
|
|
831
993
|
// Assumption 4: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
994
|
+
// Assumption 5: When called while the profiler is active, `during_sample` MUST be set.
|
|
832
995
|
VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
833
996
|
thread_context_collector_state *state;
|
|
834
997
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
835
998
|
|
|
836
999
|
if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
|
|
837
|
-
|
|
1000
|
+
// Rarely, we might be called with nothing to do, as an earlier call already flushed the needed info.
|
|
1001
|
+
// This is because Ruby clears the "pending" flag for an entire batch of postponed jobs BEFORE running any of them
|
|
1002
|
+
// (see `rb_postponed_job_flush`). Thus, if another GC happens while an earlier job in the postponed batch is
|
|
1003
|
+
// running (and other parts of the profiler such as the heap profiler can trigger this)
|
|
1004
|
+
// then `on_gc_finish` records that data and asks for a second flush, even though Ruby has not yet run the
|
|
1005
|
+
// first one. Because of this, Ruby can call us once more with nothing left to do.
|
|
1006
|
+
state->stats.gc_samples_skipped_nothing_to_flush++;
|
|
1007
|
+
return Qnil;
|
|
838
1008
|
}
|
|
839
1009
|
|
|
840
1010
|
int max_labels_needed_for_gc = 7; // Magic number gets validated inside gc_profiling_set_metadata
|
|
@@ -844,11 +1014,7 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
844
1014
|
ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = label_pos};
|
|
845
1015
|
|
|
846
1016
|
// The end_timestamp_ns is treated specially by libdatadog and that's why it's not added as a ddog_prof_Label
|
|
847
|
-
int64_t end_timestamp_ns =
|
|
848
|
-
|
|
849
|
-
if (state->timeline_enabled) {
|
|
850
|
-
end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, state->gc_tracking.wall_time_at_previous_gc_ns);
|
|
851
|
-
}
|
|
1017
|
+
int64_t end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, state->gc_tracking.wall_time_at_previous_gc_ns);
|
|
852
1018
|
|
|
853
1019
|
record_placeholder_stack(
|
|
854
1020
|
state->recorder_instance,
|
|
@@ -872,33 +1038,37 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
872
1038
|
|
|
873
1039
|
state->stats.gc_samples++;
|
|
874
1040
|
|
|
875
|
-
// Let recorder do any cleanup/updates it requires after a GC step.
|
|
876
|
-
recorder_after_gc_step(state->recorder_instance);
|
|
877
|
-
|
|
878
1041
|
// Return a VALUE to make it easier to call this function from Ruby APIs that expect a return value (such as rb_rescue2)
|
|
879
1042
|
return Qnil;
|
|
880
1043
|
}
|
|
881
1044
|
|
|
1045
|
+
// Let the recorder do any cleanup/updates it requires after a GC step.
|
|
1046
|
+
//
|
|
1047
|
+
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
1048
|
+
// Assumption 2: This function is allowed to raise exceptions. Caller is responsible for handling them, if needed.
|
|
1049
|
+
VALUE thread_context_collector_heap_update_may_lose_gvl(VALUE self_instance) {
|
|
1050
|
+
thread_context_collector_state *state;
|
|
1051
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1052
|
+
|
|
1053
|
+
recorder_heap_update_may_lose_gvl(state->recorder_instance);
|
|
1054
|
+
|
|
1055
|
+
return Qnil; // for rb_rescue2
|
|
1056
|
+
}
|
|
1057
|
+
|
|
882
1058
|
static void trigger_sample_for_thread(
|
|
883
1059
|
thread_context_collector_state *state,
|
|
884
|
-
VALUE
|
|
885
|
-
VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
|
|
1060
|
+
VALUE thread_being_sampled,
|
|
886
1061
|
per_thread_context *thread_context,
|
|
887
|
-
sampling_buffer* sampling_buffer,
|
|
888
1062
|
sample_values values,
|
|
889
1063
|
long current_monotonic_wall_time_ns,
|
|
890
1064
|
// These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
|
|
891
1065
|
ddog_CharSlice *ruby_vm_type,
|
|
892
1066
|
ddog_CharSlice *class_name,
|
|
893
|
-
bool is_gvl_waiting_state
|
|
894
|
-
// If the Ruby VM is at a state that can allocate objects safely, or not. Added for allocation profiling: we're not
|
|
895
|
-
// allowed to allocate objects (or raise exceptions) when inside the NEWOBJ tracepoint.
|
|
896
|
-
bool is_safe_to_allocate_objects
|
|
1067
|
+
bool is_gvl_waiting_state
|
|
897
1068
|
) {
|
|
898
1069
|
int max_label_count =
|
|
899
1070
|
1 + // thread id
|
|
900
1071
|
1 + // thread name
|
|
901
|
-
1 + // profiler overhead
|
|
902
1072
|
2 + // ruby vm type and allocation class
|
|
903
1073
|
1 + // state (only set for cpu/wall-time samples)
|
|
904
1074
|
2; // local root span id and span id
|
|
@@ -910,13 +1080,13 @@ static void trigger_sample_for_thread(
|
|
|
910
1080
|
.str = thread_context->thread_id_char_slice
|
|
911
1081
|
};
|
|
912
1082
|
|
|
913
|
-
VALUE thread_name = thread_name_for(
|
|
1083
|
+
VALUE thread_name = thread_name_for(thread_being_sampled);
|
|
914
1084
|
if (thread_name != Qnil) {
|
|
915
1085
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
916
1086
|
.key = DDOG_CHARSLICE_C("thread name"),
|
|
917
1087
|
.str = char_slice_from_ruby_string(thread_name)
|
|
918
1088
|
};
|
|
919
|
-
} else if (
|
|
1089
|
+
} else if (thread_being_sampled == state->main_thread) { // Threads are often not named, but we can have a nice fallback for this special thread
|
|
920
1090
|
ddog_CharSlice main_thread_name = DDOG_CHARSLICE_C("main");
|
|
921
1091
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
922
1092
|
.key = DDOG_CHARSLICE_C("thread name"),
|
|
@@ -932,11 +1102,11 @@ static void trigger_sample_for_thread(
|
|
|
932
1102
|
}
|
|
933
1103
|
|
|
934
1104
|
trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
|
|
935
|
-
trace_identifiers_for(state,
|
|
1105
|
+
trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result);
|
|
936
1106
|
|
|
937
1107
|
if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
|
|
938
1108
|
// If we couldn't get something with ddtrace, let's see if we can get some trace identifiers from opentelemetry directly
|
|
939
|
-
otel_without_ddtrace_trace_identifiers_for(state,
|
|
1109
|
+
otel_without_ddtrace_trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result);
|
|
940
1110
|
}
|
|
941
1111
|
|
|
942
1112
|
if (trace_identifiers_result.valid) {
|
|
@@ -961,13 +1131,6 @@ static void trigger_sample_for_thread(
|
|
|
961
1131
|
}
|
|
962
1132
|
}
|
|
963
1133
|
|
|
964
|
-
if (thread != stack_from_thread) {
|
|
965
|
-
labels[label_pos++] = (ddog_prof_Label) {
|
|
966
|
-
.key = DDOG_CHARSLICE_C("profiler overhead"),
|
|
967
|
-
.num = 1
|
|
968
|
-
};
|
|
969
|
-
}
|
|
970
|
-
|
|
971
1134
|
if (ruby_vm_type != NULL) {
|
|
972
1135
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
973
1136
|
.key = DDOG_CHARSLICE_C("ruby vm type"),
|
|
@@ -1008,13 +1171,14 @@ static void trigger_sample_for_thread(
|
|
|
1008
1171
|
|
|
1009
1172
|
// The end_timestamp_ns is treated specially by libdatadog and that's why it's not added as a ddog_prof_Label
|
|
1010
1173
|
int64_t end_timestamp_ns = 0;
|
|
1011
|
-
if (
|
|
1174
|
+
if (current_monotonic_wall_time_ns != INVALID_TIME) {
|
|
1012
1175
|
end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, current_monotonic_wall_time_ns);
|
|
1013
1176
|
}
|
|
1014
1177
|
|
|
1015
1178
|
sample_thread(
|
|
1016
|
-
|
|
1017
|
-
sampling_buffer,
|
|
1179
|
+
thread_being_sampled,
|
|
1180
|
+
&thread_context->sampling_buffer,
|
|
1181
|
+
state->locations,
|
|
1018
1182
|
state->recorder_instance,
|
|
1019
1183
|
values,
|
|
1020
1184
|
(sample_labels) {
|
|
@@ -1024,7 +1188,8 @@ static void trigger_sample_for_thread(
|
|
|
1024
1188
|
.is_gvl_waiting_state = is_gvl_waiting_state,
|
|
1025
1189
|
},
|
|
1026
1190
|
state->native_filenames_enabled,
|
|
1027
|
-
state->native_filenames_cache
|
|
1191
|
+
state->native_filenames_cache,
|
|
1192
|
+
state->show_classes
|
|
1028
1193
|
);
|
|
1029
1194
|
}
|
|
1030
1195
|
|
|
@@ -1042,30 +1207,37 @@ static VALUE _native_thread_list(DDTRACE_UNUSED VALUE _self) {
|
|
|
1042
1207
|
return result;
|
|
1043
1208
|
}
|
|
1044
1209
|
|
|
1045
|
-
static
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
|
|
1050
|
-
thread_context = (per_thread_context*) value_context;
|
|
1051
|
-
} else {
|
|
1052
|
-
thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
|
|
1053
|
-
initialize_context(thread, thread_context, state);
|
|
1054
|
-
st_insert(state->hash_map_per_thread_context, (st_data_t) thread, (st_data_t) thread_context);
|
|
1210
|
+
static void check_frozen_thread(VALUE thread) {
|
|
1211
|
+
if (RB_OBJ_FROZEN(thread)) {
|
|
1212
|
+
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);
|
|
1055
1213
|
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// See the docs on struct per_thread_context.
|
|
1217
|
+
// This allocates a Ruby object and therefore needs the GVL and is not safe to call from RUBY_INTERNAL_EVENT_* hooks.
|
|
1218
|
+
static per_thread_context *get_or_create_context_for(VALUE thread, long current_monotonic_wall_time_ns) {
|
|
1219
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1220
|
+
if (thread_context != NULL) return thread_context;
|
|
1221
|
+
|
|
1222
|
+
check_frozen_thread(thread);
|
|
1223
|
+
|
|
1224
|
+
thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
|
|
1225
|
+
initialize_context(thread, thread_context, current_monotonic_wall_time_ns);
|
|
1056
1226
|
|
|
1227
|
+
VALUE wrapper = TypedData_Wrap_Struct(rb_cObject, &per_thread_context_typed_data, thread_context);
|
|
1228
|
+
rb_ivar_set(thread, dd_per_thread_context_id, wrapper);
|
|
1229
|
+
|
|
1230
|
+
set_per_thread_context(thread, thread_context);
|
|
1057
1231
|
return thread_context;
|
|
1058
1232
|
}
|
|
1059
1233
|
|
|
1060
|
-
static
|
|
1061
|
-
|
|
1062
|
-
st_data_t value_context = 0;
|
|
1234
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
|
|
1235
|
+
if (!ddtrace_rb_ractor_main_p()) return;
|
|
1063
1236
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
}
|
|
1237
|
+
VALUE thread = rb_tracearg_self(rb_tracearg_from_tracepoint(tracepoint_data));
|
|
1238
|
+
ENFORCE_THREAD(thread);
|
|
1067
1239
|
|
|
1068
|
-
|
|
1240
|
+
get_or_create_context_for(thread, monotonic_wall_time_now_ns());
|
|
1069
1241
|
}
|
|
1070
1242
|
|
|
1071
1243
|
#define LOGGING_GEM_PATH "/lib/logging/diagnostic_context.rb"
|
|
@@ -1089,8 +1261,11 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
|
|
|
1089
1261
|
return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
|
|
1090
1262
|
}
|
|
1091
1263
|
|
|
1092
|
-
static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
1093
|
-
|
|
1264
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context, long current_monotonic_wall_time_ns) {
|
|
1265
|
+
// We always create per_thread_context's with latest_max_frames; that value is kept in sync with the
|
|
1266
|
+
// active profiler's max_frames by the global reset that runs when profiling starts
|
|
1267
|
+
// so we expect to always see here the latest correct value to be used.
|
|
1268
|
+
sampling_buffer_initialize(&thread_context->sampling_buffer, latest_max_frames);
|
|
1094
1269
|
|
|
1095
1270
|
snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
|
|
1096
1271
|
thread_context->thread_id_char_slice = (ddog_CharSlice) {.ptr = thread_context->thread_id, .len = strlen(thread_context->thread_id)};
|
|
@@ -1109,7 +1284,7 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
|
1109
1284
|
} else {
|
|
1110
1285
|
snprintf(thread_context->thread_invoke_location, THREAD_INVOKE_LOCATION_LIMIT_CHARS, "%s", "(Unnamed thread)");
|
|
1111
1286
|
}
|
|
1112
|
-
} else if (thread !=
|
|
1287
|
+
} else if (thread != rb_thread_main()) {
|
|
1113
1288
|
// If the first function of a thread is native code, there won't be an invoke location, so we use this fallback.
|
|
1114
1289
|
// 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
|
|
1115
1290
|
// symbol attached to it.
|
|
@@ -1123,32 +1298,58 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
|
1123
1298
|
|
|
1124
1299
|
thread_context->thread_cpu_time_id = thread_cpu_time_id_for(thread);
|
|
1125
1300
|
|
|
1126
|
-
|
|
1127
|
-
thread_context->cpu_time_at_previous_sample_ns =
|
|
1128
|
-
thread_context->wall_time_at_previous_sample_ns = INVALID_TIME;
|
|
1301
|
+
thread_context->wall_time_at_previous_sample_ns = current_monotonic_wall_time_ns;
|
|
1302
|
+
thread_context->cpu_time_at_previous_sample_ns = cpu_time_now_ns(thread_context);
|
|
1129
1303
|
|
|
1130
1304
|
// These will only be used during a GC operation
|
|
1131
1305
|
thread_context->gc_tracking.cpu_time_at_start_ns = INVALID_TIME;
|
|
1132
1306
|
thread_context->gc_tracking.wall_time_at_start_ns = INVALID_TIME;
|
|
1133
1307
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
// kind of synchronization (e.g. by threads without the GVL).
|
|
1137
|
-
//
|
|
1138
|
-
// We set this marker here for two purposes:
|
|
1139
|
-
// * To make sure there's no stale data from a previous execution of the profiler.
|
|
1140
|
-
// * To mark threads that are actually being profiled
|
|
1141
|
-
//
|
|
1142
|
-
// (Setting this is potentially a race, but what we want is to avoid _stale_ data, so
|
|
1143
|
-
// if this gets set concurrently with context initialization, then such a value will belong
|
|
1144
|
-
// to the current profiler instance, so that's OK)
|
|
1145
|
-
gvl_profiling_state_thread_object_set(thread, GVL_WAITING_ENABLED_EMPTY);
|
|
1146
|
-
#endif
|
|
1308
|
+
thread_context->gvl_waiting_at = 0;
|
|
1309
|
+
thread_context->gvl_state_change_count = 0;
|
|
1147
1310
|
}
|
|
1148
1311
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1312
|
+
// This MUST be called before profiling starts, so that a new profiler session starts from a fresh state and never
|
|
1313
|
+
// observes or includes any leftover stale state from a previous session.
|
|
1314
|
+
// Such a call MUST happen while the CpuAndWallTimeWorker is stopped (e.g. no tracepoints active, no signals
|
|
1315
|
+
// triggering samples, no gvl hooks, etc).
|
|
1316
|
+
//
|
|
1317
|
+
// It updates the global `latest_max_frames` from the given (latest) ThreadContext and (re)creates every per-thread
|
|
1318
|
+
// context's sampling buffer sized accordingly, so the buffers always match the collector that's about to start
|
|
1319
|
+
// sampling -- even if a previous session used a different max_frames.
|
|
1320
|
+
void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance) {
|
|
1321
|
+
thread_context_collector_state *state;
|
|
1322
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1323
|
+
|
|
1324
|
+
// Update global max frames to be used when allocating sampling buffers
|
|
1325
|
+
if (state->locations.len == 0) {
|
|
1326
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected locations.len == 0. Is this ThreadContext not initialized?");
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
latest_max_frames = state->locations.len;
|
|
1330
|
+
|
|
1331
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns();
|
|
1332
|
+
|
|
1333
|
+
VALUE threads = thread_list(state);
|
|
1334
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
1335
|
+
for (long i = 0; i < thread_count; i++) {
|
|
1336
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
1337
|
+
ENFORCE_THREAD(thread);
|
|
1338
|
+
|
|
1339
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1340
|
+
if (thread_context != NULL) {
|
|
1341
|
+
bool is_profiler_internal_thread = thread_context->is_profiler_internal_thread;
|
|
1342
|
+
|
|
1343
|
+
sampling_buffer_free(&thread_context->sampling_buffer);
|
|
1344
|
+
memset(thread_context, 0, sizeof(per_thread_context));
|
|
1345
|
+
initialize_context(thread, thread_context, current_monotonic_wall_time_ns);
|
|
1346
|
+
|
|
1347
|
+
thread_context->is_profiler_internal_thread = is_profiler_internal_thread;
|
|
1348
|
+
} else {
|
|
1349
|
+
// If thread didn't have a context, let's trigger its creation
|
|
1350
|
+
get_or_create_context_for(thread, current_monotonic_wall_time_ns);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1152
1353
|
}
|
|
1153
1354
|
|
|
1154
1355
|
static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
@@ -1158,16 +1359,14 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
|
1158
1359
|
VALUE result = rb_str_new2(" (native state)");
|
|
1159
1360
|
|
|
1160
1361
|
// Update this when modifying state struct
|
|
1161
|
-
rb_str_concat(result, rb_sprintf(" max_frames=%d", state->
|
|
1162
|
-
rb_str_concat(result, rb_sprintf(" hash_map_per_thread_context=%"PRIsVALUE, per_thread_context_st_table_as_ruby_hash(state)));
|
|
1362
|
+
rb_str_concat(result, rb_sprintf(" max_frames=%d", state->locations.len));
|
|
1163
1363
|
rb_str_concat(result, rb_sprintf(" recorder_instance=%"PRIsVALUE, state->recorder_instance));
|
|
1164
1364
|
VALUE tracer_context_key = state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY ? Qnil : ID2SYM(state->tracer_context_key);
|
|
1165
1365
|
rb_str_concat(result, rb_sprintf(" tracer_context_key=%+"PRIsVALUE, tracer_context_key));
|
|
1166
|
-
rb_str_concat(result, rb_sprintf("
|
|
1167
|
-
rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_as_ruby_hash(state)));
|
|
1366
|
+
rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_to_ruby_hash(state, rb_hash_new())));
|
|
1168
1367
|
rb_str_concat(result, rb_sprintf(" endpoint_collection_enabled=%"PRIsVALUE, state->endpoint_collection_enabled ? Qtrue : Qfalse));
|
|
1169
|
-
rb_str_concat(result, rb_sprintf(" timeline_enabled=%"PRIsVALUE, state->timeline_enabled ? Qtrue : Qfalse));
|
|
1170
1368
|
rb_str_concat(result, rb_sprintf(" native_filenames_enabled=%"PRIsVALUE, state->native_filenames_enabled ? Qtrue : Qfalse));
|
|
1369
|
+
rb_str_concat(result, rb_sprintf(" show_classes=%"PRIsVALUE, state->show_classes ? Qtrue : Qfalse));
|
|
1171
1370
|
// Note: `st_table_size()` is available from Ruby 3.2+ but not before
|
|
1172
1371
|
rb_str_concat(result, rb_sprintf(" native_filenames_cache_size=%zu", state->native_filenames_cache->num_entries));
|
|
1173
1372
|
rb_str_concat(result, rb_sprintf(" otel_context_enabled=%d", state->otel_context_enabled));
|
|
@@ -1179,53 +1378,51 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
|
1179
1378
|
rb_str_concat(result, rb_sprintf(" main_thread=%"PRIsVALUE, state->main_thread));
|
|
1180
1379
|
rb_str_concat(result, rb_sprintf(" gc_tracking=%"PRIsVALUE, gc_tracking_as_ruby_hash(state)));
|
|
1181
1380
|
rb_str_concat(result, rb_sprintf(" otel_current_span_key=%"PRIsVALUE, state->otel_current_span_key));
|
|
1182
|
-
rb_str_concat(result, rb_sprintf(" global_waiting_for_gvl_threshold_ns=%u", global_waiting_for_gvl_threshold_ns));
|
|
1183
|
-
|
|
1184
|
-
return result;
|
|
1185
|
-
}
|
|
1186
1381
|
|
|
1187
|
-
static VALUE per_thread_context_st_table_as_ruby_hash(thread_context_collector_state *state) {
|
|
1188
|
-
VALUE result = rb_hash_new();
|
|
1189
|
-
st_foreach(state->hash_map_per_thread_context, per_thread_context_as_ruby_hash, result);
|
|
1190
1382
|
return result;
|
|
1191
1383
|
}
|
|
1192
1384
|
|
|
1193
|
-
static
|
|
1194
|
-
VALUE thread = (VALUE) key_thread;
|
|
1195
|
-
per_thread_context *thread_context = (per_thread_context*) value_context;
|
|
1196
|
-
VALUE result = (VALUE) result_hash;
|
|
1385
|
+
static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context) {
|
|
1197
1386
|
VALUE context_as_hash = rb_hash_new();
|
|
1198
|
-
rb_hash_aset(result, thread, context_as_hash);
|
|
1199
1387
|
|
|
1200
1388
|
VALUE arguments[] = {
|
|
1201
1389
|
ID2SYM(rb_intern("thread_id")), /* => */ rb_str_new2(thread_context->thread_id),
|
|
1202
1390
|
ID2SYM(rb_intern("thread_invoke_location")), /* => */ rb_str_new2(thread_context->thread_invoke_location),
|
|
1203
1391
|
ID2SYM(rb_intern("thread_cpu_time_id_valid?")), /* => */ thread_context->thread_cpu_time_id.valid ? Qtrue : Qfalse,
|
|
1204
|
-
|
|
1392
|
+
#ifdef __APPLE__
|
|
1393
|
+
ID2SYM(rb_intern("thread_cpu_time_id")), /* => */ ULL2NUM(thread_context->thread_cpu_time_id.clock_id),
|
|
1394
|
+
#else
|
|
1395
|
+
ID2SYM(rb_intern("thread_cpu_time_id")), /* => */ CLOCKID2NUM(thread_context->thread_cpu_time_id.clock_id),
|
|
1396
|
+
#endif
|
|
1205
1397
|
ID2SYM(rb_intern("cpu_time_at_previous_sample_ns")), /* => */ LONG2NUM(thread_context->cpu_time_at_previous_sample_ns),
|
|
1206
1398
|
ID2SYM(rb_intern("wall_time_at_previous_sample_ns")), /* => */ LONG2NUM(thread_context->wall_time_at_previous_sample_ns),
|
|
1207
1399
|
|
|
1208
1400
|
ID2SYM(rb_intern("gc_tracking.cpu_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.cpu_time_at_start_ns),
|
|
1209
1401
|
ID2SYM(rb_intern("gc_tracking.wall_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.wall_time_at_start_ns),
|
|
1210
1402
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1403
|
+
ID2SYM(rb_intern("gvl_waiting_at")), /* => */ LONG2NUM(thread_context->gvl_waiting_at),
|
|
1404
|
+
ID2SYM(rb_intern("gvl_state_change_count")), /* => */ ULL2NUM(thread_context->gvl_state_change_count),
|
|
1405
|
+
ID2SYM(rb_intern("gvl_state_change_count_at_previous_sample")), /* => */ ULL2NUM(thread_context->gvl_state_change_count_at_previous_sample),
|
|
1406
|
+
ID2SYM(rb_intern("was_skipped_at_last_sample")), /* => */ thread_context->was_skipped_at_last_sample ? Qtrue : Qfalse,
|
|
1407
|
+
ID2SYM(rb_intern("is_profiler_internal_thread")), /* => */ thread_context->is_profiler_internal_thread ? Qtrue : Qfalse,
|
|
1214
1408
|
};
|
|
1215
1409
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(context_as_hash, arguments[i], arguments[i+1]);
|
|
1216
1410
|
|
|
1217
|
-
return
|
|
1411
|
+
return context_as_hash;
|
|
1218
1412
|
}
|
|
1219
1413
|
|
|
1220
|
-
static VALUE
|
|
1414
|
+
static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash) {
|
|
1221
1415
|
// Update this when modifying state struct (stats inner struct)
|
|
1222
|
-
VALUE stats_as_hash = rb_hash_new();
|
|
1223
1416
|
VALUE arguments[] = {
|
|
1417
|
+
ID2SYM(rb_intern("sample_count")), /* => */ UINT2NUM(state->stats.sample_count),
|
|
1224
1418
|
ID2SYM(rb_intern("gc_samples")), /* => */ UINT2NUM(state->stats.gc_samples),
|
|
1225
1419
|
ID2SYM(rb_intern("gc_samples_missed_due_to_missing_context")), /* => */ UINT2NUM(state->stats.gc_samples_missed_due_to_missing_context),
|
|
1420
|
+
ID2SYM(rb_intern("gc_samples_skipped_nothing_to_flush")), /* => */ UINT2NUM(state->stats.gc_samples_skipped_nothing_to_flush),
|
|
1421
|
+
ID2SYM(rb_intern("inactive_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.inactive_thread_samples_skipped),
|
|
1422
|
+
ID2SYM(rb_intern("profiler_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.profiler_thread_samples_skipped),
|
|
1226
1423
|
};
|
|
1227
|
-
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(
|
|
1228
|
-
return
|
|
1424
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
|
|
1425
|
+
return hash;
|
|
1229
1426
|
}
|
|
1230
1427
|
|
|
1231
1428
|
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state) {
|
|
@@ -1241,66 +1438,77 @@ static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state) {
|
|
|
1241
1438
|
return result;
|
|
1242
1439
|
}
|
|
1243
1440
|
|
|
1244
|
-
static void remove_context_for_dead_threads(thread_context_collector_state *state) {
|
|
1245
|
-
st_foreach(state->hash_map_per_thread_context, remove_if_dead_thread, 0 /* unused */);
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, DDTRACE_UNUSED st_data_t _argument) {
|
|
1249
|
-
VALUE thread = (VALUE) key_thread;
|
|
1250
|
-
per_thread_context* thread_context = (per_thread_context*) value_context;
|
|
1251
|
-
|
|
1252
|
-
if (is_thread_alive(thread)) return ST_CONTINUE;
|
|
1253
|
-
|
|
1254
|
-
free_context(thread_context);
|
|
1255
|
-
return ST_DELETE;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
1441
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1259
1442
|
// It SHOULD NOT be used for other purposes.
|
|
1260
1443
|
//
|
|
1261
|
-
// Returns the whole contents of the per_thread_context structs being tracked.
|
|
1444
|
+
// Returns the whole contents of the per_thread_context structs being tracked, by iterating all live threads.
|
|
1262
1445
|
static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1263
1446
|
thread_context_collector_state *state;
|
|
1264
1447
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1265
1448
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1449
|
+
VALUE result = rb_hash_new();
|
|
1450
|
+
VALUE threads = thread_list(state);
|
|
1451
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
1452
|
+
for (long i = 0; i < thread_count; i++) {
|
|
1453
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
1454
|
+
ENFORCE_THREAD(thread);
|
|
1268
1455
|
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1456
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1457
|
+
if (thread_context != NULL) {
|
|
1458
|
+
rb_hash_aset(result, thread, per_thread_context_to_ruby_hash(thread_context));
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
return result;
|
|
1462
|
+
}
|
|
1272
1463
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1464
|
+
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, per_thread_context *thread_context) {
|
|
1465
|
+
// We don't expect to be sampling a thread (and thus updating these counters) while Ruby is doing GC (between
|
|
1466
|
+
// `thread_context_collector_on_gc_start` and `thread_context_collector_on_gc_finish`)
|
|
1467
|
+
if (thread_context->gc_tracking.cpu_time_at_start_ns != INVALID_TIME) {
|
|
1468
|
+
raise_error(
|
|
1469
|
+
rb_eRuntimeError,
|
|
1470
|
+
"BUG: Unexpected sample during GC (thread_id=%s, gc_tracking.cpu_time_at_start_ns=%ld, "
|
|
1471
|
+
"gc_tracking.wall_time_at_start_ns=%ld, monotonic_wall_time_now_ns=%ld)",
|
|
1472
|
+
thread_context->thread_id,
|
|
1473
|
+
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
1474
|
+
thread_context->gc_tracking.wall_time_at_start_ns,
|
|
1475
|
+
monotonic_wall_time_now_ns()
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1275
1478
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1479
|
+
long elapsed_time_ns = current_time_ns - *time_at_previous_sample_ns; // Capture all time since previous sample
|
|
1480
|
+
*time_at_previous_sample_ns = current_time_ns;
|
|
1278
1481
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
} else {
|
|
1282
|
-
elapsed_time_ns = gc_start_time_ns - *time_at_previous_sample_ns; // Capture time between previous sample and start of GC only
|
|
1283
|
-
}
|
|
1482
|
+
return elapsed_time_ns;
|
|
1483
|
+
}
|
|
1284
1484
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1485
|
+
static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns) {
|
|
1486
|
+
// A previous `cpu_time_now_ns` may have failed to read the clock and thus invalidated our baseline; in that case we
|
|
1487
|
+
// use the current time, so this sample gets no cpu-time and the next one gets an accurate delta.
|
|
1488
|
+
// Note wall-time never needs this: it is always seeded with a real value in `initialize_context`.
|
|
1489
|
+
if (thread_context->cpu_time_at_previous_sample_ns == INVALID_TIME) {
|
|
1490
|
+
thread_context->cpu_time_at_previous_sample_ns = current_cpu_time_ns;
|
|
1290
1491
|
}
|
|
1291
1492
|
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1493
|
+
long elapsed_time_ns = update_time_since_previous_sample(
|
|
1494
|
+
&thread_context->cpu_time_at_previous_sample_ns,
|
|
1495
|
+
current_cpu_time_ns,
|
|
1496
|
+
thread_context
|
|
1497
|
+
);
|
|
1498
|
+
|
|
1499
|
+
if (elapsed_time_ns < 0) raise_error(rb_eRuntimeError, "BUG: Unexpected CPU time going backwards between samples");
|
|
1500
|
+
|
|
1501
|
+
return elapsed_time_ns;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
static long update_wall_time_since_previous_sample(per_thread_context *thread_context, long current_wall_time_ns) {
|
|
1505
|
+
long elapsed_time_ns = update_time_since_previous_sample(
|
|
1506
|
+
&thread_context->wall_time_at_previous_sample_ns,
|
|
1507
|
+
current_wall_time_ns,
|
|
1508
|
+
thread_context
|
|
1509
|
+
);
|
|
1510
|
+
|
|
1511
|
+
if (elapsed_time_ns < 0) raise_error(rb_eRuntimeError, "BUG: Unexpected wall time going backwards between samples");
|
|
1304
1512
|
|
|
1305
1513
|
return elapsed_time_ns;
|
|
1306
1514
|
}
|
|
@@ -1341,14 +1549,14 @@ VALUE enforce_thread_context_collector_instance(VALUE object) {
|
|
|
1341
1549
|
return object;
|
|
1342
1550
|
}
|
|
1343
1551
|
|
|
1344
|
-
//
|
|
1345
|
-
//
|
|
1346
|
-
//
|
|
1347
|
-
void
|
|
1552
|
+
// Commit any pending heap allocation recordings.
|
|
1553
|
+
// Recording an allocation only puts it on a pending list, because taking the weak reference the heap profiler needs
|
|
1554
|
+
// can't be done from inside the NEWOBJ tracepoint -- see `heap_recorder_commit_recordings_may_lose_gvl`.
|
|
1555
|
+
void thread_context_collector_commit_heap_recordings_may_lose_gvl(VALUE self_instance) {
|
|
1348
1556
|
thread_context_collector_state *state;
|
|
1349
1557
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1350
1558
|
|
|
1351
|
-
|
|
1559
|
+
recorder_commit_heap_recordings_may_lose_gvl(state->recorder_instance);
|
|
1352
1560
|
}
|
|
1353
1561
|
|
|
1354
1562
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
@@ -1357,7 +1565,7 @@ static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance)
|
|
|
1357
1565
|
thread_context_collector_state *state;
|
|
1358
1566
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1359
1567
|
|
|
1360
|
-
return
|
|
1568
|
+
return stats_to_ruby_hash(state, rb_hash_new());
|
|
1361
1569
|
}
|
|
1362
1570
|
|
|
1363
1571
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
@@ -1373,8 +1581,7 @@ static VALUE _native_gc_tracking(DDTRACE_UNUSED VALUE _self, VALUE collector_ins
|
|
|
1373
1581
|
static void trace_identifiers_for(
|
|
1374
1582
|
thread_context_collector_state *state,
|
|
1375
1583
|
VALUE thread,
|
|
1376
|
-
trace_identifiers *trace_identifiers_result
|
|
1377
|
-
bool is_safe_to_allocate_objects
|
|
1584
|
+
trace_identifiers *trace_identifiers_result
|
|
1378
1585
|
) {
|
|
1379
1586
|
if (state->otel_context_enabled == OTEL_CONTEXT_ENABLED_ONLY) return;
|
|
1380
1587
|
if (state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY) return;
|
|
@@ -1395,7 +1602,7 @@ static void trace_identifiers_for(
|
|
|
1395
1602
|
VALUE numeric_span_id = Qnil;
|
|
1396
1603
|
|
|
1397
1604
|
if (otel_values != Qnil) {
|
|
1398
|
-
ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values
|
|
1605
|
+
ddtrace_otel_trace_identifiers_for(state, &active_trace, &root_span, &numeric_span_id, active_span, otel_values);
|
|
1399
1606
|
}
|
|
1400
1607
|
|
|
1401
1608
|
if (root_span == Qnil || (active_span == Qnil && numeric_span_id == Qnil)) return;
|
|
@@ -1452,17 +1659,18 @@ static bool should_collect_resource(VALUE root_span) {
|
|
|
1452
1659
|
//
|
|
1453
1660
|
// Assumption: This method gets called BEFORE restarting profiling -- e.g. there are no components attempting to
|
|
1454
1661
|
// trigger samples at the same time.
|
|
1662
|
+
//
|
|
1663
|
+
// Note that tests call this method directly in the same process without forking,
|
|
1664
|
+
// and in such a case non-current Threads keep running.
|
|
1455
1665
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
1456
1666
|
thread_context_collector_state *state;
|
|
1457
1667
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1458
1668
|
|
|
1459
|
-
// Release all context memory before clearing the existing context
|
|
1460
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
|
1461
|
-
|
|
1462
|
-
st_clear(state->hash_map_per_thread_context);
|
|
1463
|
-
|
|
1464
1669
|
state->stats = (struct stats) {}; // Resets all stats back to zero
|
|
1465
1670
|
|
|
1671
|
+
// No need to clean-up the per-thread context because the CpuAndWallTimeWorker always cleans
|
|
1672
|
+
// it up unconditionally on every start/restart and that includes after a fork.
|
|
1673
|
+
|
|
1466
1674
|
rb_funcall(state->recorder_instance, rb_intern("reset_after_fork"), 0);
|
|
1467
1675
|
|
|
1468
1676
|
return Qtrue;
|
|
@@ -1475,37 +1683,38 @@ static VALUE thread_list(thread_context_collector_state *state) {
|
|
|
1475
1683
|
return result;
|
|
1476
1684
|
}
|
|
1477
1685
|
|
|
1478
|
-
|
|
1479
|
-
// the current thread.
|
|
1480
|
-
//
|
|
1481
|
-
// Assumptions for this function are same as for `thread_context_collector_sample` except that this function is
|
|
1482
|
-
// expected to be called from a signal handler and to be async-signal-safe.
|
|
1483
|
-
//
|
|
1484
|
-
// Also, no allocation (Ruby or malloc) can happen.
|
|
1485
|
-
bool thread_context_collector_prepare_sample_inside_signal_handler(VALUE self_instance) {
|
|
1486
|
-
thread_context_collector_state *state;
|
|
1487
|
-
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
|
1488
|
-
// This should never fail if the above check passes
|
|
1489
|
-
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1490
|
-
|
|
1686
|
+
static inline bool thread_context_collector_prepare_sample_internal(void) {
|
|
1491
1687
|
VALUE current_thread = rb_thread_current();
|
|
1492
|
-
per_thread_context *thread_context =
|
|
1688
|
+
per_thread_context *thread_context = get_per_thread_context(current_thread);
|
|
1493
1689
|
if (thread_context == NULL) return false;
|
|
1494
1690
|
|
|
1495
1691
|
return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
|
|
1496
1692
|
}
|
|
1497
1693
|
|
|
1498
|
-
//
|
|
1499
|
-
// Ruby
|
|
1694
|
+
// Called from a signal handler on the main Ractor with the GVL held, outside GC and with `during_sample` unset.
|
|
1695
|
+
// Must remain async-signal-safe: no allocation (Ruby or malloc), exceptions, or releasing the GVL.
|
|
1696
|
+
bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
1697
|
+
return thread_context_collector_prepare_sample_internal();
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// Called at RUBY_INTERNAL_EVENT_GC_EXIT on the main Ractor with the GVL held; `during_sample` MUST be set to prevent reentrancy.
|
|
1701
|
+
// Ruby is still doing GC: no allocation (Ruby or malloc), exceptions, or releasing the GVL.
|
|
1702
|
+
void thread_context_collector_prepare_sample_on_gc_finish(void) {
|
|
1703
|
+
thread_context_collector_prepare_sample_internal();
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
// This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
|
|
1707
|
+
// Ruby heap nor release the GVL (https://github.com/DataDog/dd-trace-rb/pull/4240).
|
|
1708
|
+
//
|
|
1709
|
+
// Returns true if `thread_context_collector_commit_heap_recordings_may_lose_gvl` needs to be called (to do work
|
|
1710
|
+
// that can't be done from inside the tracepoint, such as allocate new objects), and false if it doesn't
|
|
1500
1711
|
//
|
|
1501
|
-
//
|
|
1502
|
-
//
|
|
1503
|
-
bool thread_context_collector_sample_allocation(VALUE self_instance, unsigned int sample_weight, VALUE new_object) {
|
|
1712
|
+
// Assumption 1: The callers must ensure thread_context is non-NULL.
|
|
1713
|
+
// Assumption 2: When called while the profiler is active, `during_sample` MUST be set.
|
|
1714
|
+
bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object) {
|
|
1504
1715
|
thread_context_collector_state *state;
|
|
1505
1716
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1506
1717
|
|
|
1507
|
-
VALUE current_thread = rb_thread_current();
|
|
1508
|
-
|
|
1509
1718
|
enum ruby_value_type type = rb_type(new_object);
|
|
1510
1719
|
|
|
1511
1720
|
// Tag samples with the VM internal types
|
|
@@ -1544,13 +1753,10 @@ bool thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
|
1544
1753
|
// Thus, we need to make sure there's actually a class before getting its name.
|
|
1545
1754
|
|
|
1546
1755
|
if (klass != 0) {
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
if (name_length > 0) {
|
|
1551
|
-
class_name = (ddog_CharSlice) {.ptr = name, .len = name_length};
|
|
1756
|
+
VALUE name = ddtrace_alloc_free_rb_mod_name(klass);
|
|
1757
|
+
if (name != Qnil) {
|
|
1758
|
+
class_name = char_slice_from_ruby_string(name);
|
|
1552
1759
|
} else {
|
|
1553
|
-
// @ivoanjo: I'm not sure this can ever happen, but just-in-case
|
|
1554
1760
|
class_name = ruby_value_type_to_class_name(type);
|
|
1555
1761
|
}
|
|
1556
1762
|
} else {
|
|
@@ -1570,42 +1776,47 @@ bool thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
|
1570
1776
|
class_name = ruby_vm_type; // For other weird internal things we just use the VM type
|
|
1571
1777
|
}
|
|
1572
1778
|
|
|
1573
|
-
|
|
1779
|
+
VALUE current_thread = rb_thread_current();
|
|
1574
1780
|
|
|
1575
|
-
|
|
1781
|
+
heap_sample_values heap_sample = {.new_object = new_object, .alloc_class = class_name};
|
|
1576
1782
|
|
|
1577
1783
|
trigger_sample_for_thread(
|
|
1578
1784
|
state,
|
|
1579
|
-
|
|
1580
|
-
/* stack_from_thread: */ current_thread,
|
|
1785
|
+
current_thread,
|
|
1581
1786
|
thread_context,
|
|
1582
|
-
&
|
|
1583
|
-
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
|
|
1787
|
+
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = &heap_sample},
|
|
1584
1788
|
INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
|
|
1585
1789
|
&ruby_vm_type,
|
|
1586
|
-
|
|
1587
|
-
/* is_gvl_waiting_state: */ false
|
|
1588
|
-
/* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
|
|
1790
|
+
&class_name,
|
|
1791
|
+
/* is_gvl_waiting_state: */ false
|
|
1589
1792
|
);
|
|
1590
1793
|
|
|
1591
|
-
return
|
|
1794
|
+
return heap_sample.out_needs_commit;
|
|
1592
1795
|
}
|
|
1593
1796
|
|
|
1594
1797
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1595
1798
|
// It SHOULD NOT be used for other purposes.
|
|
1596
1799
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object) {
|
|
1800
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
1801
|
+
if (thread_context == NULL) {
|
|
1802
|
+
rb_raise(rb_eRuntimeError, "Missing per_thread_context for current thread in _native_sample_allocation");
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1597
1805
|
debug_enter_unsafe_context();
|
|
1598
1806
|
|
|
1599
|
-
bool
|
|
1807
|
+
bool needs_commit = thread_context_collector_sample_allocation(collector_instance, thread_context, NUM2UINT(sample_weight), new_object);
|
|
1600
1808
|
|
|
1601
1809
|
debug_leave_unsafe_context();
|
|
1602
1810
|
|
|
1603
1811
|
// We could instead choose to automatically trigger the after allocation here; yet, it seems kinda nice to keep it manual for
|
|
1604
1812
|
// the tests so we can pull on each lever separately and observe "the sausage being made" in steps
|
|
1605
|
-
return
|
|
1813
|
+
return needs_commit ? Qtrue : Qfalse;
|
|
1606
1814
|
}
|
|
1607
1815
|
|
|
1608
|
-
static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
|
|
1816
|
+
static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
|
|
1817
|
+
rb_thread_sleep(INT_MAX);
|
|
1818
|
+
return Qnil;
|
|
1819
|
+
}
|
|
1609
1820
|
|
|
1610
1821
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1611
1822
|
// It SHOULD NOT be used for other purposes.
|
|
@@ -1649,26 +1860,57 @@ static VALUE read_otel_current_span_key_const(DDTRACE_UNUSED VALUE _unused) {
|
|
|
1649
1860
|
return rb_const_get(trace_module, rb_intern("CURRENT_SPAN_KEY"));
|
|
1650
1861
|
}
|
|
1651
1862
|
|
|
1652
|
-
static VALUE
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
// Calling read_otel_current_span_key_const below can trigger exceptions and arbitrary Ruby code running (e.g.
|
|
1656
|
-
// `const_missing`, etc). Not safe to call in this situation, so we just skip otel info for this sample.
|
|
1657
|
-
return Qnil;
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
// If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
|
|
1661
|
-
// gets refactored, we should not fall on our face)
|
|
1662
|
-
VALUE span_key = rb_protect(read_otel_current_span_key_const, Qnil, NULL);
|
|
1663
|
-
rb_set_errinfo(Qnil); // **Clear any pending exception after ignoring it**
|
|
1863
|
+
static VALUE otel_current_span_key_not_found(DDTRACE_UNUSED VALUE _unused, DDTRACE_UNUSED VALUE _exception) {
|
|
1864
|
+
return Qnil;
|
|
1865
|
+
}
|
|
1664
1866
|
|
|
1665
|
-
|
|
1666
|
-
|
|
1867
|
+
static VALUE get_otel_current_span_key(thread_context_collector_state *state) {
|
|
1868
|
+
if (state->otel_current_span_key == OTEL_CURRENT_SPAN_KEY_NOT_EXTRACTED) {
|
|
1869
|
+
// Extracting this needs calling into Ruby code, which we don't want to do in the middle of a sample.
|
|
1870
|
+
// So instead we signal that we want this, and let it be resolved separately.
|
|
1871
|
+
state->otel_current_span_key = OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED;
|
|
1872
|
+
return Qnil;
|
|
1667
1873
|
}
|
|
1668
1874
|
|
|
1875
|
+
// We asked for it, but it hasn't been extracted yet
|
|
1876
|
+
if (is_otel_current_span_key_needed(state)) return Qnil;
|
|
1877
|
+
|
|
1669
1878
|
return state->otel_current_span_key;
|
|
1670
1879
|
}
|
|
1671
1880
|
|
|
1881
|
+
static bool is_otel_current_span_key_needed(thread_context_collector_state *state) {
|
|
1882
|
+
return state->otel_current_span_key == OTEL_CURRENT_SPAN_KEY_EXTRACTION_NEEDED;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// Extracts `otel_current_span_key`, if a sample asked for it (see `get_otel_current_span_key`).
|
|
1886
|
+
// This is deliberately not done during sampling because we can lose the GVL.
|
|
1887
|
+
//
|
|
1888
|
+
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
1889
|
+
void thread_context_collector_resolve_otel_span_key_may_lose_gvl(VALUE self_instance) {
|
|
1890
|
+
thread_context_collector_state *state;
|
|
1891
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1892
|
+
|
|
1893
|
+
if (!is_otel_current_span_key_needed(state)) return;
|
|
1894
|
+
|
|
1895
|
+
// If this fails, we want to fail gracefully, rather than raise an exception (e.g. if the opentelemetry gem
|
|
1896
|
+
// gets refactored, we should not fall on our face).
|
|
1897
|
+
//
|
|
1898
|
+
// Note that we use `rb_rescue2` and not `rb_protect` so that we only swallow exceptions: anything else that can
|
|
1899
|
+
// unwind through here (e.g. a `Thread#kill`) is not ours to swallow. `rb_rescue2` also takes care of restoring
|
|
1900
|
+
// any previously-pending exception for us.
|
|
1901
|
+
VALUE span_key = rb_rescue2(
|
|
1902
|
+
read_otel_current_span_key_const,
|
|
1903
|
+
Qnil,
|
|
1904
|
+
otel_current_span_key_not_found,
|
|
1905
|
+
Qnil,
|
|
1906
|
+
rb_eException, // rb_eException is the base class of all Ruby exceptions
|
|
1907
|
+
0 // Required by API to be the last argument
|
|
1908
|
+
);
|
|
1909
|
+
|
|
1910
|
+
// 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
|
|
1911
|
+
state->otel_current_span_key = span_key;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1672
1914
|
// This method gets used when ddtrace is being used indirectly via the opentelemetry APIs. Information gets stored slightly
|
|
1673
1915
|
// differently, and this codepath handles it.
|
|
1674
1916
|
static void ddtrace_otel_trace_identifiers_for(
|
|
@@ -1677,8 +1919,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
1677
1919
|
VALUE *root_span,
|
|
1678
1920
|
VALUE *numeric_span_id,
|
|
1679
1921
|
VALUE active_span,
|
|
1680
|
-
VALUE otel_values
|
|
1681
|
-
bool is_safe_to_allocate_objects
|
|
1922
|
+
VALUE otel_values
|
|
1682
1923
|
) {
|
|
1683
1924
|
VALUE resolved_numeric_span_id =
|
|
1684
1925
|
active_span == Qnil ?
|
|
@@ -1689,7 +1930,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
1689
1930
|
|
|
1690
1931
|
if (resolved_numeric_span_id == Qnil) return;
|
|
1691
1932
|
|
|
1692
|
-
VALUE otel_current_span_key = get_otel_current_span_key(state
|
|
1933
|
+
VALUE otel_current_span_key = get_otel_current_span_key(state);
|
|
1693
1934
|
if (otel_current_span_key == Qnil) return;
|
|
1694
1935
|
VALUE current_trace = *active_trace;
|
|
1695
1936
|
|
|
@@ -1715,6 +1956,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
1715
1956
|
*numeric_span_id = resolved_numeric_span_id;
|
|
1716
1957
|
}
|
|
1717
1958
|
|
|
1959
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
1718
1960
|
void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples) {
|
|
1719
1961
|
thread_context_collector_state *state;
|
|
1720
1962
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
@@ -1809,15 +2051,14 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
|
|
|
1809
2051
|
static void otel_without_ddtrace_trace_identifiers_for(
|
|
1810
2052
|
thread_context_collector_state *state,
|
|
1811
2053
|
VALUE thread,
|
|
1812
|
-
trace_identifiers *trace_identifiers_result
|
|
1813
|
-
bool is_safe_to_allocate_objects
|
|
2054
|
+
trace_identifiers *trace_identifiers_result
|
|
1814
2055
|
) {
|
|
1815
2056
|
VALUE context_storage = otel_context_storage_for(state, thread);
|
|
1816
2057
|
|
|
1817
2058
|
// If it exists, context_storage is expected to be an Array[OpenTelemetry::Context]
|
|
1818
2059
|
if (context_storage == Qnil || !RB_TYPE_P(context_storage, T_ARRAY)) return;
|
|
1819
2060
|
|
|
1820
|
-
VALUE otel_current_span_key = get_otel_current_span_key(state
|
|
2061
|
+
VALUE otel_current_span_key = get_otel_current_span_key(state);
|
|
1821
2062
|
if (otel_current_span_key == Qnil) return;
|
|
1822
2063
|
|
|
1823
2064
|
long active_context_index = RARRAY_LEN(context_storage) - 1;
|
|
@@ -1898,57 +2139,184 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1898
2139
|
((uint64_t)span_bytes[7]);
|
|
1899
2140
|
}
|
|
1900
2141
|
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
intptr_t thread_being_profiled = gvl_profiling_state_get(thread);
|
|
1913
|
-
if (!thread_being_profiled) return;
|
|
2142
|
+
void thread_context_collector_stats(VALUE self_instance, VALUE stats_hash) {
|
|
2143
|
+
thread_context_collector_state *state;
|
|
2144
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2145
|
+
stats_to_ruby_hash(state, stats_hash);
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
2149
|
+
thread_context_collector_state *state;
|
|
2150
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2151
|
+
state->stats = (struct stats) {};
|
|
2152
|
+
}
|
|
1914
2153
|
|
|
1915
|
-
|
|
1916
|
-
|
|
2154
|
+
static void mark_thread_as_profiler_internal(per_thread_context *ctx) {
|
|
2155
|
+
ctx->is_profiler_internal_thread = true;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
void thread_context_collector_profiler_internal_thread_started(void) {
|
|
2159
|
+
per_thread_context *ctx = get_or_create_context_for(rb_thread_current(), monotonic_wall_time_now_ns());
|
|
2160
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2164
|
+
per_thread_context *ctx = get_or_create_context_for(thread, monotonic_wall_time_now_ns());
|
|
2165
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2166
|
+
return Qnil;
|
|
2167
|
+
}
|
|
1917
2168
|
|
|
1918
|
-
|
|
2169
|
+
// Called via rb_ensure when a profiler-internal thread (worker or idle helper) is about to exit.
|
|
2170
|
+
// Records a final sample so the thread's accumulated cpu/wall time since the last prepare_serialize
|
|
2171
|
+
// is not lost. prepare_serialize (below) also flushes profiler-internal threads during periodic
|
|
2172
|
+
// serialization, but it can't help at shutdown: by the time the final serialize runs, these
|
|
2173
|
+
// threads are already dead and absent from thread_list.
|
|
2174
|
+
//
|
|
2175
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2176
|
+
VALUE thread_context_collector_profiler_internal_thread_done(VALUE self_instance) {
|
|
2177
|
+
thread_context_collector_state *state;
|
|
2178
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2179
|
+
|
|
2180
|
+
VALUE current_thread = rb_thread_current();
|
|
2181
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns();
|
|
2182
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread, current_monotonic_wall_time_ns);
|
|
2183
|
+
if (!thread_context->is_profiler_internal_thread) {
|
|
2184
|
+
rb_raise(rb_eRuntimeError, "current thread %"PRIsVALUE" is not profiler-internal thread", current_thread);
|
|
1919
2185
|
}
|
|
1920
2186
|
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
2187
|
+
update_metrics_and_sample(
|
|
2188
|
+
state,
|
|
2189
|
+
current_thread,
|
|
2190
|
+
thread_context,
|
|
2191
|
+
current_monotonic_wall_time_ns,
|
|
2192
|
+
true);
|
|
2193
|
+
|
|
2194
|
+
return Qnil;
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
// Flushes threads whose last per-tick sample was skipped (either by the SUSPENDED-skip
|
|
2198
|
+
// optimization, or by is_profiler_internal_thread) so their accumulated time is recorded.
|
|
2199
|
+
// Called before serialization so that threads suspended across the whole profile period still get sampled.
|
|
2200
|
+
//
|
|
2201
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2202
|
+
VALUE thread_context_prepare_serialize(VALUE self_instance) {
|
|
2203
|
+
thread_context_collector_state *state;
|
|
2204
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2205
|
+
|
|
2206
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns();
|
|
2207
|
+
VALUE threads = thread_list(state);
|
|
2208
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
2209
|
+
|
|
2210
|
+
for (long i = 0; i < thread_count; i++) {
|
|
2211
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
2212
|
+
ENFORCE_THREAD(thread);
|
|
1925
2213
|
|
|
1926
|
-
|
|
1927
|
-
if (gvl_waiting_at == 0 || gvl_waiting_at == GVL_WAITING_ENABLED_EMPTY) return ON_GVL_RUNNING_UNKNOWN;
|
|
2214
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1928
2215
|
|
|
2216
|
+
if (thread_context != NULL && (thread_context->was_skipped_at_last_sample || thread_context->is_profiler_internal_thread)) {
|
|
2217
|
+
update_metrics_and_sample(
|
|
2218
|
+
state,
|
|
2219
|
+
thread,
|
|
2220
|
+
thread_context,
|
|
2221
|
+
current_monotonic_wall_time_ns,
|
|
2222
|
+
true // We need to force_sample=true otherwise this sample would be skipped too
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
return state->recorder_instance;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
static VALUE _native_prepare_serialize(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
2231
|
+
return thread_context_prepare_serialize(collector_instance);
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
void thread_context_collector_on_gvl_released(per_thread_context *thread_context) {
|
|
2235
|
+
thread_context->gvl_state_change_count |= GVL_SUSPENDED;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
void thread_context_collector_on_gvl_waiting(per_thread_context *thread_context) {
|
|
2239
|
+
thread_context->gvl_waiting_at = monotonic_wall_time_now_ns();
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2243
|
+
ENFORCE_THREAD(thread);
|
|
2244
|
+
|
|
2245
|
+
debug_enter_unsafe_context();
|
|
2246
|
+
|
|
2247
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2248
|
+
if (thread_context) thread_context_collector_on_gvl_waiting(thread_context);
|
|
2249
|
+
|
|
2250
|
+
debug_leave_unsafe_context();
|
|
2251
|
+
|
|
2252
|
+
return Qnil;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2256
|
+
ENFORCE_THREAD(thread);
|
|
2257
|
+
|
|
2258
|
+
debug_enter_unsafe_context();
|
|
2259
|
+
|
|
2260
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2261
|
+
if (thread_context) thread_context_collector_on_gvl_released(thread_context);
|
|
2262
|
+
|
|
2263
|
+
debug_leave_unsafe_context();
|
|
2264
|
+
|
|
2265
|
+
return Qnil;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
|
2269
|
+
// We must only use async-signal-safe functions here, see notes in the caller
|
|
2270
|
+
//
|
|
2271
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2272
|
+
__attribute__((warn_unused_result))
|
|
2273
|
+
on_gvl_running_result thread_context_collector_on_gvl_running(VALUE thread, per_thread_context *thread_context, uint32_t waiting_for_gvl_threshold_ns) {
|
|
2274
|
+
// Bump the event counter and clears the state bit to "running"
|
|
2275
|
+
uint64_t counter_portion = thread_context->gvl_state_change_count >> 1;
|
|
2276
|
+
thread_context->gvl_state_change_count = ((counter_portion + 1) << 1) | GVL_RUNNING;
|
|
2277
|
+
|
|
2278
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
2279
|
+
// Thread was not waiting on gvl
|
|
2280
|
+
if (gvl_waiting_at == 0) {
|
|
2281
|
+
return (on_gvl_running_result) {.action = ON_GVL_RUNNING_UNKNOWN, .waiting_for_gvl_duration_ns = 0};
|
|
2282
|
+
}
|
|
1929
2283
|
// @ivoanjo: I'm not sure if this can happen -- It means we should've sampled already but haven't gotten the chance yet?
|
|
1930
|
-
if (gvl_waiting_at < 0)
|
|
2284
|
+
if (gvl_waiting_at < 0) {
|
|
2285
|
+
return (on_gvl_running_result) {.action = ON_GVL_RUNNING_SAMPLE, .waiting_for_gvl_duration_ns = 0};
|
|
2286
|
+
}
|
|
1931
2287
|
|
|
1932
|
-
long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns(
|
|
2288
|
+
long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns() - gvl_waiting_at;
|
|
1933
2289
|
|
|
1934
2290
|
bool should_sample = waiting_for_gvl_duration_ns >= waiting_for_gvl_threshold_ns;
|
|
1935
2291
|
|
|
1936
2292
|
if (should_sample) {
|
|
1937
2293
|
// We flip the gvl_waiting_at to negative to mark that the thread is now running and no longer waiting
|
|
1938
|
-
|
|
2294
|
+
long gvl_waiting_at_is_now_running = -gvl_waiting_at;
|
|
1939
2295
|
|
|
1940
|
-
|
|
2296
|
+
thread_context->gvl_waiting_at = gvl_waiting_at_is_now_running;
|
|
1941
2297
|
} else {
|
|
1942
|
-
|
|
1943
|
-
|
|
2298
|
+
thread_context->gvl_waiting_at = 0;
|
|
2299
|
+
|
|
2300
|
+
// Even though the GVL wait itself was below threshold, if the thread had skipped samples
|
|
2301
|
+
// (was suspended for a long time without the GVL), we still need to force a sample now.
|
|
2302
|
+
// Otherwise, the accumulated idle wall-time would be reported against whatever stack the
|
|
2303
|
+
// thread runs next, misrepresenting the time spent idle.
|
|
2304
|
+
if (thread_context->was_skipped_at_last_sample) {
|
|
2305
|
+
should_sample = true;
|
|
2306
|
+
}
|
|
1944
2307
|
}
|
|
1945
2308
|
|
|
1946
|
-
|
|
1947
|
-
|
|
2309
|
+
if (should_sample) {
|
|
2310
|
+
// We prepare the sample here because the postponed job might be called some time later,
|
|
2311
|
+
// possibly after some Ruby calls which change the Ruby stack,
|
|
2312
|
+
// and we want to attribute the time acquiring or without the GVL to the correct Ruby stack.
|
|
2313
|
+
prepare_sample_thread(thread, &thread_context->sampling_buffer);
|
|
2314
|
+
}
|
|
1948
2315
|
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
2316
|
+
return (on_gvl_running_result) {
|
|
2317
|
+
.action = should_sample ? ON_GVL_RUNNING_SAMPLE : ON_GVL_RUNNING_DONT_SAMPLE,
|
|
2318
|
+
.waiting_for_gvl_duration_ns = waiting_for_gvl_duration_ns,
|
|
2319
|
+
};
|
|
1952
2320
|
}
|
|
1953
2321
|
|
|
1954
2322
|
// Why does this method need to exist?
|
|
@@ -1968,7 +2336,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1968
2336
|
//
|
|
1969
2337
|
// Stack:
|
|
1970
2338
|
// If the thread starts working without the end of the Waiting for GVL sample, then by the time the thread is sampled
|
|
1971
|
-
// via the regular cpu/wall-time samples mechanism, the stack can be
|
|
2339
|
+
// via the regular cpu/wall-time samples mechanism, the stack can be inaccurate (e.g. does not correctly pinpoint
|
|
1972
2340
|
// where the waiting happened).
|
|
1973
2341
|
//
|
|
1974
2342
|
// Arguably, the last sample after Waiting for GVL ended (when gvl_waiting_at < 0) should always come from this method
|
|
@@ -1977,17 +2345,20 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1977
2345
|
//
|
|
1978
2346
|
// ---
|
|
1979
2347
|
//
|
|
2348
|
+
// Assumption 1: Always called with the GVL, either from a postponed_job or from tests.
|
|
2349
|
+
// Assumption 2: When called while the profiler is active, `during_sample` MUST be set.
|
|
2350
|
+
//
|
|
1980
2351
|
// NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
|
|
1981
2352
|
// exists to enable testing.
|
|
1982
2353
|
VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns) {
|
|
1983
2354
|
thread_context_collector_state *state;
|
|
1984
2355
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1985
2356
|
|
|
1986
|
-
|
|
2357
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread, current_monotonic_wall_time_ns);
|
|
1987
2358
|
|
|
1988
|
-
|
|
2359
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
1989
2360
|
|
|
1990
|
-
if (gvl_waiting_at >= 0) {
|
|
2361
|
+
if (gvl_waiting_at >= 0 && !thread_context->was_skipped_at_last_sample) {
|
|
1991
2362
|
// @ivoanjo: I'm not sure if this can ever happen. This means that we're not on the same thread
|
|
1992
2363
|
// that ran `thread_context_collector_on_gvl_running` and made the decision to sample OR a regular sample was
|
|
1993
2364
|
// triggered ahead of us.
|
|
@@ -1995,24 +2366,14 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1995
2366
|
return Qfalse;
|
|
1996
2367
|
}
|
|
1997
2368
|
|
|
1998
|
-
per_thread_context *thread_context = get_or_create_context_for(current_thread, state);
|
|
1999
|
-
|
|
2000
|
-
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may chose to push an
|
|
2001
|
-
// extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
|
|
2002
|
-
// cpu-time of the thread and let `update_metrics_and_sample` decide what to do with it.
|
|
2003
|
-
long cpu_time_for_thread = cpu_time_now_ns(thread_context);
|
|
2004
|
-
|
|
2005
2369
|
// TODO: Should we update the dynamic sampling rate overhead tracking with this sample as well?
|
|
2006
2370
|
|
|
2007
2371
|
update_metrics_and_sample(
|
|
2008
2372
|
state,
|
|
2009
|
-
|
|
2010
|
-
/* stack_from_thread: */ current_thread,
|
|
2373
|
+
current_thread,
|
|
2011
2374
|
thread_context,
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
current_monotonic_wall_time_ns
|
|
2015
|
-
);
|
|
2375
|
+
current_monotonic_wall_time_ns,
|
|
2376
|
+
false);
|
|
2016
2377
|
|
|
2017
2378
|
return Qtrue;
|
|
2018
2379
|
}
|
|
@@ -2023,14 +2384,12 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2023
2384
|
static bool handle_gvl_waiting(
|
|
2024
2385
|
thread_context_collector_state *state,
|
|
2025
2386
|
VALUE thread_being_sampled,
|
|
2026
|
-
VALUE stack_from_thread,
|
|
2027
2387
|
per_thread_context *thread_context,
|
|
2028
|
-
sampling_buffer* sampling_buffer,
|
|
2029
2388
|
long current_cpu_time_ns
|
|
2030
2389
|
) {
|
|
2031
|
-
|
|
2390
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
2032
2391
|
|
|
2033
|
-
bool is_gvl_waiting_state = gvl_waiting_at != 0
|
|
2392
|
+
bool is_gvl_waiting_state = gvl_waiting_at != 0;
|
|
2034
2393
|
|
|
2035
2394
|
if (!is_gvl_waiting_state) return false;
|
|
2036
2395
|
|
|
@@ -2043,17 +2402,17 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2043
2402
|
// ...──────────────┬───────────────────...
|
|
2044
2403
|
// Other state │ Waiting for GVL
|
|
2045
2404
|
// ...──────────────┴───────────────────...
|
|
2046
|
-
// ▲
|
|
2405
|
+
// ▲ ▲
|
|
2047
2406
|
// └─ Previous sample └─ Regular sample (caller)
|
|
2048
2407
|
//
|
|
2049
2408
|
// In this case, we'll want to push two samples: a) one for the current time (handled by the caller), b) an extra sample
|
|
2050
|
-
// to represent the remaining cpu/wall time before the "Waiting for GVL" started:
|
|
2409
|
+
// to represent the remaining cpu/wall time before the "Waiting for GVL" started (for timeline purposes):
|
|
2051
2410
|
//
|
|
2052
2411
|
// time ─────►
|
|
2053
2412
|
// ...──────────────┬───────────────────...
|
|
2054
2413
|
// Other state │ Waiting for GVL
|
|
2055
2414
|
// ...──────────────┴───────────────────...
|
|
2056
|
-
// ▲
|
|
2415
|
+
// ▲ ▲ ▲
|
|
2057
2416
|
// └─ Prev... └─ Extra sample └─ Regular sample (caller)
|
|
2058
2417
|
//
|
|
2059
2418
|
// 2. The current sample is the n-th one after we entered the "Waiting for GVL" state
|
|
@@ -2063,7 +2422,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2063
2422
|
// ...──────────────┬───────────────────────────────────────────────...
|
|
2064
2423
|
// Other state │ Waiting for GVL
|
|
2065
2424
|
// ...──────────────┴───────────────────────────────────────────────...
|
|
2066
|
-
// ▲
|
|
2425
|
+
// ▲ ▲ ▲
|
|
2067
2426
|
// └─ Previous sample └─ Previous sample └─ Regular sample (caller)
|
|
2068
2427
|
//
|
|
2069
2428
|
// In this case, we just report back to the caller that the thread is in the "Waiting for GVL" state.
|
|
@@ -2078,125 +2437,124 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2078
2437
|
|
|
2079
2438
|
if (gvl_waiting_at < 0) {
|
|
2080
2439
|
// Negative means the waiting for GVL just ended, so we clear the state, so next samples no longer represent waiting
|
|
2081
|
-
|
|
2440
|
+
thread_context->gvl_waiting_at = 0;
|
|
2082
2441
|
}
|
|
2083
2442
|
|
|
2084
2443
|
long gvl_waiting_started_wall_time_ns = labs(gvl_waiting_at);
|
|
2085
2444
|
|
|
2086
2445
|
if (thread_context->wall_time_at_previous_sample_ns < gvl_waiting_started_wall_time_ns) { // situation 1 above
|
|
2087
|
-
long cpu_time_elapsed_ns =
|
|
2088
|
-
&thread_context->cpu_time_at_previous_sample_ns,
|
|
2089
|
-
current_cpu_time_ns,
|
|
2090
|
-
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
2091
|
-
IS_NOT_WALL_TIME
|
|
2092
|
-
);
|
|
2446
|
+
long cpu_time_elapsed_ns = update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
2093
2447
|
|
|
2094
|
-
long duration_until_start_of_gvl_waiting_ns =
|
|
2095
|
-
|
|
2096
|
-
gvl_waiting_started_wall_time_ns,
|
|
2097
|
-
INVALID_TIME,
|
|
2098
|
-
IS_WALL_TIME
|
|
2099
|
-
);
|
|
2448
|
+
long duration_until_start_of_gvl_waiting_ns =
|
|
2449
|
+
update_wall_time_since_previous_sample(thread_context, gvl_waiting_started_wall_time_ns);
|
|
2100
2450
|
|
|
2101
2451
|
// Push extra sample
|
|
2102
2452
|
trigger_sample_for_thread(
|
|
2103
2453
|
state,
|
|
2104
2454
|
thread_being_sampled,
|
|
2105
|
-
stack_from_thread,
|
|
2106
2455
|
thread_context,
|
|
2107
|
-
sampling_buffer,
|
|
2108
2456
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = duration_until_start_of_gvl_waiting_ns},
|
|
2109
2457
|
gvl_waiting_started_wall_time_ns,
|
|
2110
2458
|
NULL,
|
|
2111
2459
|
NULL,
|
|
2112
|
-
/* is_gvl_waiting_state: */ false
|
|
2113
|
-
/* is_safe_to_allocate_objects: */ true // This is similar to a regular cpu/wall sample, so it's also safe
|
|
2460
|
+
/* 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
|
|
2114
2461
|
);
|
|
2115
2462
|
}
|
|
2116
2463
|
|
|
2117
2464
|
return true;
|
|
2118
2465
|
}
|
|
2119
2466
|
|
|
2120
|
-
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2121
|
-
ENFORCE_THREAD(thread);
|
|
2122
|
-
|
|
2123
|
-
debug_enter_unsafe_context();
|
|
2124
|
-
|
|
2125
|
-
thread_context_collector_on_gvl_waiting(thread_from_thread_object(thread));
|
|
2126
|
-
|
|
2127
|
-
debug_leave_unsafe_context();
|
|
2128
|
-
|
|
2129
|
-
return Qnil;
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
2467
|
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2133
2468
|
ENFORCE_THREAD(thread);
|
|
2134
2469
|
|
|
2135
2470
|
debug_enter_unsafe_context();
|
|
2136
2471
|
|
|
2137
|
-
|
|
2472
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2473
|
+
VALUE result = thread_context ? LONG2NUM(thread_context->gvl_waiting_at) : Qnil;
|
|
2138
2474
|
|
|
2139
2475
|
debug_leave_unsafe_context();
|
|
2140
2476
|
|
|
2141
|
-
return
|
|
2477
|
+
return result;
|
|
2142
2478
|
}
|
|
2143
2479
|
|
|
2144
|
-
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2480
|
+
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE waiting_for_gvl_threshold_ns) {
|
|
2145
2481
|
ENFORCE_THREAD(thread);
|
|
2482
|
+
ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
|
|
2146
2483
|
|
|
2147
2484
|
debug_enter_unsafe_context();
|
|
2148
2485
|
|
|
2149
|
-
|
|
2486
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2487
|
+
VALUE result;
|
|
2488
|
+
if (thread_context) {
|
|
2489
|
+
on_gvl_running_result gvl_running_result =
|
|
2490
|
+
thread_context_collector_on_gvl_running(thread, thread_context, NUM2UINT(waiting_for_gvl_threshold_ns));
|
|
2491
|
+
result = gvl_running_result.action == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
|
|
2492
|
+
} else {
|
|
2493
|
+
result = Qfalse;
|
|
2494
|
+
}
|
|
2150
2495
|
|
|
2151
2496
|
debug_leave_unsafe_context();
|
|
2152
2497
|
|
|
2153
2498
|
return result;
|
|
2154
2499
|
}
|
|
2155
2500
|
|
|
2156
|
-
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread
|
|
2501
|
+
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
|
|
2157
2502
|
ENFORCE_THREAD(thread);
|
|
2158
|
-
ENFORCE_BOOLEAN(allow_exception);
|
|
2159
|
-
|
|
2160
2503
|
|
|
2161
|
-
|
|
2162
|
-
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
2504
|
+
debug_enter_unsafe_context();
|
|
2163
2505
|
|
|
2164
2506
|
VALUE result = thread_context_collector_sample_after_gvl_running(
|
|
2165
2507
|
collector_instance,
|
|
2166
2508
|
thread,
|
|
2167
|
-
monotonic_wall_time_now_ns(
|
|
2509
|
+
monotonic_wall_time_now_ns()
|
|
2168
2510
|
);
|
|
2169
2511
|
|
|
2170
|
-
|
|
2512
|
+
debug_leave_unsafe_context();
|
|
2171
2513
|
|
|
2172
2514
|
return result;
|
|
2173
2515
|
}
|
|
2174
2516
|
|
|
2175
|
-
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns) {
|
|
2176
|
-
ENFORCE_THREAD(thread);
|
|
2177
|
-
|
|
2178
|
-
thread_context_collector_state *state;
|
|
2179
|
-
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2180
|
-
|
|
2181
|
-
per_thread_context *thread_context = get_context_for(thread, state);
|
|
2182
|
-
if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2183
|
-
|
|
2184
|
-
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
|
2185
|
-
|
|
2186
|
-
return Qtrue;
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
2517
|
#else
|
|
2190
2518
|
static bool handle_gvl_waiting(
|
|
2191
2519
|
DDTRACE_UNUSED thread_context_collector_state *state,
|
|
2192
2520
|
DDTRACE_UNUSED VALUE thread_being_sampled,
|
|
2193
|
-
DDTRACE_UNUSED VALUE stack_from_thread,
|
|
2194
2521
|
DDTRACE_UNUSED per_thread_context *thread_context,
|
|
2195
|
-
DDTRACE_UNUSED sampling_buffer* sampling_buffer,
|
|
2196
2522
|
DDTRACE_UNUSED long current_cpu_time_ns
|
|
2197
2523
|
) { return false; }
|
|
2524
|
+
|
|
2198
2525
|
#endif // NO_GVL_INSTRUMENTATION
|
|
2199
2526
|
|
|
2527
|
+
static VALUE _native_apply_delta_to_time_at_previous_sample_ns(int argc, VALUE *argv, DDTRACE_UNUSED VALUE self) {
|
|
2528
|
+
VALUE thread;
|
|
2529
|
+
VALUE options;
|
|
2530
|
+
rb_scan_args(argc, argv, "1:", &thread, &options);
|
|
2531
|
+
if (options == Qnil) options = rb_hash_new();
|
|
2532
|
+
|
|
2533
|
+
VALUE cpu_time_delta_ns = rb_hash_lookup2(options, ID2SYM(rb_intern("cpu_time")), INT2FIX(0));
|
|
2534
|
+
VALUE wall_time_delta_ns = rb_hash_lookup2(options, ID2SYM(rb_intern("wall_time")), INT2FIX(0));
|
|
2535
|
+
|
|
2536
|
+
ENFORCE_THREAD(thread);
|
|
2537
|
+
|
|
2538
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2539
|
+
if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2540
|
+
|
|
2541
|
+
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(cpu_time_delta_ns);
|
|
2542
|
+
thread_context->wall_time_at_previous_sample_ns += NUM2LONG(wall_time_delta_ns);
|
|
2543
|
+
|
|
2544
|
+
return Qtrue;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
static VALUE _native_apply_delta_to_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns) {
|
|
2548
|
+
ENFORCE_THREAD(thread);
|
|
2549
|
+
|
|
2550
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2551
|
+
if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2552
|
+
|
|
2553
|
+
thread_context->gvl_waiting_at += NUM2LONG(delta_ns);
|
|
2554
|
+
|
|
2555
|
+
return Qtrue;
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2200
2558
|
#define MAX_SAFE_LOOKUP_SIZE 16
|
|
2201
2559
|
|
|
2202
2560
|
typedef struct { VALUE lookup_key; VALUE result; } safe_lookup_hash_state;
|
|
@@ -2235,12 +2593,11 @@ static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE c
|
|
|
2235
2593
|
thread_context_collector_state *state;
|
|
2236
2594
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2237
2595
|
|
|
2238
|
-
long
|
|
2239
|
-
long system_epoch_time_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, current_monotonic_wall_time_ns);
|
|
2596
|
+
long system_epoch_time_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, monotonic_wall_time_now_ns());
|
|
2240
2597
|
|
|
2241
2598
|
return LONG2NUM(system_epoch_time_ns);
|
|
2242
2599
|
}
|
|
2243
2600
|
|
|
2244
|
-
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self
|
|
2245
|
-
return thread_context_collector_prepare_sample_inside_signal_handler(
|
|
2601
|
+
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self) {
|
|
2602
|
+
return thread_context_collector_prepare_sample_inside_signal_handler() ? Qtrue : Qfalse;
|
|
2246
2603
|
}
|