datadog 2.17.0 → 2.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +686 -1
- data/README.md +0 -1
- data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
- 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 +3 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +344 -126
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -4
- data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +18 -4
- data/ext/datadog_profiling_native_extension/collectors_stack.c +314 -91
- data/ext/datadog_profiling_native_extension/collectors_stack.h +26 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +680 -352
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +18 -5
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +65 -1
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +40 -1
- data/ext/datadog_profiling_native_extension/encoded_profile.c +2 -1
- data/ext/datadog_profiling_native_extension/extconf.rb +48 -34
- 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 +455 -430
- data/ext/datadog_profiling_native_extension/heap_recorder.h +16 -7
- data/ext/datadog_profiling_native_extension/http_transport.c +66 -68
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +18 -15
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -10
- data/ext/datadog_profiling_native_extension/macos_sampler_thread.h +55 -0
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -13
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +73 -69
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +10 -4
- data/ext/datadog_profiling_native_extension/profiling.c +65 -19
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +64 -59
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +20 -16
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +32 -10
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +2 -1
- data/ext/datadog_profiling_native_extension/stack_recorder.c +253 -131
- data/ext/datadog_profiling_native_extension/stack_recorder.h +3 -1
- data/ext/datadog_profiling_native_extension/time_helpers.h +1 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +2 -1
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +9 -4
- data/ext/libdatadog_api/crashtracker.c +16 -15
- data/ext/libdatadog_api/crashtracker_report_exception.c +126 -0
- data/ext/libdatadog_api/datadog_ruby_common.c +65 -1
- data/ext/libdatadog_api/datadog_ruby_common.h +40 -1
- data/ext/libdatadog_api/ddsketch.c +102 -0
- data/ext/libdatadog_api/di.c +203 -0
- data/ext/libdatadog_api/extconf.rb +21 -25
- data/ext/libdatadog_api/feature_flags.c +554 -0
- data/ext/libdatadog_api/feature_flags.h +5 -0
- data/ext/libdatadog_api/helpers.h +27 -0
- data/ext/libdatadog_api/init.c +13 -1
- data/ext/libdatadog_api/library_config.c +88 -37
- data/ext/libdatadog_api/library_config.h +6 -0
- data/ext/libdatadog_api/process_discovery.c +25 -24
- data/ext/libdatadog_extconf_helpers.rb +107 -14
- data/lib/datadog/ai_guard/api_client.rb +84 -0
- data/lib/datadog/ai_guard/autoload.rb +10 -0
- data/lib/datadog/ai_guard/component.rb +44 -0
- data/lib/datadog/ai_guard/configuration/ext.rb +17 -0
- data/lib/datadog/ai_guard/configuration.rb +115 -0
- data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -0
- data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
- data/lib/datadog/ai_guard/contrib/rack/integration.rb +42 -0
- data/lib/datadog/ai_guard/contrib/rack/patcher.rb +26 -0
- data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +97 -0
- data/lib/datadog/ai_guard/contrib/rails/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/rails/patcher.rb +97 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +80 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +30 -0
- data/lib/datadog/ai_guard/evaluation/content_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/content_part.rb +36 -0
- data/lib/datadog/ai_guard/evaluation/message.rb +25 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +36 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +86 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +45 -0
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +18 -0
- data/lib/datadog/ai_guard/evaluation.rb +108 -0
- data/lib/datadog/ai_guard/ext.rb +29 -0
- data/lib/datadog/ai_guard.rb +181 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +66 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +36 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
- data/lib/datadog/appsec/api_security/route_extractor.rb +104 -0
- data/lib/datadog/appsec/api_security/sampler.rb +62 -0
- data/lib/datadog/appsec/api_security.rb +14 -0
- data/lib/datadog/appsec/assets/blocked.html +10 -1
- data/lib/datadog/appsec/assets/blocked.json +1 -1
- data/lib/datadog/appsec/assets/blocked.text +3 -1
- data/lib/datadog/appsec/assets/waf_rules/README.md +30 -36
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +601 -74
- data/lib/datadog/appsec/assets/waf_rules/strict.json +48 -75
- data/lib/datadog/appsec/assets.rb +1 -1
- data/lib/datadog/appsec/autoload.rb +2 -2
- data/lib/datadog/appsec/component.rb +37 -64
- data/lib/datadog/appsec/compressed_json.rb +3 -3
- data/lib/datadog/appsec/configuration.rb +427 -1
- data/lib/datadog/appsec/context.rb +77 -16
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
- data/lib/datadog/appsec/contrib/active_record/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +4 -1
- data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +75 -0
- data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +39 -0
- data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +144 -0
- data/lib/datadog/appsec/contrib/devise/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +4 -2
- data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +2 -1
- data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/patcher.rb +3 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +152 -13
- data/lib/datadog/appsec/contrib/faraday/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/faraday/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +138 -13
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +74 -24
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +4 -2
- data/lib/datadog/appsec/contrib/graphql/integration.rb +2 -1
- data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
- data/lib/datadog/appsec/contrib/rack/ext.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +47 -13
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +40 -19
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +79 -0
- data/lib/datadog/appsec/contrib/rack/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +80 -62
- data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +35 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +47 -5
- data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/patcher.rb +52 -26
- data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +29 -0
- data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +33 -0
- data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rest_client/patcher.rb +3 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +167 -12
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +63 -10
- data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +11 -21
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +31 -0
- data/lib/datadog/appsec/counter_sampler.rb +25 -0
- data/lib/datadog/appsec/default_header_tags.rb +52 -0
- data/lib/datadog/appsec/event.rb +14 -47
- data/lib/datadog/appsec/ext.rb +3 -0
- data/lib/datadog/appsec/instrumentation/gateway/argument.rb +17 -1
- data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +2 -3
- data/lib/datadog/appsec/instrumentation/gateway.rb +2 -15
- data/lib/datadog/appsec/metrics/collector.rb +47 -5
- data/lib/datadog/appsec/metrics/exporter.rb +16 -3
- data/lib/datadog/appsec/metrics/telemetry.rb +21 -4
- data/lib/datadog/appsec/metrics/telemetry_exporter.rb +47 -0
- data/lib/datadog/appsec/metrics.rb +5 -4
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +8 -6
- data/lib/datadog/appsec/processor/rule_loader.rb +5 -6
- data/lib/datadog/appsec/remote.rb +51 -76
- data/lib/datadog/appsec/response.rb +18 -4
- data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
- data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
- data/lib/datadog/appsec/route_normalizer.rb +80 -0
- data/lib/datadog/appsec/security_engine/engine.rb +197 -0
- data/lib/datadog/appsec/security_engine/result.rb +45 -9
- data/lib/datadog/appsec/security_engine/runner.rb +44 -21
- data/lib/datadog/appsec/security_event.rb +5 -7
- data/lib/datadog/appsec/thread_safe_ref.rb +61 -0
- data/lib/datadog/appsec/trace_keeper.rb +36 -0
- data/lib/datadog/appsec/utils/hash_coercion.rb +23 -0
- data/lib/datadog/appsec/utils/http/body.rb +38 -0
- data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
- data/lib/datadog/appsec/utils/http/media_range.rb +2 -1
- data/lib/datadog/appsec/utils/http/media_type.rb +32 -26
- data/lib/datadog/appsec/utils/http/url_encoded.rb +52 -0
- data/lib/datadog/appsec.rb +10 -17
- data/lib/datadog/auto_instrument_base.rb +2 -1
- data/lib/datadog/core/configuration/agent_settings.rb +52 -0
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +5 -47
- data/lib/datadog/core/configuration/base.rb +17 -5
- data/lib/datadog/core/configuration/components.rb +192 -20
- data/lib/datadog/core/configuration/components_state.rb +6 -1
- data/lib/datadog/core/configuration/config_helper.rb +109 -0
- data/lib/datadog/core/configuration/deprecations.rb +36 -0
- data/lib/datadog/core/configuration/ext.rb +0 -1
- data/lib/datadog/core/configuration/option.rb +95 -65
- data/lib/datadog/core/configuration/option_definition.rb +42 -23
- data/lib/datadog/core/configuration/options.rb +57 -18
- data/lib/datadog/core/configuration/settings.rb +160 -25
- data/lib/datadog/core/configuration/stable_config.rb +11 -2
- data/lib/datadog/core/configuration/supported_configurations.rb +413 -0
- data/lib/datadog/core/configuration.rb +4 -4
- 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 +73 -21
- data/lib/datadog/core/crashtracking/tag_builder.rb +9 -21
- data/lib/datadog/core/ddsketch.rb +19 -0
- data/lib/datadog/core/deprecations.rb +2 -2
- data/lib/datadog/core/diagnostics/environment_logger.rb +3 -1
- data/lib/datadog/core/environment/agent_info.rb +65 -1
- data/lib/datadog/core/environment/cgroup.rb +52 -25
- data/lib/datadog/core/environment/container.rb +140 -46
- data/lib/datadog/core/environment/ext.rb +13 -2
- data/lib/datadog/core/environment/git.rb +2 -2
- data/lib/datadog/core/environment/identity.rb +40 -4
- data/lib/datadog/core/environment/process.rb +122 -0
- data/lib/datadog/core/environment/socket.rb +13 -0
- data/lib/datadog/core/environment/variable_helpers.rb +3 -3
- data/lib/datadog/core/environment/yjit.rb +2 -1
- data/lib/datadog/core/error.rb +6 -6
- data/lib/datadog/core/evp.rb +11 -0
- data/lib/datadog/core/feature_flags.rb +61 -0
- data/lib/datadog/core/knuth_sampler.rb +57 -0
- data/lib/datadog/core/logger.rb +1 -1
- data/lib/datadog/core/metrics/client.rb +7 -7
- data/lib/datadog/core/metrics/logging.rb +1 -1
- data/lib/datadog/core/pin.rb +8 -8
- data/lib/datadog/core/process_discovery/tracer_memfd.rb +13 -0
- data/lib/datadog/core/process_discovery.rb +51 -21
- data/lib/datadog/core/rate_limiter.rb +11 -1
- data/lib/datadog/core/remote/client/capabilities.rb +49 -6
- data/lib/datadog/core/remote/client.rb +15 -7
- data/lib/datadog/core/remote/component.rb +64 -36
- data/lib/datadog/core/remote/configuration/content.rb +15 -2
- data/lib/datadog/core/remote/configuration/digest.rb +14 -7
- data/lib/datadog/core/remote/configuration/repository.rb +13 -1
- data/lib/datadog/core/remote/configuration/target.rb +13 -6
- data/lib/datadog/core/remote/transport/config.rb +9 -36
- data/lib/datadog/core/remote/transport/http/config.rb +15 -55
- data/lib/datadog/core/remote/transport/http/negotiation.rb +14 -44
- data/lib/datadog/core/remote/transport/http.rb +15 -24
- data/lib/datadog/core/remote/transport/negotiation.rb +8 -33
- data/lib/datadog/core/remote/worker.rb +25 -37
- data/lib/datadog/core/runtime/ext.rb +0 -1
- data/lib/datadog/core/runtime/metrics.rb +11 -2
- data/lib/datadog/core/semaphore.rb +1 -4
- data/lib/datadog/core/tag_builder.rb +52 -0
- data/lib/datadog/core/tag_normalizer.rb +84 -0
- data/lib/datadog/core/telemetry/component.rb +65 -17
- data/lib/datadog/core/telemetry/emitter.rb +6 -6
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -3
- data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
- data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
- data/lib/datadog/core/telemetry/event/app_started.rb +203 -89
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
- data/lib/datadog/core/telemetry/event.rb +2 -7
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/core/telemetry/logger.rb +5 -2
- data/lib/datadog/core/telemetry/logging.rb +22 -4
- data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
- data/lib/datadog/core/telemetry/request.rb +17 -5
- data/lib/datadog/core/telemetry/transport/http/telemetry.rb +8 -34
- data/lib/datadog/core/telemetry/transport/http.rb +21 -16
- data/lib/datadog/core/telemetry/transport/telemetry.rb +8 -17
- data/lib/datadog/core/telemetry/worker.rb +108 -32
- data/lib/datadog/core/transport/ext.rb +3 -0
- data/lib/datadog/core/transport/http/adapters/net.rb +17 -2
- data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
- data/lib/datadog/core/transport/http/api/instance.rb +4 -21
- data/lib/datadog/core/transport/http/builder.rb +11 -7
- data/lib/datadog/core/transport/http/client.rb +80 -0
- data/lib/datadog/core/transport/http/env.rb +8 -0
- data/lib/datadog/core/transport/http/response.rb +4 -0
- data/lib/datadog/core/transport/http.rb +24 -19
- data/lib/datadog/core/transport/parcel.rb +61 -9
- data/lib/datadog/core/transport/response.rb +16 -2
- data/lib/datadog/core/transport/transport.rb +90 -0
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
- data/lib/datadog/core/utils/{base64.rb → base64_codec.rb} +4 -3
- data/lib/datadog/core/utils/enumerable_compat.rb +29 -0
- data/lib/datadog/core/utils/fnv.rb +26 -0
- data/lib/datadog/core/utils/forking.rb +3 -1
- data/lib/datadog/core/utils/hash.rb +0 -23
- data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -14
- data/lib/datadog/core/utils/network.rb +22 -1
- data/lib/datadog/core/utils/only_once.rb +1 -1
- data/lib/datadog/core/utils/only_once_successful.rb +8 -2
- data/lib/datadog/core/utils/safe_dup.rb +2 -2
- data/lib/datadog/core/utils/sequence.rb +2 -0
- data/lib/datadog/core/utils/spawn_monkey_patch.rb +68 -0
- data/lib/datadog/core/utils/time.rb +1 -1
- data/lib/datadog/core/utils.rb +10 -1
- data/lib/datadog/core/workers/async.rb +11 -2
- data/lib/datadog/core/workers/interval_loop.rb +51 -3
- data/lib/datadog/core/workers/polling.rb +2 -0
- data/lib/datadog/core/workers/queue.rb +96 -1
- data/lib/datadog/core/workers/runtime_metrics.rb +9 -1
- data/lib/datadog/core.rb +11 -2
- data/lib/datadog/data_streams/configuration.rb +50 -0
- data/lib/datadog/data_streams/ext.rb +11 -0
- data/lib/datadog/data_streams/extensions.rb +16 -0
- data/lib/datadog/data_streams/pathway_context.rb +169 -0
- data/lib/datadog/data_streams/processor.rb +511 -0
- data/lib/datadog/data_streams/transport/http/stats.rb +58 -0
- data/lib/datadog/data_streams/transport/http.rb +36 -0
- data/lib/datadog/data_streams/transport/stats.rb +39 -0
- data/lib/datadog/data_streams.rb +100 -0
- data/lib/datadog/di/base.rb +15 -7
- data/lib/datadog/di/boot.rb +11 -1
- data/lib/datadog/di/code_tracker.rb +217 -15
- data/lib/datadog/di/component.rb +143 -48
- data/lib/datadog/di/configuration.rb +235 -2
- data/lib/datadog/di/context.rb +76 -0
- data/lib/datadog/di/contrib/active_record.rb +34 -5
- data/lib/datadog/di/el/compiler.rb +168 -0
- data/lib/datadog/di/el/evaluator.rb +159 -0
- data/lib/datadog/di/el/expression.rb +42 -0
- data/lib/datadog/di/el.rb +5 -0
- data/lib/datadog/di/error.rb +44 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +620 -132
- data/lib/datadog/di/logger.rb +2 -2
- data/lib/datadog/di/probe.rb +78 -15
- data/lib/datadog/di/probe_builder.rb +42 -3
- data/lib/datadog/di/probe_file_loader/railtie.rb +15 -0
- data/lib/datadog/di/probe_file_loader.rb +87 -0
- data/lib/datadog/di/probe_manager.rb +195 -100
- data/lib/datadog/di/probe_notification_builder.rb +285 -100
- data/lib/datadog/di/probe_notifier_worker.rb +120 -36
- data/lib/datadog/di/probe_repository.rb +198 -0
- data/lib/datadog/di/proc_responder.rb +36 -0
- data/lib/datadog/di/redactor.rb +24 -2
- data/lib/datadog/di/remote.rb +258 -95
- data/lib/datadog/di/serializer.rb +297 -20
- data/lib/datadog/di/transport/diagnostics.rb +12 -42
- data/lib/datadog/di/transport/http/diagnostics.rb +4 -34
- data/lib/datadog/di/transport/http/input.rb +12 -34
- data/lib/datadog/di/transport/http.rb +43 -19
- data/lib/datadog/di/transport/input.rb +121 -39
- data/lib/datadog/di/utils.rb +42 -14
- data/lib/datadog/di.rb +203 -10
- data/lib/datadog/error_tracking/collector.rb +2 -1
- data/lib/datadog/error_tracking/component.rb +2 -2
- data/lib/datadog/error_tracking/configuration.rb +55 -2
- data/lib/datadog/error_tracking/filters.rb +2 -2
- data/lib/datadog/kit/appsec/events/v2.rb +253 -0
- data/lib/datadog/kit/appsec/events.rb +11 -10
- data/lib/datadog/kit/enable_core_dumps.rb +1 -1
- data/lib/datadog/kit/identity.rb +17 -11
- data/lib/datadog/kit/tracing/method_tracer.rb +135 -0
- data/lib/datadog/open_feature/component.rb +100 -0
- data/lib/datadog/open_feature/configuration.rb +37 -0
- data/lib/datadog/open_feature/evaluation_engine.rb +70 -0
- data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
- data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
- data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
- data/lib/datadog/open_feature/exposures/event.rb +60 -0
- data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
- data/lib/datadog/open_feature/exposures/worker.rb +116 -0
- data/lib/datadog/open_feature/ext.rb +16 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
- data/lib/datadog/open_feature/hooks/flag_eval_metrics_hook.rb +49 -0
- data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
- data/lib/datadog/open_feature/native_evaluator.rb +60 -0
- data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
- data/lib/datadog/open_feature/provider.rb +191 -0
- data/lib/datadog/open_feature/remote.rb +67 -0
- data/lib/datadog/open_feature/resolution_details.rb +35 -0
- data/lib/datadog/open_feature/transport.rb +121 -0
- data/lib/datadog/open_feature.rb +19 -0
- data/lib/datadog/opentelemetry/api/baggage.rb +3 -3
- data/lib/datadog/opentelemetry/api/context.rb +10 -9
- data/lib/datadog/opentelemetry/configuration/settings.rb +226 -0
- data/lib/datadog/opentelemetry/ext.rb +9 -0
- data/lib/datadog/opentelemetry/logs.rb +98 -0
- data/lib/datadog/opentelemetry/metrics.rb +90 -0
- data/lib/datadog/opentelemetry/sdk/configurator.rb +66 -2
- data/lib/datadog/opentelemetry/sdk/id_generator.rb +16 -10
- data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +30 -0
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +28 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +13 -7
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +12 -9
- data/lib/datadog/opentelemetry/sdk/trace/span.rb +14 -10
- data/lib/datadog/opentelemetry/sdk.rb +19 -0
- data/lib/datadog/opentelemetry/signal_configuration.rb +53 -0
- data/lib/datadog/opentelemetry/trace.rb +4 -4
- data/lib/datadog/opentelemetry.rb +4 -0
- data/lib/datadog/profiling/collectors/code_provenance.rb +83 -15
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +52 -3
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +13 -4
- data/lib/datadog/profiling/collectors/info.rb +59 -4
- data/lib/datadog/profiling/collectors/thread_context.rb +17 -5
- data/lib/datadog/profiling/component.rb +73 -49
- data/lib/datadog/profiling/exporter.rb +48 -13
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +24 -5
- data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
- data/lib/datadog/profiling/ext.rb +2 -15
- data/lib/datadog/profiling/flush.rb +23 -11
- data/lib/datadog/profiling/http_transport.rb +24 -11
- data/lib/datadog/profiling/load_native_extension.rb +2 -2
- data/lib/datadog/profiling/profiler.rb +31 -12
- data/lib/datadog/profiling/scheduler.rb +12 -10
- data/lib/datadog/profiling/sequence_tracker.rb +44 -0
- data/lib/datadog/profiling/stack_recorder.rb +5 -13
- data/lib/datadog/profiling/tag_builder.rb +42 -38
- data/lib/datadog/profiling/tasks/exec.rb +10 -5
- data/lib/datadog/profiling/tasks/help.rb +1 -0
- data/lib/datadog/profiling/tasks/setup.rb +4 -2
- data/lib/datadog/profiling.rb +8 -11
- data/lib/datadog/ruby_version.rb +25 -0
- data/lib/datadog/single_step_instrument.rb +10 -1
- data/lib/datadog/symbol_database/component.rb +734 -0
- data/lib/datadog/symbol_database/configuration.rb +73 -0
- data/lib/datadog/symbol_database/extensions.rb +19 -0
- data/lib/datadog/symbol_database/extractor.rb +1110 -0
- data/lib/datadog/symbol_database/file_hash.rb +48 -0
- data/lib/datadog/symbol_database/logger.rb +43 -0
- data/lib/datadog/symbol_database/remote.rb +180 -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 +10 -10
- data/lib/datadog/tracing/component.rb +17 -17
- data/lib/datadog/tracing/configuration/dynamic.rb +6 -8
- data/lib/datadog/tracing/configuration/ext.rb +25 -5
- data/lib/datadog/tracing/configuration/settings.rb +125 -11
- data/lib/datadog/tracing/context.rb +2 -2
- data/lib/datadog/tracing/contrib/action_cable/event.rb +1 -1
- data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +4 -1
- data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +4 -1
- data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +4 -1
- data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +4 -1
- data/lib/datadog/tracing/contrib/action_cable/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/action_mailer/event.rb +4 -1
- data/lib/datadog/tracing/contrib/action_mailer/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +29 -11
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +47 -12
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +3 -1
- data/lib/datadog/tracing/contrib/action_pack/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/action_pack/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
- data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +4 -1
- data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/event.rb +8 -8
- data/lib/datadog/tracing/contrib/active_job/events/discard.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/events/perform.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +6 -3
- data/lib/datadog/tracing/contrib/active_job/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
- data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
- data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +2 -2
- data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +4 -4
- data/lib/datadog/tracing/contrib/active_model_serializers/integration.rb +1 -2
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +3 -3
- data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +2 -2
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +7 -10
- data/lib/datadog/tracing/contrib/active_record/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/utils.rb +16 -16
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +15 -12
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +1 -4
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +13 -0
- data/lib/datadog/tracing/contrib/active_support/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +2 -1
- data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +8 -10
- data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +8 -10
- data/lib/datadog/tracing/contrib/aws/parsed_context.rb +3 -1
- data/lib/datadog/tracing/contrib/aws/patcher.rb +5 -1
- data/lib/datadog/tracing/contrib/aws/service/base.rb +2 -1
- data/lib/datadog/tracing/contrib/aws/service/dynamodb.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/service/eventbridge.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/service/kinesis.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/service/s3.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/service/sns.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/service/sqs.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/service/states.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/services.rb +7 -7
- data/lib/datadog/tracing/contrib/component.rb +3 -3
- data/lib/datadog/tracing/contrib/concurrent_ruby/async_patch.rb +1 -1
- data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +1 -1
- data/lib/datadog/tracing/contrib/concurrent_ruby/future_patch.rb +1 -1
- data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/concurrent_ruby/promises_future_patch.rb +1 -1
- data/lib/datadog/tracing/contrib/configurable.rb +22 -7
- data/lib/datadog/tracing/contrib/configuration/resolver.rb +14 -4
- data/lib/datadog/tracing/contrib/configuration/resolvers/pattern_resolver.rb +4 -4
- data/lib/datadog/tracing/contrib/dalli/ext.rb +3 -2
- data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +1 -5
- data/lib/datadog/tracing/contrib/dalli/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/dalli/quantize.rb +1 -1
- data/lib/datadog/tracing/contrib/delayed_job/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +2 -0
- data/lib/datadog/tracing/contrib/delayed_job/server_internal_tracer/worker.rb +1 -0
- data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +3 -2
- data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +4 -4
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +47 -53
- data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +7 -7
- data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +7 -8
- data/lib/datadog/tracing/contrib/ethon/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/ethon/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +1 -8
- data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +16 -5
- data/lib/datadog/tracing/contrib/excon/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/excon/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/excon/middleware.rb +5 -9
- data/lib/datadog/tracing/contrib/ext.rb +6 -4
- data/lib/datadog/tracing/contrib/extensions.rb +28 -11
- data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +16 -9
- data/lib/datadog/tracing/contrib/faraday/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/faraday/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +7 -9
- data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +18 -15
- data/lib/datadog/tracing/contrib/grape/instrumentation.rb +13 -8
- data/lib/datadog/tracing/contrib/grape/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/grape/patcher.rb +6 -1
- data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
- data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/graphql/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +105 -63
- data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +11 -15
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +6 -10
- data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/grpc/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/grpc/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc.rb +1 -0
- data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/hanami/ext.rb +2 -2
- data/lib/datadog/tracing/contrib/hanami/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +2 -1
- data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +10 -11
- data/lib/datadog/tracing/contrib/http/configuration/settings.rb +16 -5
- data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +4 -4
- data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/http/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +9 -13
- data/lib/datadog/tracing/contrib/http/integration.rb +0 -2
- data/lib/datadog/tracing/contrib/http.rb +1 -0
- data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +16 -5
- data/lib/datadog/tracing/contrib/httpclient/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +10 -10
- data/lib/datadog/tracing/contrib/httpclient/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +16 -5
- data/lib/datadog/tracing/contrib/httprb/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +8 -12
- data/lib/datadog/tracing/contrib/httprb/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/event.rb +2 -1
- data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/join_group.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/leave_group.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/sync_group.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
- data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
- data/lib/datadog/tracing/contrib/kafka/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
- data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/karafka/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
- data/lib/datadog/tracing/contrib/karafka/monitor.rb +24 -13
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +39 -8
- data/lib/datadog/tracing/contrib/karafka.rb +1 -0
- data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +1 -1
- data/lib/datadog/tracing/contrib/lograge/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +4 -2
- data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/mongodb/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/mongodb/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/mongodb/parsers.rb +6 -6
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +11 -14
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/mysql2/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +19 -12
- data/lib/datadog/tracing/contrib/mysql2/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/opensearch/ext.rb +3 -2
- data/lib/datadog/tracing/contrib/opensearch/integration.rb +1 -2
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +64 -70
- data/lib/datadog/tracing/contrib/opensearch/quantize.rb +7 -7
- data/lib/datadog/tracing/contrib/patcher.rb +7 -9
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +3 -6
- data/lib/datadog/tracing/contrib/pg/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/presto/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/presto/instrumentation.rb +9 -11
- data/lib/datadog/tracing/contrib/presto/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +1 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +13 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +6 -2
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +25 -1
- data/lib/datadog/tracing/contrib/que/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/que/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/que/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/que/tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/racecar/event.rb +2 -6
- data/lib/datadog/tracing/contrib/racecar/events/batch.rb +2 -2
- data/lib/datadog/tracing/contrib/racecar/events/consume.rb +1 -1
- data/lib/datadog/tracing/contrib/racecar/events/message.rb +2 -2
- data/lib/datadog/tracing/contrib/racecar/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +11 -1
- data/lib/datadog/tracing/contrib/rack/ext.rb +28 -0
- data/lib/datadog/tracing/contrib/rack/header_collection.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/header_tagging.rb +55 -32
- data/lib/datadog/tracing/contrib/rack/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +83 -44
- data/lib/datadog/tracing/contrib/rack/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/rack/request_queue.rb +4 -3
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +67 -0
- data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +126 -2
- data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +6 -3
- data/lib/datadog/tracing/contrib/rails/ext.rb +3 -1
- data/lib/datadog/tracing/contrib/rails/integration.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/log_injection.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/middlewares.rb +3 -3
- data/lib/datadog/tracing/contrib/rails/patcher.rb +4 -2
- data/lib/datadog/tracing/contrib/rails/runner.rb +64 -40
- data/lib/datadog/tracing/contrib/rake/instrumentation.rb +10 -8
- data/lib/datadog/tracing/contrib/rake/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +2 -2
- data/lib/datadog/tracing/contrib/redis/ext.rb +3 -2
- data/lib/datadog/tracing/contrib/redis/integration.rb +2 -2
- data/lib/datadog/tracing/contrib/redis/patcher.rb +4 -4
- data/lib/datadog/tracing/contrib/redis/quantize.rb +2 -2
- data/lib/datadog/tracing/contrib/redis/tags.rb +2 -7
- data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +6 -4
- data/lib/datadog/tracing/contrib/registerable.rb +11 -0
- data/lib/datadog/tracing/contrib/registry.rb +1 -1
- data/lib/datadog/tracing/contrib/resque/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/resque/resque_job.rb +2 -1
- data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/rest_client/ext.rb +4 -2
- data/lib/datadog/tracing/contrib/rest_client/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +8 -9
- data/lib/datadog/tracing/contrib/roda/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/roda/instrumentation.rb +8 -3
- data/lib/datadog/tracing/contrib/roda/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +1 -1
- data/lib/datadog/tracing/contrib/semantic_logger/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/sequel/database.rb +6 -5
- data/lib/datadog/tracing/contrib/sequel/dataset.rb +2 -1
- data/lib/datadog/tracing/contrib/sequel/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/sequel/utils.rb +1 -6
- data/lib/datadog/tracing/contrib/shoryuken/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq/ext.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq/integration.rb +1 -1
- 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 +8 -4
- data/lib/datadog/tracing/contrib/sidekiq/utils.rb +1 -1
- data/lib/datadog/tracing/contrib/sidekiq.rb +1 -0
- data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/sinatra/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/sinatra/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +40 -39
- data/lib/datadog/tracing/contrib/sneakers/integration.rb +15 -4
- data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/span_attribute_schema.rb +1 -1
- data/lib/datadog/tracing/contrib/status_range_matcher.rb +13 -1
- data/lib/datadog/tracing/contrib/stripe/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/stripe/request.rb +2 -2
- data/lib/datadog/tracing/contrib/sucker_punch/instrumentation.rb +1 -0
- data/lib/datadog/tracing/contrib/sucker_punch/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/trilogy/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -6
- data/lib/datadog/tracing/contrib/trilogy/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/utils/quantization/{hash.rb → hash_formatter.rb} +15 -13
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +6 -6
- data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +31 -0
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +50 -0
- data/lib/datadog/tracing/contrib/waterdrop/ext.rb +18 -0
- data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
- data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
- data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +49 -0
- data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
- data/lib/datadog/tracing/contrib/waterdrop.rb +42 -0
- data/lib/datadog/tracing/contrib.rb +9 -0
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +12 -4
- data/lib/datadog/tracing/distributed/b3_single.rb +1 -1
- data/lib/datadog/tracing/distributed/baggage.rb +133 -12
- data/lib/datadog/tracing/distributed/datadog.rb +17 -16
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +11 -26
- data/lib/datadog/tracing/distributed/helpers.rb +1 -1
- data/lib/datadog/tracing/distributed/none.rb +4 -2
- data/lib/datadog/tracing/distributed/propagation.rb +39 -4
- data/lib/datadog/tracing/distributed/propagation_policy.rb +1 -1
- data/lib/datadog/tracing/distributed/trace_context.rb +107 -78
- data/lib/datadog/tracing/event.rb +5 -7
- data/lib/datadog/tracing/ext.rb +9 -0
- data/lib/datadog/tracing/flush.rb +1 -1
- data/lib/datadog/tracing/metadata/analytics.rb +1 -1
- data/lib/datadog/tracing/metadata/ext.rb +23 -1
- data/lib/datadog/tracing/metadata/tagging.rb +6 -6
- data/lib/datadog/tracing/pipeline/span_filter.rb +3 -1
- data/lib/datadog/tracing/pipeline/span_processor.rb +3 -1
- data/lib/datadog/tracing/pipeline.rb +2 -2
- data/lib/datadog/tracing/remote.rb +36 -13
- data/lib/datadog/tracing/sampling/ext.rb +2 -2
- data/lib/datadog/tracing/sampling/priority_sampler.rb +16 -1
- data/lib/datadog/tracing/sampling/rate_sampler.rb +8 -19
- data/lib/datadog/tracing/sampling/rule.rb +1 -1
- data/lib/datadog/tracing/sampling/rule_sampler.rb +77 -48
- data/lib/datadog/tracing/sampling/span/rule_parser.rb +3 -3
- data/lib/datadog/tracing/sampling/span/sampler.rb +0 -7
- data/lib/datadog/tracing/span.rb +2 -2
- data/lib/datadog/tracing/span_event.rb +12 -12
- data/lib/datadog/tracing/span_link.rb +12 -12
- data/lib/datadog/tracing/span_operation.rb +63 -19
- data/lib/datadog/tracing/sync_writer.rb +1 -2
- data/lib/datadog/tracing/trace_digest.rb +28 -23
- data/lib/datadog/tracing/trace_operation.rb +209 -98
- data/lib/datadog/tracing/trace_segment.rb +2 -2
- data/lib/datadog/tracing/tracer.rb +113 -52
- data/lib/datadog/tracing/transport/http/client.rb +12 -26
- data/lib/datadog/tracing/transport/http/traces.rb +4 -52
- data/lib/datadog/tracing/transport/http.rb +15 -9
- data/lib/datadog/tracing/transport/io/client.rb +11 -14
- data/lib/datadog/tracing/transport/io/traces.rb +28 -34
- data/lib/datadog/tracing/transport/statistics.rb +1 -1
- data/lib/datadog/tracing/transport/trace_formatter.rb +22 -0
- data/lib/datadog/tracing/transport/traces.rb +14 -82
- data/lib/datadog/tracing/workers.rb +4 -3
- data/lib/datadog/tracing/writer.rb +1 -1
- data/lib/datadog/tracing.rb +2 -2
- data/lib/datadog/version.rb +2 -2
- data/lib/datadog.rb +14 -0
- metadata +201 -40
- data/ext/datadog_profiling_native_extension/clock_id_noop.c +0 -21
- data/ext/libdatadog_api/macos_development.md +0 -26
- data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -321
- data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -1023
- data/lib/datadog/appsec/configuration/settings.rb +0 -342
- data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
- data/lib/datadog/appsec/processor/rule_merger.rb +0 -171
- data/lib/datadog/appsec/processor.rb +0 -107
- data/lib/datadog/core/remote/transport/http/api.rb +0 -53
- data/lib/datadog/core/remote/transport/http/client.rb +0 -49
- data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
- data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
- data/lib/datadog/core/transport/http/api/spec.rb +0 -36
- data/lib/datadog/di/configuration/settings.rb +0 -212
- data/lib/datadog/di/transport/http/api.rb +0 -42
- data/lib/datadog/di/transport/http/client.rb +0 -47
- data/lib/datadog/error_tracking/configuration/settings.rb +0 -63
- data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
- data/lib/datadog/tracing/transport/http/api.rb +0 -44
- data/lib/datadog/tracing/workers/trace_writer.rb +0 -199
|
@@ -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"
|
|
@@ -8,9 +9,11 @@
|
|
|
8
9
|
#include "helpers.h"
|
|
9
10
|
#include "libdatadog_helpers.h"
|
|
10
11
|
#include "private_vm_api_access.h"
|
|
12
|
+
#include "ruby_helpers.h"
|
|
11
13
|
#include "stack_recorder.h"
|
|
12
14
|
#include "time_helpers.h"
|
|
13
15
|
#include "unsafe_api_calls_check.h"
|
|
16
|
+
#include "extconf.h"
|
|
14
17
|
|
|
15
18
|
// Used to trigger sampling of threads, based on external "events", such as:
|
|
16
19
|
// * periodic timer for cpu-time and wall-time
|
|
@@ -74,14 +77,15 @@
|
|
|
74
77
|
#define THREAD_ID_LIMIT_CHARS 44 // Why 44? "#{2**64} (#{2**64})".size + 1 for \0
|
|
75
78
|
#define THREAD_INVOKE_LOCATION_LIMIT_CHARS 512
|
|
76
79
|
#define IS_WALL_TIME true
|
|
77
|
-
#define
|
|
80
|
+
#define IS_CPU_TIME false
|
|
78
81
|
#define MISSING_TRACER_CONTEXT_KEY 0
|
|
79
82
|
#define TIME_BETWEEN_GC_EVENTS_NS MILLIS_AS_NS(10)
|
|
83
|
+
#define GVL_SUSPENDED ((uint64_t)1)
|
|
84
|
+
#define GVL_RUNNING ((uint64_t)0)
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
#define GVL_WAITING_ENABLED_EMPTY RUBY_FIXNUM_MAX
|
|
86
|
+
#define MAX(a, b) ((a) < (b) ? (b) : (a))
|
|
87
|
+
|
|
88
|
+
static ID dd_per_thread_context_id; // Hidden ivar (no @ prefix, inaccessible from Ruby)
|
|
85
89
|
|
|
86
90
|
static ID at_active_span_id; // id of :@active_span in Ruby
|
|
87
91
|
static ID at_active_trace_id; // id of :@active_trace in Ruby
|
|
@@ -105,12 +109,15 @@ static ID server_id; // id of :server in Ruby
|
|
|
105
109
|
static ID otel_context_storage_id; // id of :__opentelemetry_context_storage__ in Ruby
|
|
106
110
|
static ID otel_fiber_context_storage_id; // id of :@opentelemetry_context in Ruby
|
|
107
111
|
|
|
108
|
-
// This is
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
// This is mutable and gets set last-writer-wins style whenever a new `ThreadContext` is created.
|
|
113
|
+
//
|
|
114
|
+
// The initial value should be kept in sync with the default for DD_PROFILING_MAX_FRAMES
|
|
115
|
+
// in settings.rb. See `initialize_context` for details on why this is needed/used.
|
|
116
|
+
static uint16_t latest_max_frames = 400;
|
|
117
|
+
|
|
118
|
+
// Global tracepoint for RUBY_EVENT_THREAD_BEGIN. Created and enabled once when the first ThreadContext collector is initialized.
|
|
119
|
+
static VALUE thread_begin_tracepoint = Qnil;
|
|
120
|
+
|
|
114
121
|
|
|
115
122
|
typedef enum { OTEL_CONTEXT_ENABLED_FALSE, OTEL_CONTEXT_ENABLED_ONLY, OTEL_CONTEXT_ENABLED_BOTH } otel_context_enabled;
|
|
116
123
|
typedef enum { OTEL_CONTEXT_SOURCE_UNKNOWN, OTEL_CONTEXT_SOURCE_FIBER_IVAR, OTEL_CONTEXT_SOURCE_FIBER_LOCAL } otel_context_source;
|
|
@@ -120,27 +127,18 @@ typedef struct {
|
|
|
120
127
|
// Note: Places in this file that usually need to be changed when this struct is changed are tagged with
|
|
121
128
|
// "Update this when modifying state struct"
|
|
122
129
|
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
uint16_t max_frames;
|
|
126
|
-
// Hashmap <Thread Object, per_thread_context>
|
|
127
|
-
st_table *hash_map_per_thread_context;
|
|
130
|
+
// Output buffer for stack traces, passed to sample_thread()
|
|
131
|
+
sample_locations locations;
|
|
128
132
|
// Datadog::Profiling::StackRecorder instance
|
|
129
133
|
VALUE recorder_instance;
|
|
130
134
|
// If the tracer is available and enabled, this will be the fiber-local symbol for accessing its running context,
|
|
131
135
|
// to enable code hotspots and endpoint aggregation.
|
|
132
136
|
// When not available, this is set to MISSING_TRACER_CONTEXT_KEY.
|
|
133
137
|
ID tracer_context_key;
|
|
134
|
-
// Track how many regular samples we've taken. Does not include garbage collection samples.
|
|
135
|
-
// Currently **outside** of stats struct because we also use it to decide when to clean the contexts, and thus this
|
|
136
|
-
// is not (just) a stat.
|
|
137
|
-
unsigned int sample_count;
|
|
138
138
|
// Reusable array to get list of threads
|
|
139
139
|
VALUE thread_list_buffer;
|
|
140
140
|
// Used to omit endpoint names (retrieved from tracer) from collected data
|
|
141
141
|
bool endpoint_collection_enabled;
|
|
142
|
-
// Used to omit timestamps / timeline events from collected data
|
|
143
|
-
bool timeline_enabled;
|
|
144
142
|
// Used to control context collection
|
|
145
143
|
otel_context_enabled otel_context_enabled;
|
|
146
144
|
// Used to remember where otel context is being stored after we observe it the first time
|
|
@@ -153,12 +151,26 @@ typedef struct {
|
|
|
153
151
|
// Qtrue serves as a marker we've not yet extracted it; when we try to extract it, we set it to an object if
|
|
154
152
|
// successful and Qnil if not.
|
|
155
153
|
VALUE otel_current_span_key;
|
|
154
|
+
// Used to enable native filenames in stack traces
|
|
155
|
+
bool native_filenames_enabled;
|
|
156
|
+
// Used to cache native filename lookup results (Map[void *function_pointer, char *filename])
|
|
157
|
+
st_table *native_filenames_cache;
|
|
158
|
+
// Used to attribute overhead during sampling to this component
|
|
159
|
+
VALUE overhead_filename;
|
|
160
|
+
// Minimum duration of a "Waiting for GVL" period to trigger a sample
|
|
161
|
+
uint32_t waiting_for_gvl_threshold_ns;
|
|
156
162
|
|
|
157
163
|
struct stats {
|
|
164
|
+
// Track how many regular samples we've taken. Does not include garbage collection samples.
|
|
165
|
+
unsigned int sample_count;
|
|
158
166
|
// Track how many garbage collection samples we've taken.
|
|
159
167
|
unsigned int gc_samples;
|
|
160
168
|
// See thread_context_collector_on_gc_start for details
|
|
161
169
|
unsigned int gc_samples_missed_due_to_missing_context;
|
|
170
|
+
// How many per-thread samples were skipped because the thread has been continuously suspended
|
|
171
|
+
// (no GVL) since its previous sample, so its Ruby stack cannot have changed.
|
|
172
|
+
unsigned int inactive_thread_samples_skipped;
|
|
173
|
+
unsigned int profiler_thread_samples_skipped;
|
|
162
174
|
} stats;
|
|
163
175
|
|
|
164
176
|
struct {
|
|
@@ -170,16 +182,89 @@ typedef struct {
|
|
|
170
182
|
} gc_tracking;
|
|
171
183
|
} thread_context_collector_state;
|
|
172
184
|
|
|
173
|
-
// Tracks per-thread state
|
|
174
|
-
|
|
175
|
-
|
|
185
|
+
// Tracks per-thread state.
|
|
186
|
+
// This state is global and lives forever on the Ruby Thread (until the Thread is GC'd).
|
|
187
|
+
// The state is created early on for all threads on the main Ractor
|
|
188
|
+
// (enabling a TracePoint only enables it for the current Ractor).
|
|
189
|
+
// The state is either created when the Thread starts running (via the RUBY_EVENT_THREAD_BEGIN TracePoint),
|
|
190
|
+
// or the first time we create a ThreadContext by iterating Thread.list.
|
|
191
|
+
// Unfortunately that RUBY_EVENT_THREAD_BEGIN TracePoint still fires after some other events:
|
|
192
|
+
// * RUBY_INTERNAL_THREAD_EVENT_RESUMED for the Thread acquiring the GVL for the first time
|
|
193
|
+
// * an early SIGPROF calling handle_sampling_signal()
|
|
194
|
+
// * Ruby might check for interrupts and run postponed jobs (e.g. thread_context_collector_sample)
|
|
195
|
+
// * another RUBY_EVENT_THREAD_BEGIN TracePoint which might run before ours
|
|
196
|
+
// * etc
|
|
197
|
+
// For those cases we have to ignore those events and we cannot assume the state is always set,
|
|
198
|
+
// however this only matters for a very short period when a thread starts.
|
|
199
|
+
struct per_thread_context {
|
|
200
|
+
sampling_buffer sampling_buffer;
|
|
176
201
|
char thread_id[THREAD_ID_LIMIT_CHARS];
|
|
177
202
|
ddog_CharSlice thread_id_char_slice;
|
|
178
203
|
char thread_invoke_location[THREAD_INVOKE_LOCATION_LIMIT_CHARS];
|
|
179
204
|
ddog_CharSlice thread_invoke_location_char_slice;
|
|
180
205
|
thread_cpu_time_id thread_cpu_time_id;
|
|
181
|
-
long cpu_time_at_previous_sample_ns;
|
|
182
|
-
long wall_time_at_previous_sample_ns;
|
|
206
|
+
long cpu_time_at_previous_sample_ns;
|
|
207
|
+
long wall_time_at_previous_sample_ns;
|
|
208
|
+
|
|
209
|
+
// There are 3 possible states for the GVL (per thread), and 3 transitions for which we receive GVL events:
|
|
210
|
+
// Thread holds the GVL
|
|
211
|
+
// on_gvl_released() the thread releases the GVL (RUBY_INTERNAL_THREAD_EVENT_SUSPENDED)
|
|
212
|
+
// Thread runs without the GVL
|
|
213
|
+
// on_gvl_waiting() the thread wants the GVL (RUBY_INTERNAL_THREAD_EVENT_READY)
|
|
214
|
+
// Thread is "Waiting for GVL"
|
|
215
|
+
// on_gvl_running() the thread now got the GVL (RUBY_INTERNAL_THREAD_EVENT_RESUMED)
|
|
216
|
+
// ... and the cycle restarts
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
// --- GVL waiting tracking state machine ---
|
|
220
|
+
//
|
|
221
|
+
// gvl_waiting_at tracks the GVL wait state for each profiled thread:
|
|
222
|
+
//
|
|
223
|
+
// ┌───────────────────────────────────┐
|
|
224
|
+
// │ on_gvl_waiting │
|
|
225
|
+
// │ ▼
|
|
226
|
+
// Not Waiting (0) ◀────────────────── Waiting (> 0)
|
|
227
|
+
// ▲ on_gvl_running │
|
|
228
|
+
// │ (below threshold) │ on_gvl_running (above threshold)
|
|
229
|
+
// │ ▼
|
|
230
|
+
// └─────────────────────────── Sample Pending (< 0)
|
|
231
|
+
// sample / sample_after_gvl_running
|
|
232
|
+
//
|
|
233
|
+
// Not Waiting (0): thread is running or not waiting for the GVL
|
|
234
|
+
// Waiting (> 0): monotonic wall time (ns) when the thread started waiting
|
|
235
|
+
// Sample Pending (< 0): negated timestamp; the wait ended and a sample is pending
|
|
236
|
+
//
|
|
237
|
+
// The field is accessed under the GVL for most functions EXCEPT on_gvl_waiting() which writes to it without the GVL.
|
|
238
|
+
// So we need to pack the above state in a single long to ensure atomicity.
|
|
239
|
+
long gvl_waiting_at;
|
|
240
|
+
|
|
241
|
+
// Per-thread "state + version" word, updated on every GVL state transition. The encoding is:
|
|
242
|
+
// - low bit: current state (1 = currently suspended, 0 = currently running)
|
|
243
|
+
// - bits 1+: monotonic event counter (incremented on every RESUMED)
|
|
244
|
+
// The hooks set the state bit explicitly rather than relying on parity, so the encoding stays
|
|
245
|
+
// correct even when events are not paired properly (as in tests).
|
|
246
|
+
//
|
|
247
|
+
// Note that SUSPENDED can happen multiple times in a row on Ruby 3.2,
|
|
248
|
+
// see https://github.com/DataDog/dd-trace-rb/pull/5777#discussion_r3388560254,
|
|
249
|
+
// the encoding is designed to naturally not change the field in such a case.
|
|
250
|
+
uint64_t gvl_state_change_count;
|
|
251
|
+
// Snapshot of the thread's gvl_state_change_count at the moment we last sampled it.
|
|
252
|
+
// Equality with this snapshot means no GVL transition since the last sample.
|
|
253
|
+
uint64_t gvl_state_change_count_at_previous_sample;
|
|
254
|
+
// True when the previous per-tick sample was skipped by the SUSPENDED-skip optimization, so the
|
|
255
|
+
// flush-before-serialize pass knows it needs to report this thread.
|
|
256
|
+
// As a result, we will accumulate all wall & CPU time as a single batch per reporting period,
|
|
257
|
+
// but this is deemed worth it for this optimization. In any case we don't know exactly
|
|
258
|
+
// at what time a thread was doing CPU work (unless it's on CPU 100% of the time).
|
|
259
|
+
bool was_skipped_at_last_sample;
|
|
260
|
+
// Set as true for CpuAndWallTimeWorker and IdleSamplingHelper threads.
|
|
261
|
+
// When true, per-tick samples are skipped entirely; the thread is sampled only once per
|
|
262
|
+
// reporting period during the on_serialize flush.
|
|
263
|
+
//
|
|
264
|
+
// These threads are always in native code so their stacks aren't interesting;
|
|
265
|
+
// the Profiling::Scheduler thread on the other hand does a lot of different
|
|
266
|
+
// things using a mix of Ruby and native code, so that one isn't considered internal.
|
|
267
|
+
bool is_profiler_internal_thread;
|
|
183
268
|
|
|
184
269
|
struct {
|
|
185
270
|
// Both of these fields are set by on_gc_start and kept until on_gc_finish is called.
|
|
@@ -187,7 +272,7 @@ typedef struct {
|
|
|
187
272
|
long cpu_time_at_start_ns;
|
|
188
273
|
long wall_time_at_start_ns;
|
|
189
274
|
} gc_tracking;
|
|
190
|
-
}
|
|
275
|
+
};
|
|
191
276
|
|
|
192
277
|
// Used to correlate profiles with traces
|
|
193
278
|
typedef struct {
|
|
@@ -205,29 +290,26 @@ typedef struct {
|
|
|
205
290
|
|
|
206
291
|
static void thread_context_collector_typed_data_mark(void *state_ptr);
|
|
207
292
|
static void thread_context_collector_typed_data_free(void *state_ptr);
|
|
208
|
-
static
|
|
209
|
-
static
|
|
293
|
+
static void per_thread_context_typed_data_mark(void *ctx_ptr);
|
|
294
|
+
static void per_thread_context_typed_data_free(void *ctx_ptr);
|
|
210
295
|
static VALUE _native_new(VALUE klass);
|
|
211
296
|
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
|
212
|
-
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE
|
|
297
|
+
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE allow_exception);
|
|
213
298
|
static VALUE _native_on_gc_start(VALUE self, VALUE collector_instance);
|
|
214
299
|
static VALUE _native_on_gc_finish(VALUE self, VALUE collector_instance);
|
|
215
300
|
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE allow_exception);
|
|
216
301
|
static void update_metrics_and_sample(
|
|
217
302
|
thread_context_collector_state *state,
|
|
218
303
|
VALUE thread_being_sampled,
|
|
219
|
-
VALUE stack_from_thread,
|
|
220
304
|
per_thread_context *thread_context,
|
|
221
|
-
sampling_buffer* sampling_buffer,
|
|
222
305
|
long current_cpu_time_ns,
|
|
223
|
-
long current_monotonic_wall_time_ns
|
|
306
|
+
long current_monotonic_wall_time_ns,
|
|
307
|
+
bool force_sample
|
|
224
308
|
);
|
|
225
309
|
static void trigger_sample_for_thread(
|
|
226
310
|
thread_context_collector_state *state,
|
|
227
|
-
VALUE
|
|
228
|
-
VALUE stack_from_thread,
|
|
311
|
+
VALUE thread_being_sampled,
|
|
229
312
|
per_thread_context *thread_context,
|
|
230
|
-
sampling_buffer* sampling_buffer,
|
|
231
313
|
sample_values values,
|
|
232
314
|
long current_monotonic_wall_time_ns,
|
|
233
315
|
ddog_CharSlice *ruby_vm_type,
|
|
@@ -236,17 +318,13 @@ static void trigger_sample_for_thread(
|
|
|
236
318
|
bool is_safe_to_allocate_objects
|
|
237
319
|
);
|
|
238
320
|
static VALUE _native_thread_list(VALUE self);
|
|
239
|
-
static
|
|
240
|
-
static per_thread_context *
|
|
241
|
-
static void initialize_context(VALUE thread, per_thread_context *thread_context
|
|
242
|
-
static void free_context(per_thread_context* thread_context);
|
|
321
|
+
static void check_frozen_thread(VALUE thread);
|
|
322
|
+
static per_thread_context *get_or_create_context_for(VALUE thread);
|
|
323
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context);
|
|
243
324
|
static VALUE _native_inspect(VALUE self, VALUE collector_instance);
|
|
244
|
-
static VALUE
|
|
245
|
-
static
|
|
246
|
-
static VALUE stats_as_ruby_hash(thread_context_collector_state *state);
|
|
325
|
+
static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context);
|
|
326
|
+
static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash);
|
|
247
327
|
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state);
|
|
248
|
-
static void remove_context_for_dead_threads(thread_context_collector_state *state);
|
|
249
|
-
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, st_data_t _argument);
|
|
250
328
|
static VALUE _native_per_thread_context(VALUE self, VALUE collector_instance);
|
|
251
329
|
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, long gc_start_time_ns, bool is_wall_time);
|
|
252
330
|
static long cpu_time_now_ns(per_thread_context *thread_context);
|
|
@@ -278,16 +356,17 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
|
|
|
278
356
|
static bool handle_gvl_waiting(
|
|
279
357
|
thread_context_collector_state *state,
|
|
280
358
|
VALUE thread_being_sampled,
|
|
281
|
-
VALUE stack_from_thread,
|
|
282
359
|
per_thread_context *thread_context,
|
|
283
|
-
sampling_buffer* sampling_buffer,
|
|
284
360
|
long current_cpu_time_ns
|
|
285
361
|
);
|
|
286
|
-
|
|
287
|
-
static VALUE
|
|
288
|
-
static VALUE
|
|
289
|
-
static VALUE
|
|
290
|
-
static VALUE
|
|
362
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
|
363
|
+
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
364
|
+
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
365
|
+
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
|
|
366
|
+
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
367
|
+
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE allow_exception);
|
|
368
|
+
#endif
|
|
369
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns);
|
|
291
370
|
static void otel_without_ddtrace_trace_identifiers_for(
|
|
292
371
|
thread_context_collector_state *state,
|
|
293
372
|
VALUE thread,
|
|
@@ -298,10 +377,17 @@ static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key)
|
|
|
298
377
|
static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
|
|
299
378
|
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
|
|
300
379
|
static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
380
|
+
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
381
|
+
static VALUE _native_clear_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
382
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
383
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
384
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample);
|
|
385
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused);
|
|
301
386
|
|
|
302
387
|
void collectors_thread_context_init(VALUE profiling_module) {
|
|
303
388
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
|
304
389
|
VALUE collectors_thread_context_class = rb_define_class_under(collectors_module, "ThreadContext", rb_cObject);
|
|
390
|
+
|
|
305
391
|
// Hosts methods used for testing the native code using RSpec
|
|
306
392
|
VALUE testing_module = rb_define_module_under(collectors_thread_context_class, "Testing");
|
|
307
393
|
|
|
@@ -318,7 +404,7 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
318
404
|
rb_define_singleton_method(collectors_thread_context_class, "_native_initialize", _native_initialize, -1);
|
|
319
405
|
rb_define_singleton_method(collectors_thread_context_class, "_native_inspect", _native_inspect, 1);
|
|
320
406
|
rb_define_singleton_method(collectors_thread_context_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
|
321
|
-
rb_define_singleton_method(testing_module, "_native_sample", _native_sample,
|
|
407
|
+
rb_define_singleton_method(testing_module, "_native_sample", _native_sample, 2);
|
|
322
408
|
rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
|
|
323
409
|
rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
|
|
324
410
|
rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
|
|
@@ -330,13 +416,18 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
330
416
|
rb_define_singleton_method(testing_module, "_native_new_empty_thread", _native_new_empty_thread, 0);
|
|
331
417
|
rb_define_singleton_method(testing_module, "_native_sample_skipped_allocation_samples", _native_sample_skipped_allocation_samples, 2);
|
|
332
418
|
rb_define_singleton_method(testing_module, "_native_system_epoch_time_now_ns", _native_system_epoch_time_now_ns, 1);
|
|
419
|
+
rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler, 0);
|
|
420
|
+
rb_define_singleton_method(testing_module, "_native_clear_per_thread_context_for", _native_clear_per_thread_context_for, 1);
|
|
421
|
+
rb_define_singleton_method(testing_module, "_native_remove_per_thread_context_for", _native_remove_per_thread_context_for, 1);
|
|
422
|
+
rb_define_singleton_method(testing_module, "_native_mark_thread_as_profiler_internal", _native_mark_thread_as_profiler_internal, 1);
|
|
333
423
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
334
424
|
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
335
425
|
rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
|
|
336
|
-
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running,
|
|
337
|
-
rb_define_singleton_method(testing_module, "
|
|
338
|
-
rb_define_singleton_method(testing_module, "
|
|
426
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 2);
|
|
427
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_released", _native_on_gvl_released, 1);
|
|
428
|
+
rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 3);
|
|
339
429
|
#endif
|
|
430
|
+
rb_define_singleton_method(testing_module, "_native_apply_delta_to_cpu_time_at_previous_sample_ns", _native_apply_delta_to_cpu_time_at_previous_sample_ns, 2);
|
|
340
431
|
|
|
341
432
|
at_active_span_id = rb_intern_const("@active_span");
|
|
342
433
|
at_active_trace_id = rb_intern_const("@active_trace");
|
|
@@ -357,10 +448,12 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
357
448
|
otel_context_storage_id = rb_intern_const("__opentelemetry_context_storage__");
|
|
358
449
|
otel_fiber_context_storage_id = rb_intern_const("@opentelemetry_context");
|
|
359
450
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
451
|
+
dd_per_thread_context_id = rb_intern_const("dd_per_thread_context");
|
|
452
|
+
|
|
453
|
+
// This will raise if Ruby already ran out of thread-local keys
|
|
454
|
+
per_thread_context_tls_init();
|
|
455
|
+
|
|
456
|
+
rb_global_variable(&thread_begin_tracepoint);
|
|
364
457
|
|
|
365
458
|
gc_profiling_init();
|
|
366
459
|
}
|
|
@@ -385,10 +478,10 @@ static void thread_context_collector_typed_data_mark(void *state_ptr) {
|
|
|
385
478
|
|
|
386
479
|
// Update this when modifying state struct
|
|
387
480
|
rb_gc_mark(state->recorder_instance);
|
|
388
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_mark, 0 /* unused */);
|
|
389
481
|
rb_gc_mark(state->thread_list_buffer);
|
|
390
482
|
rb_gc_mark(state->main_thread);
|
|
391
483
|
rb_gc_mark(state->otel_current_span_key);
|
|
484
|
+
rb_gc_mark(state->overhead_filename);
|
|
392
485
|
}
|
|
393
486
|
|
|
394
487
|
static void thread_context_collector_typed_data_free(void *state_ptr) {
|
|
@@ -398,28 +491,54 @@ static void thread_context_collector_typed_data_free(void *state_ptr) {
|
|
|
398
491
|
|
|
399
492
|
// Important: Remember that we're only guaranteed to see here what's been set in _native_new, aka
|
|
400
493
|
// pointers that have been set NULL there may still be NULL here.
|
|
401
|
-
if (state->locations != NULL) ruby_xfree(state->locations);
|
|
494
|
+
if (state->locations.ptr != NULL) ruby_xfree(state->locations.ptr);
|
|
402
495
|
|
|
403
|
-
|
|
404
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
|
405
|
-
// ...and then the map
|
|
406
|
-
st_free_table(state->hash_map_per_thread_context);
|
|
496
|
+
st_free_table(state->native_filenames_cache);
|
|
407
497
|
|
|
408
498
|
ruby_xfree(state);
|
|
409
499
|
}
|
|
410
500
|
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
501
|
+
// per_thread_context is wrapped in a TypedData Ruby object stored as an ivar on each Ruby Thread.
|
|
502
|
+
// This gives us automatic GC marking (for sampling_buffer iseq VALUEs) and lifecycle management.
|
|
503
|
+
static const rb_data_type_t per_thread_context_typed_data = {
|
|
504
|
+
.wrap_struct_name = "Datadog::Profiling::PerThreadContext",
|
|
505
|
+
.function = {
|
|
506
|
+
.dmark = per_thread_context_typed_data_mark,
|
|
507
|
+
.dfree = per_thread_context_typed_data_free,
|
|
508
|
+
.dsize = NULL,
|
|
509
|
+
},
|
|
510
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
static void per_thread_context_typed_data_mark(void *ctx_ptr) {
|
|
514
|
+
per_thread_context *ctx = (per_thread_context *) ctx_ptr;
|
|
515
|
+
if (sampling_buffer_needs_marking(&ctx->sampling_buffer)) {
|
|
516
|
+
sampling_buffer_mark(&ctx->sampling_buffer);
|
|
517
|
+
}
|
|
416
518
|
}
|
|
417
519
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
520
|
+
static void per_thread_context_typed_data_free(void *ctx_ptr) {
|
|
521
|
+
per_thread_context *ctx = (per_thread_context *) ctx_ptr;
|
|
522
|
+
sampling_buffer_free(&ctx->sampling_buffer);
|
|
523
|
+
free(ctx);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
static VALUE _native_clear_per_thread_context_for(VALUE self, VALUE thread) {
|
|
527
|
+
_native_remove_per_thread_context_for(self, thread);
|
|
528
|
+
get_or_create_context_for(thread);
|
|
529
|
+
return Qnil;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Only for testing: removes the per-thread context without recreating it, so the thread has no context.
|
|
533
|
+
// This simulates a thread that starts running before the RUBY_EVENT_THREAD_BEGIN tracepoint fires.
|
|
534
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
535
|
+
check_frozen_thread(thread);
|
|
536
|
+
per_thread_context *ctx = get_per_thread_context(thread);
|
|
537
|
+
if (ctx != NULL) {
|
|
538
|
+
set_per_thread_context(thread, NULL);
|
|
539
|
+
rb_ivar_set(thread, dd_per_thread_context_id, Qnil);
|
|
540
|
+
}
|
|
541
|
+
return Qnil;
|
|
423
542
|
}
|
|
424
543
|
|
|
425
544
|
static VALUE _native_new(VALUE klass) {
|
|
@@ -429,17 +548,15 @@ static VALUE _native_new(VALUE klass) {
|
|
|
429
548
|
// being leaked.
|
|
430
549
|
|
|
431
550
|
// Update this when modifying state struct
|
|
432
|
-
state->locations = NULL;
|
|
433
|
-
state->
|
|
434
|
-
state->hash_map_per_thread_context =
|
|
435
|
-
// "numtable" is an awful name, but TL;DR it's what should be used when keys are `VALUE`s.
|
|
436
|
-
st_init_numtable();
|
|
551
|
+
state->locations.ptr = NULL;
|
|
552
|
+
state->locations.len = 0;
|
|
437
553
|
state->recorder_instance = Qnil;
|
|
438
554
|
state->tracer_context_key = MISSING_TRACER_CONTEXT_KEY;
|
|
439
555
|
VALUE thread_list_buffer = rb_ary_new();
|
|
440
556
|
state->thread_list_buffer = thread_list_buffer;
|
|
441
557
|
state->endpoint_collection_enabled = true;
|
|
442
|
-
state->
|
|
558
|
+
state->native_filenames_enabled = false;
|
|
559
|
+
state->native_filenames_cache = st_init_numtable();
|
|
443
560
|
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
|
|
444
561
|
state->otel_context_source = OTEL_CONTEXT_SOURCE_UNKNOWN;
|
|
445
562
|
state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
|
|
@@ -471,25 +588,31 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
471
588
|
VALUE max_frames = rb_hash_fetch(options, ID2SYM(rb_intern("max_frames")));
|
|
472
589
|
VALUE tracer_context_key = rb_hash_fetch(options, ID2SYM(rb_intern("tracer_context_key")));
|
|
473
590
|
VALUE endpoint_collection_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("endpoint_collection_enabled")));
|
|
474
|
-
VALUE timeline_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("timeline_enabled")));
|
|
475
591
|
VALUE waiting_for_gvl_threshold_ns = rb_hash_fetch(options, ID2SYM(rb_intern("waiting_for_gvl_threshold_ns")));
|
|
476
592
|
VALUE otel_context_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("otel_context_enabled")));
|
|
593
|
+
VALUE native_filenames_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("native_filenames_enabled")));
|
|
594
|
+
VALUE overhead_filename = rb_hash_fetch(options, ID2SYM(rb_intern("overhead_filename")));
|
|
477
595
|
|
|
478
596
|
ENFORCE_TYPE(max_frames, T_FIXNUM);
|
|
479
597
|
ENFORCE_BOOLEAN(endpoint_collection_enabled);
|
|
480
|
-
ENFORCE_BOOLEAN(timeline_enabled);
|
|
481
598
|
ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
|
|
599
|
+
ENFORCE_BOOLEAN(native_filenames_enabled);
|
|
600
|
+
ENFORCE_TYPE(overhead_filename, T_STRING);
|
|
601
|
+
|
|
602
|
+
uint16_t max_frame_int = sampling_buffer_check_max_frames(NUM2INT(max_frames));
|
|
603
|
+
latest_max_frames = max_frame_int;
|
|
482
604
|
|
|
483
605
|
thread_context_collector_state *state;
|
|
484
606
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
485
607
|
|
|
486
608
|
// Update this when modifying state struct
|
|
487
|
-
state->
|
|
488
|
-
state->locations = ruby_xcalloc(
|
|
489
|
-
// hash_map_per_thread_context is already initialized, nothing to do here
|
|
609
|
+
state->locations.len = max_frame_int;
|
|
610
|
+
state->locations.ptr = ruby_xcalloc(max_frame_int, sizeof(ddog_prof_Location));
|
|
490
611
|
state->recorder_instance = enforce_recorder_instance(recorder_instance);
|
|
612
|
+
recorder_install_on_serialize(recorder_instance, self_instance);
|
|
491
613
|
state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
|
|
492
|
-
state->
|
|
614
|
+
state->native_filenames_enabled = (native_filenames_enabled == Qtrue);
|
|
615
|
+
state->overhead_filename = overhead_filename;
|
|
493
616
|
if (otel_context_enabled == Qfalse || otel_context_enabled == Qnil) {
|
|
494
617
|
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
|
|
495
618
|
} else if (otel_context_enabled == ID2SYM(rb_intern("only"))) {
|
|
@@ -497,10 +620,10 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
497
620
|
} else if (otel_context_enabled == ID2SYM(rb_intern("both"))) {
|
|
498
621
|
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_BOTH;
|
|
499
622
|
} else {
|
|
500
|
-
|
|
623
|
+
raise_error(rb_eArgError, "Unexpected value for otel_context_enabled: %+" PRIsVALUE, otel_context_enabled);
|
|
501
624
|
}
|
|
502
625
|
|
|
503
|
-
|
|
626
|
+
state->waiting_for_gvl_threshold_ns = NUM2UINT(waiting_for_gvl_threshold_ns);
|
|
504
627
|
|
|
505
628
|
if (RTEST(tracer_context_key)) {
|
|
506
629
|
ENFORCE_TYPE(tracer_context_key, T_SYMBOL);
|
|
@@ -510,19 +633,30 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
510
633
|
state->tracer_context_key = rb_to_id(tracer_context_key);
|
|
511
634
|
}
|
|
512
635
|
|
|
636
|
+
if (thread_begin_tracepoint == Qnil) {
|
|
637
|
+
thread_begin_tracepoint = rb_tracepoint_new(Qnil, RUBY_EVENT_THREAD_BEGIN, on_thread_begin_event, NULL);
|
|
638
|
+
rb_tracepoint_enable(thread_begin_tracepoint);
|
|
639
|
+
|
|
640
|
+
VALUE thread_list = rb_ary_new();
|
|
641
|
+
ddtrace_thread_list(thread_list);
|
|
642
|
+
long thread_count = RARRAY_LEN(thread_list);
|
|
643
|
+
for (long i = 0; i < thread_count; i++) {
|
|
644
|
+
get_or_create_context_for(RARRAY_AREF(thread_list, i));
|
|
645
|
+
}
|
|
646
|
+
RB_GC_GUARD(thread_list);
|
|
647
|
+
}
|
|
648
|
+
|
|
513
649
|
return Qtrue;
|
|
514
650
|
}
|
|
515
651
|
|
|
516
652
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
517
653
|
// It SHOULD NOT be used for other purposes.
|
|
518
|
-
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE
|
|
654
|
+
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE allow_exception) {
|
|
519
655
|
ENFORCE_BOOLEAN(allow_exception);
|
|
520
656
|
|
|
521
|
-
if (!is_thread_alive(profiler_overhead_stack_thread)) rb_raise(rb_eArgError, "Unexpected: profiler_overhead_stack_thread is not alive");
|
|
522
|
-
|
|
523
657
|
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
524
658
|
|
|
525
|
-
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
659
|
+
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE));
|
|
526
660
|
|
|
527
661
|
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
|
528
662
|
|
|
@@ -565,6 +699,53 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_
|
|
|
565
699
|
return Qtrue;
|
|
566
700
|
}
|
|
567
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(RAISE_ON_FAILURE);
|
|
705
|
+
long cpu_time_after_sampling = cpu_time_now_ns(current_thread_context);
|
|
706
|
+
|
|
707
|
+
long overhead_cpu_time_ns = update_time_since_previous_sample(
|
|
708
|
+
¤t_thread_context->cpu_time_at_previous_sample_ns,
|
|
709
|
+
cpu_time_after_sampling,
|
|
710
|
+
current_thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
711
|
+
IS_CPU_TIME);
|
|
712
|
+
|
|
713
|
+
long overhead_wall_time_ns = update_time_since_previous_sample(
|
|
714
|
+
¤t_thread_context->wall_time_at_previous_sample_ns,
|
|
715
|
+
wall_time_after_sampling,
|
|
716
|
+
INVALID_TIME,
|
|
717
|
+
IS_WALL_TIME);
|
|
718
|
+
|
|
719
|
+
ddog_prof_Label overhead_labels[] = {
|
|
720
|
+
{.key = DDOG_CHARSLICE_C("thread id"), .str = DDOG_CHARSLICE_C("0"), .num = 0},
|
|
721
|
+
{.key = DDOG_CHARSLICE_C("thread name"), .str = DDOG_CHARSLICE_C("Datadog::Profiling::Sampling"), .num = 0},
|
|
722
|
+
{.key = DDOG_CHARSLICE_C("state"), .str = DDOG_CHARSLICE_C("had cpu"), .num = 0},
|
|
723
|
+
{.key = DDOG_CHARSLICE_C("profiler overhead"), .num = 1},
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
int64_t end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, wall_time_after_sampling);
|
|
727
|
+
|
|
728
|
+
ddog_prof_Location overhead_location = {
|
|
729
|
+
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
|
730
|
+
.function = {
|
|
731
|
+
.name = DDOG_CHARSLICE_C("sampling"),
|
|
732
|
+
.filename = char_slice_from_ruby_string(state->overhead_filename),
|
|
733
|
+
},
|
|
734
|
+
.line = 0,
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
record_sample(
|
|
738
|
+
state->recorder_instance,
|
|
739
|
+
(ddog_prof_Slice_Location) {.ptr = &overhead_location, .len = 1},
|
|
740
|
+
(sample_values) {.cpu_time_ns = overhead_cpu_time_ns, .cpu_or_wall_samples = 1, .wall_time_ns = overhead_wall_time_ns},
|
|
741
|
+
(sample_labels) {
|
|
742
|
+
.labels = (ddog_prof_Slice_Label) {.ptr = overhead_labels, .len = sizeof(overhead_labels) / sizeof(overhead_labels[0])},
|
|
743
|
+
.state_label = NULL,
|
|
744
|
+
.end_timestamp_ns = end_timestamp_ns,
|
|
745
|
+
}
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
|
|
568
749
|
// This function gets called from the Collectors::CpuAndWallTimeWorker to trigger the actual sampling.
|
|
569
750
|
//
|
|
570
751
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
@@ -573,14 +754,12 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_
|
|
|
573
754
|
// Assumption 4: This function IS NOT called in a reentrant way.
|
|
574
755
|
// Assumption 5: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
575
756
|
//
|
|
576
|
-
|
|
577
|
-
// (belonging to ddtrace), so that the overhead is visible in the profile rather than blamed on user code.
|
|
578
|
-
void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns, VALUE profiler_overhead_stack_thread) {
|
|
757
|
+
void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns) {
|
|
579
758
|
thread_context_collector_state *state;
|
|
580
759
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
581
760
|
|
|
582
761
|
VALUE current_thread = rb_thread_current();
|
|
583
|
-
per_thread_context *current_thread_context = get_or_create_context_for(current_thread
|
|
762
|
+
per_thread_context *current_thread_context = get_or_create_context_for(current_thread);
|
|
584
763
|
long cpu_time_at_sample_start_for_current_thread = cpu_time_now_ns(current_thread_context);
|
|
585
764
|
|
|
586
765
|
VALUE threads = thread_list(state);
|
|
@@ -588,68 +767,59 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
|
588
767
|
const long thread_count = RARRAY_LEN(threads);
|
|
589
768
|
for (long i = 0; i < thread_count; i++) {
|
|
590
769
|
VALUE thread = RARRAY_AREF(threads, i);
|
|
591
|
-
per_thread_context *thread_context = get_or_create_context_for(thread
|
|
770
|
+
per_thread_context *thread_context = get_or_create_context_for(thread);
|
|
592
771
|
|
|
593
|
-
// We account for cpu-time for the current thread in a different way
|
|
594
|
-
// blaming the time the profiler took on whatever
|
|
595
|
-
|
|
772
|
+
// We account for cpu-time for the current thread in a different way: we use the cpu-time at sampling start,
|
|
773
|
+
// to avoid blaming the time the profiler took on whatever is currently running on the thread,
|
|
774
|
+
// and instead we report that time the profiler took as sampling overhead below.
|
|
775
|
+
long current_cpu_time_ns = (thread == current_thread) ? cpu_time_at_sample_start_for_current_thread : cpu_time_now_ns(thread_context);
|
|
596
776
|
|
|
597
777
|
update_metrics_and_sample(
|
|
598
778
|
state,
|
|
599
|
-
|
|
600
|
-
/* stack_from_thread: */ thread,
|
|
779
|
+
thread,
|
|
601
780
|
thread_context,
|
|
602
|
-
thread_context->sampling_buffer,
|
|
603
781
|
current_cpu_time_ns,
|
|
604
|
-
current_monotonic_wall_time_ns
|
|
605
|
-
|
|
782
|
+
current_monotonic_wall_time_ns,
|
|
783
|
+
false);
|
|
606
784
|
}
|
|
607
785
|
|
|
608
|
-
state->sample_count++;
|
|
609
|
-
|
|
610
|
-
// TODO: This seems somewhat overkill and inefficient to do often; right now we just do it every few samples
|
|
611
|
-
// but there's probably a better way to do this if we actually track when threads finish
|
|
612
|
-
if (state->sample_count % 100 == 0) remove_context_for_dead_threads(state);
|
|
786
|
+
state->stats.sample_count++;
|
|
613
787
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
);
|
|
788
|
+
// If the current thread is a profiler-internal thread, we don't use record_sampling_overhead()
|
|
789
|
+
// and accept the sampling overhead is attributed to the profiler-internal thread
|
|
790
|
+
// (both are under the same datadog group in the flamegraph).
|
|
791
|
+
// The reason we need to do this is profiler-internal threads are skipped during per-tick sampling
|
|
792
|
+
// so their timestamps are not updated. record_sampling_overhead() would see the stale previous-sample timestamps and
|
|
793
|
+
// attribute the entire sleep interval incorrectly as overhead instead of just the sampling work.
|
|
794
|
+
if (!current_thread_context->is_profiler_internal_thread) {
|
|
795
|
+
record_sampling_overhead(state, current_thread_context);
|
|
796
|
+
}
|
|
624
797
|
}
|
|
625
798
|
|
|
626
799
|
static void update_metrics_and_sample(
|
|
627
800
|
thread_context_collector_state *state,
|
|
628
801
|
VALUE thread_being_sampled,
|
|
629
|
-
VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
|
|
630
802
|
per_thread_context *thread_context,
|
|
631
|
-
sampling_buffer* sampling_buffer,
|
|
632
803
|
long current_cpu_time_ns,
|
|
633
|
-
long current_monotonic_wall_time_ns
|
|
804
|
+
long current_monotonic_wall_time_ns,
|
|
805
|
+
bool force_sample
|
|
634
806
|
) {
|
|
635
807
|
bool is_gvl_waiting_state =
|
|
636
|
-
handle_gvl_waiting(state, thread_being_sampled,
|
|
808
|
+
handle_gvl_waiting(state, thread_being_sampled, thread_context, current_cpu_time_ns);
|
|
809
|
+
|
|
810
|
+
if (skip_sample(state, thread_context, is_gvl_waiting_state, force_sample)) return;
|
|
637
811
|
|
|
638
812
|
// Don't assign/update cpu during "Waiting for GVL"
|
|
639
813
|
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_time_since_previous_sample(
|
|
640
814
|
&thread_context->cpu_time_at_previous_sample_ns,
|
|
641
815
|
current_cpu_time_ns,
|
|
642
816
|
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
643
|
-
|
|
817
|
+
IS_CPU_TIME
|
|
644
818
|
);
|
|
645
819
|
|
|
646
820
|
long wall_time_elapsed_ns = update_time_since_previous_sample(
|
|
647
821
|
&thread_context->wall_time_at_previous_sample_ns,
|
|
648
822
|
current_monotonic_wall_time_ns,
|
|
649
|
-
// We explicitly pass in `INVALID_TIME` as an argument for `gc_start_time_ns` here because we don't want wall-time
|
|
650
|
-
// accounting to change during GC.
|
|
651
|
-
// E.g. if 60 seconds pass in the real world, 60 seconds of wall-time are recorded, regardless of the thread doing
|
|
652
|
-
// GC or not.
|
|
653
823
|
INVALID_TIME,
|
|
654
824
|
IS_WALL_TIME
|
|
655
825
|
);
|
|
@@ -657,7 +827,7 @@ static void update_metrics_and_sample(
|
|
|
657
827
|
// A thread enters "Waiting for GVL", well, as the name implies, without the GVL.
|
|
658
828
|
//
|
|
659
829
|
// As a consequence, it's possible that a thread enters "Waiting for GVL" in parallel with the current thread working
|
|
660
|
-
// on sampling, and thus for the
|
|
830
|
+
// on sampling, and thus for the `current_monotonic_wall_time_ns` (which is recorded at the start of sampling)
|
|
661
831
|
// to be < the time at which we started Waiting for GVL.
|
|
662
832
|
//
|
|
663
833
|
// All together, this means that when `handle_gvl_waiting` creates an extra sample (see comments on that function for
|
|
@@ -672,9 +842,7 @@ static void update_metrics_and_sample(
|
|
|
672
842
|
trigger_sample_for_thread(
|
|
673
843
|
state,
|
|
674
844
|
thread_being_sampled,
|
|
675
|
-
stack_from_thread,
|
|
676
845
|
thread_context,
|
|
677
|
-
sampling_buffer,
|
|
678
846
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = wall_time_elapsed_ns},
|
|
679
847
|
current_monotonic_wall_time_ns,
|
|
680
848
|
NULL,
|
|
@@ -684,6 +852,37 @@ static void update_metrics_and_sample(
|
|
|
684
852
|
);
|
|
685
853
|
}
|
|
686
854
|
|
|
855
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample) {
|
|
856
|
+
if (!force_sample && thread_context->is_profiler_internal_thread) {
|
|
857
|
+
state->stats.profiler_thread_samples_skipped++;
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Racy read but harmless, can only cause an extra sample
|
|
862
|
+
uint64_t gvl_state_change_count = thread_context->gvl_state_change_count;
|
|
863
|
+
|
|
864
|
+
// Skip this per-tick sample entirely when the thread does not have the GVL and did not acquire
|
|
865
|
+
// it since the previous sample: its Ruby-level stack has not changed. The skipped wall-time will
|
|
866
|
+
// be picked up by either by an extra sample when the thread acquires the GVL, or by
|
|
867
|
+
// the on-serialize flush in the stack recorder (using was_skipped_at_last_sample).
|
|
868
|
+
// The check is gated by `!is_gvl_waiting_state` so the existing "Waiting for GVL" machinery
|
|
869
|
+
// in handle_gvl_waiting (situation 1 extra sample, situation 2 regular sample) keeps running.
|
|
870
|
+
// TODO: we could probably also skip while "Waiting for GVL"
|
|
871
|
+
if (!is_gvl_waiting_state &&
|
|
872
|
+
!force_sample &&
|
|
873
|
+
(gvl_state_change_count & GVL_SUSPENDED) &&
|
|
874
|
+
gvl_state_change_count == thread_context->gvl_state_change_count_at_previous_sample) {
|
|
875
|
+
state->stats.inactive_thread_samples_skipped++;
|
|
876
|
+
thread_context->was_skipped_at_last_sample = true;
|
|
877
|
+
return true; // Do NOT update wall_time_at_previous_sample_ns or cpu_time_at_previous_sample_ns
|
|
878
|
+
} else {
|
|
879
|
+
// We are going to sample, update the state accordingly:
|
|
880
|
+
thread_context->gvl_state_change_count_at_previous_sample = gvl_state_change_count;
|
|
881
|
+
thread_context->was_skipped_at_last_sample = false;
|
|
882
|
+
return false;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
687
886
|
// This function gets called when Ruby is about to start running the Garbage Collector on the current thread.
|
|
688
887
|
// It updates the per_thread_context of the current thread to include the current cpu/wall times, to be used to later
|
|
689
888
|
// create an event including the cpu/wall time spent in garbage collector work.
|
|
@@ -697,15 +896,13 @@ static void update_metrics_and_sample(
|
|
|
697
896
|
void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
698
897
|
thread_context_collector_state *state;
|
|
699
898
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return;
|
|
700
|
-
// This should never fail
|
|
899
|
+
// This should never fail when the above check passes
|
|
701
900
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
702
901
|
|
|
703
|
-
per_thread_context *thread_context =
|
|
902
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
704
903
|
|
|
705
|
-
//
|
|
706
|
-
//
|
|
707
|
-
// GC" is a rare enough case that we can just ignore it.
|
|
708
|
-
// We can always improve this later if we find that this happens often (and we have the counter to help us figure that out)!
|
|
904
|
+
// Context is created eagerly via on_thread_begin_event, so this should not normally be NULL.
|
|
905
|
+
// We can't get_or_create_context_for() here since we can't allocate (GC context).
|
|
709
906
|
if (thread_context == NULL) {
|
|
710
907
|
state->stats.gc_samples_missed_due_to_missing_context++;
|
|
711
908
|
return;
|
|
@@ -730,13 +927,12 @@ __attribute__((warn_unused_result))
|
|
|
730
927
|
bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
731
928
|
thread_context_collector_state *state;
|
|
732
929
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
|
733
|
-
// This should never fail
|
|
930
|
+
// This should never fail when the above check passes
|
|
734
931
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
735
932
|
|
|
736
|
-
per_thread_context *thread_context =
|
|
933
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
737
934
|
|
|
738
|
-
//
|
|
739
|
-
// how often this happens -- see on_gc_start.
|
|
935
|
+
// Context is created eagerly, so this should not normally be NULL (see on_gc_start).
|
|
740
936
|
if (thread_context == NULL) return false;
|
|
741
937
|
|
|
742
938
|
long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
|
|
@@ -810,7 +1006,7 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
810
1006
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
811
1007
|
|
|
812
1008
|
if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
|
|
813
|
-
|
|
1009
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected call to sample_after_gc without valid GC information available");
|
|
814
1010
|
}
|
|
815
1011
|
|
|
816
1012
|
int max_labels_needed_for_gc = 7; // Magic number gets validated inside gc_profiling_set_metadata
|
|
@@ -820,11 +1016,7 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
820
1016
|
ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = label_pos};
|
|
821
1017
|
|
|
822
1018
|
// The end_timestamp_ns is treated specially by libdatadog and that's why it's not added as a ddog_prof_Label
|
|
823
|
-
int64_t end_timestamp_ns =
|
|
824
|
-
|
|
825
|
-
if (state->timeline_enabled) {
|
|
826
|
-
end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, state->gc_tracking.wall_time_at_previous_gc_ns);
|
|
827
|
-
}
|
|
1019
|
+
int64_t end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, state->gc_tracking.wall_time_at_previous_gc_ns);
|
|
828
1020
|
|
|
829
1021
|
record_placeholder_stack(
|
|
830
1022
|
state->recorder_instance,
|
|
@@ -857,10 +1049,8 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
857
1049
|
|
|
858
1050
|
static void trigger_sample_for_thread(
|
|
859
1051
|
thread_context_collector_state *state,
|
|
860
|
-
VALUE
|
|
861
|
-
VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
|
|
1052
|
+
VALUE thread_being_sampled,
|
|
862
1053
|
per_thread_context *thread_context,
|
|
863
|
-
sampling_buffer* sampling_buffer,
|
|
864
1054
|
sample_values values,
|
|
865
1055
|
long current_monotonic_wall_time_ns,
|
|
866
1056
|
// These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
|
|
@@ -874,7 +1064,6 @@ static void trigger_sample_for_thread(
|
|
|
874
1064
|
int max_label_count =
|
|
875
1065
|
1 + // thread id
|
|
876
1066
|
1 + // thread name
|
|
877
|
-
1 + // profiler overhead
|
|
878
1067
|
2 + // ruby vm type and allocation class
|
|
879
1068
|
1 + // state (only set for cpu/wall-time samples)
|
|
880
1069
|
2; // local root span id and span id
|
|
@@ -886,13 +1075,13 @@ static void trigger_sample_for_thread(
|
|
|
886
1075
|
.str = thread_context->thread_id_char_slice
|
|
887
1076
|
};
|
|
888
1077
|
|
|
889
|
-
VALUE thread_name = thread_name_for(
|
|
1078
|
+
VALUE thread_name = thread_name_for(thread_being_sampled);
|
|
890
1079
|
if (thread_name != Qnil) {
|
|
891
1080
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
892
1081
|
.key = DDOG_CHARSLICE_C("thread name"),
|
|
893
1082
|
.str = char_slice_from_ruby_string(thread_name)
|
|
894
1083
|
};
|
|
895
|
-
} else if (
|
|
1084
|
+
} else if (thread_being_sampled == state->main_thread) { // Threads are often not named, but we can have a nice fallback for this special thread
|
|
896
1085
|
ddog_CharSlice main_thread_name = DDOG_CHARSLICE_C("main");
|
|
897
1086
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
898
1087
|
.key = DDOG_CHARSLICE_C("thread name"),
|
|
@@ -908,11 +1097,11 @@ static void trigger_sample_for_thread(
|
|
|
908
1097
|
}
|
|
909
1098
|
|
|
910
1099
|
trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
|
|
911
|
-
trace_identifiers_for(state,
|
|
1100
|
+
trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result, is_safe_to_allocate_objects);
|
|
912
1101
|
|
|
913
1102
|
if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
|
|
914
1103
|
// If we couldn't get something with ddtrace, let's see if we can get some trace identifiers from opentelemetry directly
|
|
915
|
-
otel_without_ddtrace_trace_identifiers_for(state,
|
|
1104
|
+
otel_without_ddtrace_trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result, is_safe_to_allocate_objects);
|
|
916
1105
|
}
|
|
917
1106
|
|
|
918
1107
|
if (trace_identifiers_result.valid) {
|
|
@@ -937,13 +1126,6 @@ static void trigger_sample_for_thread(
|
|
|
937
1126
|
}
|
|
938
1127
|
}
|
|
939
1128
|
|
|
940
|
-
if (thread != stack_from_thread) {
|
|
941
|
-
labels[label_pos++] = (ddog_prof_Label) {
|
|
942
|
-
.key = DDOG_CHARSLICE_C("profiler overhead"),
|
|
943
|
-
.num = 1
|
|
944
|
-
};
|
|
945
|
-
}
|
|
946
|
-
|
|
947
1129
|
if (ruby_vm_type != NULL) {
|
|
948
1130
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
949
1131
|
.key = DDOG_CHARSLICE_C("ruby vm type"),
|
|
@@ -977,20 +1159,21 @@ static void trigger_sample_for_thread(
|
|
|
977
1159
|
// @ivoanjo: I wonder if C compilers are smart enough to statically prove this check never triggers unless someone
|
|
978
1160
|
// changes the code erroneously and remove it entirely?
|
|
979
1161
|
if (label_pos > max_label_count) {
|
|
980
|
-
|
|
1162
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected label_pos (%d) > max_label_count (%d)", label_pos, max_label_count);
|
|
981
1163
|
}
|
|
982
1164
|
|
|
983
1165
|
ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = label_pos};
|
|
984
1166
|
|
|
985
1167
|
// The end_timestamp_ns is treated specially by libdatadog and that's why it's not added as a ddog_prof_Label
|
|
986
1168
|
int64_t end_timestamp_ns = 0;
|
|
987
|
-
if (
|
|
1169
|
+
if (current_monotonic_wall_time_ns != INVALID_TIME) {
|
|
988
1170
|
end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, current_monotonic_wall_time_ns);
|
|
989
1171
|
}
|
|
990
1172
|
|
|
991
1173
|
sample_thread(
|
|
992
|
-
|
|
993
|
-
sampling_buffer,
|
|
1174
|
+
thread_being_sampled,
|
|
1175
|
+
&thread_context->sampling_buffer,
|
|
1176
|
+
state->locations,
|
|
994
1177
|
state->recorder_instance,
|
|
995
1178
|
values,
|
|
996
1179
|
(sample_labels) {
|
|
@@ -998,7 +1181,9 @@ static void trigger_sample_for_thread(
|
|
|
998
1181
|
.state_label = state_label,
|
|
999
1182
|
.end_timestamp_ns = end_timestamp_ns,
|
|
1000
1183
|
.is_gvl_waiting_state = is_gvl_waiting_state,
|
|
1001
|
-
}
|
|
1184
|
+
},
|
|
1185
|
+
state->native_filenames_enabled,
|
|
1186
|
+
state->native_filenames_cache
|
|
1002
1187
|
);
|
|
1003
1188
|
}
|
|
1004
1189
|
|
|
@@ -1016,32 +1201,38 @@ static VALUE _native_thread_list(DDTRACE_UNUSED VALUE _self) {
|
|
|
1016
1201
|
return result;
|
|
1017
1202
|
}
|
|
1018
1203
|
|
|
1019
|
-
static
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
|
|
1024
|
-
thread_context = (per_thread_context*) value_context;
|
|
1025
|
-
} else {
|
|
1026
|
-
thread_context = ruby_xcalloc(1, sizeof(per_thread_context));
|
|
1027
|
-
initialize_context(thread, thread_context, state);
|
|
1028
|
-
st_insert(state->hash_map_per_thread_context, (st_data_t) thread, (st_data_t) thread_context);
|
|
1204
|
+
static void check_frozen_thread(VALUE thread) {
|
|
1205
|
+
if (RB_OBJ_FROZEN(thread)) {
|
|
1206
|
+
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);
|
|
1029
1207
|
}
|
|
1030
|
-
|
|
1031
|
-
return thread_context;
|
|
1032
1208
|
}
|
|
1033
1209
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1210
|
+
// See the docs on struct per_thread_context.
|
|
1211
|
+
// This allocates a Ruby object and therefore needs the GVL and is not safe to call from RUBY_INTERNAL_EVENT_* hooks.
|
|
1212
|
+
static per_thread_context *get_or_create_context_for(VALUE thread) {
|
|
1213
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1214
|
+
if (thread_context != NULL) return thread_context;
|
|
1037
1215
|
|
|
1038
|
-
|
|
1039
|
-
thread_context = (per_thread_context*) value_context;
|
|
1040
|
-
}
|
|
1216
|
+
check_frozen_thread(thread);
|
|
1041
1217
|
|
|
1218
|
+
thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
|
|
1219
|
+
initialize_context(thread, thread_context);
|
|
1220
|
+
|
|
1221
|
+
VALUE wrapper = TypedData_Wrap_Struct(rb_cObject, &per_thread_context_typed_data, thread_context);
|
|
1222
|
+
rb_ivar_set(thread, dd_per_thread_context_id, wrapper);
|
|
1223
|
+
|
|
1224
|
+
set_per_thread_context(thread, thread_context);
|
|
1042
1225
|
return thread_context;
|
|
1043
1226
|
}
|
|
1044
1227
|
|
|
1228
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
|
|
1229
|
+
if (!ddtrace_rb_ractor_main_p()) return;
|
|
1230
|
+
|
|
1231
|
+
VALUE thread = rb_tracearg_self(rb_tracearg_from_tracepoint(tracepoint_data));
|
|
1232
|
+
ENFORCE_THREAD(thread);
|
|
1233
|
+
get_or_create_context_for(thread);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1045
1236
|
#define LOGGING_GEM_PATH "/lib/logging/diagnostic_context.rb"
|
|
1046
1237
|
|
|
1047
1238
|
// The `logging` gem monkey patches thread creation, which makes the `invoke_location_for` useless, since every thread
|
|
@@ -1063,8 +1254,12 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
|
|
|
1063
1254
|
return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
|
|
1064
1255
|
}
|
|
1065
1256
|
|
|
1066
|
-
static void initialize_context(VALUE thread, per_thread_context *thread_context
|
|
1067
|
-
|
|
1257
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context) {
|
|
1258
|
+
// We always create per_thread_context's with latest_max_frames because
|
|
1259
|
+
// 1) we don't always have access to the ThreadContext object (e.g. in a TracePoint).
|
|
1260
|
+
// 2) the per_thread_context's are global and so might not match the ThreadContext#max_frames anyway.
|
|
1261
|
+
// This is fine because sample_thread() handles when they don't match and resizes as needed.
|
|
1262
|
+
sampling_buffer_initialize(&thread_context->sampling_buffer, latest_max_frames);
|
|
1068
1263
|
|
|
1069
1264
|
snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
|
|
1070
1265
|
thread_context->thread_id_char_slice = (ddog_CharSlice) {.ptr = thread_context->thread_id, .len = strlen(thread_context->thread_id)};
|
|
@@ -1083,7 +1278,7 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
|
1083
1278
|
} else {
|
|
1084
1279
|
snprintf(thread_context->thread_invoke_location, THREAD_INVOKE_LOCATION_LIMIT_CHARS, "%s", "(Unnamed thread)");
|
|
1085
1280
|
}
|
|
1086
|
-
} else if (thread !=
|
|
1281
|
+
} else if (thread != rb_thread_main()) {
|
|
1087
1282
|
// If the first function of a thread is native code, there won't be an invoke location, so we use this fallback.
|
|
1088
1283
|
// 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
|
|
1089
1284
|
// symbol attached to it.
|
|
@@ -1097,32 +1292,15 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
|
1097
1292
|
|
|
1098
1293
|
thread_context->thread_cpu_time_id = thread_cpu_time_id_for(thread);
|
|
1099
1294
|
|
|
1100
|
-
|
|
1101
|
-
thread_context->cpu_time_at_previous_sample_ns =
|
|
1102
|
-
thread_context->wall_time_at_previous_sample_ns = INVALID_TIME;
|
|
1295
|
+
thread_context->wall_time_at_previous_sample_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
1296
|
+
thread_context->cpu_time_at_previous_sample_ns = cpu_time_now_ns(thread_context);
|
|
1103
1297
|
|
|
1104
1298
|
// These will only be used during a GC operation
|
|
1105
1299
|
thread_context->gc_tracking.cpu_time_at_start_ns = INVALID_TIME;
|
|
1106
1300
|
thread_context->gc_tracking.wall_time_at_start_ns = INVALID_TIME;
|
|
1107
1301
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
// kind of synchronization (e.g. by threads without the GVL).
|
|
1111
|
-
//
|
|
1112
|
-
// We set this marker here for two purposes:
|
|
1113
|
-
// * To make sure there's no stale data from a previous execution of the profiler.
|
|
1114
|
-
// * To mark threads that are actually being profiled
|
|
1115
|
-
//
|
|
1116
|
-
// (Setting this is potentially a race, but what we want is to avoid _stale_ data, so
|
|
1117
|
-
// if this gets set concurrently with context initialization, then such a value will belong
|
|
1118
|
-
// to the current profiler instance, so that's OK)
|
|
1119
|
-
gvl_profiling_state_thread_object_set(thread, GVL_WAITING_ENABLED_EMPTY);
|
|
1120
|
-
#endif
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
static void free_context(per_thread_context* thread_context) {
|
|
1124
|
-
sampling_buffer_free(thread_context->sampling_buffer);
|
|
1125
|
-
ruby_xfree(thread_context);
|
|
1302
|
+
thread_context->gvl_waiting_at = 0;
|
|
1303
|
+
thread_context->gvl_state_change_count = 0;
|
|
1126
1304
|
}
|
|
1127
1305
|
|
|
1128
1306
|
static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
@@ -1132,15 +1310,15 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
|
1132
1310
|
VALUE result = rb_str_new2(" (native state)");
|
|
1133
1311
|
|
|
1134
1312
|
// Update this when modifying state struct
|
|
1135
|
-
rb_str_concat(result, rb_sprintf(" max_frames=%d", state->
|
|
1136
|
-
rb_str_concat(result, rb_sprintf(" hash_map_per_thread_context=%"PRIsVALUE, per_thread_context_st_table_as_ruby_hash(state)));
|
|
1313
|
+
rb_str_concat(result, rb_sprintf(" max_frames=%d", state->locations.len));
|
|
1137
1314
|
rb_str_concat(result, rb_sprintf(" recorder_instance=%"PRIsVALUE, state->recorder_instance));
|
|
1138
1315
|
VALUE tracer_context_key = state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY ? Qnil : ID2SYM(state->tracer_context_key);
|
|
1139
1316
|
rb_str_concat(result, rb_sprintf(" tracer_context_key=%+"PRIsVALUE, tracer_context_key));
|
|
1140
|
-
rb_str_concat(result, rb_sprintf("
|
|
1141
|
-
rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_as_ruby_hash(state)));
|
|
1317
|
+
rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_to_ruby_hash(state, rb_hash_new())));
|
|
1142
1318
|
rb_str_concat(result, rb_sprintf(" endpoint_collection_enabled=%"PRIsVALUE, state->endpoint_collection_enabled ? Qtrue : Qfalse));
|
|
1143
|
-
rb_str_concat(result, rb_sprintf("
|
|
1319
|
+
rb_str_concat(result, rb_sprintf(" native_filenames_enabled=%"PRIsVALUE, state->native_filenames_enabled ? Qtrue : Qfalse));
|
|
1320
|
+
// Note: `st_table_size()` is available from Ruby 3.2+ but not before
|
|
1321
|
+
rb_str_concat(result, rb_sprintf(" native_filenames_cache_size=%zu", state->native_filenames_cache->num_entries));
|
|
1144
1322
|
rb_str_concat(result, rb_sprintf(" otel_context_enabled=%d", state->otel_context_enabled));
|
|
1145
1323
|
rb_str_concat(result, rb_sprintf(
|
|
1146
1324
|
" time_converter_state={.system_epoch_ns_reference=%ld, .delta_to_epoch_ns=%ld}",
|
|
@@ -1150,53 +1328,51 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
|
1150
1328
|
rb_str_concat(result, rb_sprintf(" main_thread=%"PRIsVALUE, state->main_thread));
|
|
1151
1329
|
rb_str_concat(result, rb_sprintf(" gc_tracking=%"PRIsVALUE, gc_tracking_as_ruby_hash(state)));
|
|
1152
1330
|
rb_str_concat(result, rb_sprintf(" otel_current_span_key=%"PRIsVALUE, state->otel_current_span_key));
|
|
1153
|
-
rb_str_concat(result, rb_sprintf("
|
|
1331
|
+
rb_str_concat(result, rb_sprintf(" waiting_for_gvl_threshold_ns=%u", state->waiting_for_gvl_threshold_ns));
|
|
1154
1332
|
|
|
1155
1333
|
return result;
|
|
1156
1334
|
}
|
|
1157
1335
|
|
|
1158
|
-
static VALUE
|
|
1159
|
-
VALUE result = rb_hash_new();
|
|
1160
|
-
st_foreach(state->hash_map_per_thread_context, per_thread_context_as_ruby_hash, result);
|
|
1161
|
-
return result;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash) {
|
|
1165
|
-
VALUE thread = (VALUE) key_thread;
|
|
1166
|
-
per_thread_context *thread_context = (per_thread_context*) value_context;
|
|
1167
|
-
VALUE result = (VALUE) result_hash;
|
|
1336
|
+
static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context) {
|
|
1168
1337
|
VALUE context_as_hash = rb_hash_new();
|
|
1169
|
-
rb_hash_aset(result, thread, context_as_hash);
|
|
1170
1338
|
|
|
1171
1339
|
VALUE arguments[] = {
|
|
1172
1340
|
ID2SYM(rb_intern("thread_id")), /* => */ rb_str_new2(thread_context->thread_id),
|
|
1173
1341
|
ID2SYM(rb_intern("thread_invoke_location")), /* => */ rb_str_new2(thread_context->thread_invoke_location),
|
|
1174
1342
|
ID2SYM(rb_intern("thread_cpu_time_id_valid?")), /* => */ thread_context->thread_cpu_time_id.valid ? Qtrue : Qfalse,
|
|
1175
|
-
|
|
1343
|
+
#ifdef __APPLE__
|
|
1344
|
+
ID2SYM(rb_intern("thread_cpu_time_id")), /* => */ ULL2NUM(thread_context->thread_cpu_time_id.clock_id),
|
|
1345
|
+
#else
|
|
1346
|
+
ID2SYM(rb_intern("thread_cpu_time_id")), /* => */ CLOCKID2NUM(thread_context->thread_cpu_time_id.clock_id),
|
|
1347
|
+
#endif
|
|
1176
1348
|
ID2SYM(rb_intern("cpu_time_at_previous_sample_ns")), /* => */ LONG2NUM(thread_context->cpu_time_at_previous_sample_ns),
|
|
1177
1349
|
ID2SYM(rb_intern("wall_time_at_previous_sample_ns")), /* => */ LONG2NUM(thread_context->wall_time_at_previous_sample_ns),
|
|
1178
1350
|
|
|
1179
1351
|
ID2SYM(rb_intern("gc_tracking.cpu_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.cpu_time_at_start_ns),
|
|
1180
1352
|
ID2SYM(rb_intern("gc_tracking.wall_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.wall_time_at_start_ns),
|
|
1181
1353
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1354
|
+
ID2SYM(rb_intern("gvl_waiting_at")), /* => */ LONG2NUM(thread_context->gvl_waiting_at),
|
|
1355
|
+
ID2SYM(rb_intern("gvl_state_change_count")), /* => */ ULL2NUM(thread_context->gvl_state_change_count),
|
|
1356
|
+
ID2SYM(rb_intern("gvl_state_change_count_at_previous_sample")), /* => */ ULL2NUM(thread_context->gvl_state_change_count_at_previous_sample),
|
|
1357
|
+
ID2SYM(rb_intern("was_skipped_at_last_sample")), /* => */ thread_context->was_skipped_at_last_sample ? Qtrue : Qfalse,
|
|
1358
|
+
ID2SYM(rb_intern("is_profiler_internal_thread")), /* => */ thread_context->is_profiler_internal_thread ? Qtrue : Qfalse,
|
|
1185
1359
|
};
|
|
1186
1360
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(context_as_hash, arguments[i], arguments[i+1]);
|
|
1187
1361
|
|
|
1188
|
-
return
|
|
1362
|
+
return context_as_hash;
|
|
1189
1363
|
}
|
|
1190
1364
|
|
|
1191
|
-
static VALUE
|
|
1365
|
+
static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash) {
|
|
1192
1366
|
// Update this when modifying state struct (stats inner struct)
|
|
1193
|
-
VALUE stats_as_hash = rb_hash_new();
|
|
1194
1367
|
VALUE arguments[] = {
|
|
1368
|
+
ID2SYM(rb_intern("sample_count")), /* => */ UINT2NUM(state->stats.sample_count),
|
|
1195
1369
|
ID2SYM(rb_intern("gc_samples")), /* => */ UINT2NUM(state->stats.gc_samples),
|
|
1196
1370
|
ID2SYM(rb_intern("gc_samples_missed_due_to_missing_context")), /* => */ UINT2NUM(state->stats.gc_samples_missed_due_to_missing_context),
|
|
1371
|
+
ID2SYM(rb_intern("inactive_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.inactive_thread_samples_skipped),
|
|
1372
|
+
ID2SYM(rb_intern("profiler_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.profiler_thread_samples_skipped),
|
|
1197
1373
|
};
|
|
1198
|
-
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(
|
|
1199
|
-
return
|
|
1374
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
|
|
1375
|
+
return hash;
|
|
1200
1376
|
}
|
|
1201
1377
|
|
|
1202
1378
|
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state) {
|
|
@@ -1212,36 +1388,35 @@ static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state) {
|
|
|
1212
1388
|
return result;
|
|
1213
1389
|
}
|
|
1214
1390
|
|
|
1215
|
-
static void remove_context_for_dead_threads(thread_context_collector_state *state) {
|
|
1216
|
-
st_foreach(state->hash_map_per_thread_context, remove_if_dead_thread, 0 /* unused */);
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, DDTRACE_UNUSED st_data_t _argument) {
|
|
1220
|
-
VALUE thread = (VALUE) key_thread;
|
|
1221
|
-
per_thread_context* thread_context = (per_thread_context*) value_context;
|
|
1222
|
-
|
|
1223
|
-
if (is_thread_alive(thread)) return ST_CONTINUE;
|
|
1224
|
-
|
|
1225
|
-
free_context(thread_context);
|
|
1226
|
-
return ST_DELETE;
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
1391
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1230
1392
|
// It SHOULD NOT be used for other purposes.
|
|
1231
1393
|
//
|
|
1232
|
-
// Returns the whole contents of the per_thread_context structs being tracked.
|
|
1394
|
+
// Returns the whole contents of the per_thread_context structs being tracked, by iterating all live threads.
|
|
1233
1395
|
static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1234
1396
|
thread_context_collector_state *state;
|
|
1235
1397
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1236
1398
|
|
|
1237
|
-
|
|
1399
|
+
VALUE result = rb_hash_new();
|
|
1400
|
+
VALUE threads = thread_list(state);
|
|
1401
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
1402
|
+
for (long i = 0; i < thread_count; i++) {
|
|
1403
|
+
VALUE thread = RARRAY_AREF(threads, i);
|
|
1404
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1405
|
+
if (thread_context != NULL) {
|
|
1406
|
+
rb_hash_aset(result, thread, per_thread_context_to_ruby_hash(thread_context));
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
return result;
|
|
1238
1410
|
}
|
|
1239
1411
|
|
|
1412
|
+
// gc_start_time_ns should only be passed if IS_CPU_TIME
|
|
1240
1413
|
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, long gc_start_time_ns, bool is_wall_time) {
|
|
1241
1414
|
// If we didn't have a time for the previous sample, we use the current one
|
|
1242
1415
|
if (*time_at_previous_sample_ns == INVALID_TIME) *time_at_previous_sample_ns = current_time_ns;
|
|
1243
1416
|
|
|
1244
|
-
|
|
1417
|
+
// We don't want wall-time accounting to change during GC.
|
|
1418
|
+
// E.g. if 60 seconds pass in the real world, 60 seconds of wall-time are recorded, regardless of the thread doing GC or not.
|
|
1419
|
+
bool is_thread_doing_gc = !is_wall_time && gc_start_time_ns != INVALID_TIME;
|
|
1245
1420
|
long elapsed_time_ns = -1;
|
|
1246
1421
|
|
|
1247
1422
|
if (is_thread_doing_gc) {
|
|
@@ -1269,7 +1444,7 @@ static long update_time_since_previous_sample(long *time_at_previous_sample_ns,
|
|
|
1269
1444
|
elapsed_time_ns = 0;
|
|
1270
1445
|
} else {
|
|
1271
1446
|
// We don't expect non-wall time to go backwards, so let's flag this as a bug
|
|
1272
|
-
|
|
1447
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected negative elapsed_time_ns between samples");
|
|
1273
1448
|
}
|
|
1274
1449
|
}
|
|
1275
1450
|
|
|
@@ -1312,13 +1487,23 @@ VALUE enforce_thread_context_collector_instance(VALUE object) {
|
|
|
1312
1487
|
return object;
|
|
1313
1488
|
}
|
|
1314
1489
|
|
|
1490
|
+
// Finalize any pending heap allocation recordings.
|
|
1491
|
+
// On Ruby 4+, heap allocations are recorded in two phases: during on_newobj_event we capture
|
|
1492
|
+
// the object reference, then later we safely call rb_obj_id() to get the object ID.
|
|
1493
|
+
void thread_context_collector_after_allocation(VALUE self_instance) {
|
|
1494
|
+
thread_context_collector_state *state;
|
|
1495
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1496
|
+
|
|
1497
|
+
recorder_after_sample(state->recorder_instance);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1315
1500
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1316
1501
|
// It SHOULD NOT be used for other purposes.
|
|
1317
1502
|
static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1318
1503
|
thread_context_collector_state *state;
|
|
1319
1504
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1320
1505
|
|
|
1321
|
-
return
|
|
1506
|
+
return stats_to_ruby_hash(state, rb_hash_new());
|
|
1322
1507
|
}
|
|
1323
1508
|
|
|
1324
1509
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
@@ -1413,17 +1598,18 @@ static bool should_collect_resource(VALUE root_span) {
|
|
|
1413
1598
|
//
|
|
1414
1599
|
// Assumption: This method gets called BEFORE restarting profiling -- e.g. there are no components attempting to
|
|
1415
1600
|
// trigger samples at the same time.
|
|
1601
|
+
//
|
|
1602
|
+
// Note that tests call this method directly in the same process without forking,
|
|
1603
|
+
// and in such a case non-current Threads keep running.
|
|
1416
1604
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
1417
1605
|
thread_context_collector_state *state;
|
|
1418
1606
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1419
1607
|
|
|
1420
|
-
// Release all context memory before clearing the existing context
|
|
1421
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
|
1422
|
-
|
|
1423
|
-
st_clear(state->hash_map_per_thread_context);
|
|
1424
|
-
|
|
1425
1608
|
state->stats = (struct stats) {}; // Resets all stats back to zero
|
|
1426
1609
|
|
|
1610
|
+
// Clear any leftover state from parent process in the current thread; all other threads are assumed dead
|
|
1611
|
+
_native_clear_per_thread_context_for(Qnil, rb_thread_current());
|
|
1612
|
+
|
|
1427
1613
|
rb_funcall(state->recorder_instance, rb_intern("reset_after_fork"), 0);
|
|
1428
1614
|
|
|
1429
1615
|
return Qtrue;
|
|
@@ -1436,12 +1622,32 @@ static VALUE thread_list(thread_context_collector_state *state) {
|
|
|
1436
1622
|
return result;
|
|
1437
1623
|
}
|
|
1438
1624
|
|
|
1439
|
-
|
|
1625
|
+
// Inside a signal handler, we don't want to do the whole work of recording a sample, but we only record the stack of
|
|
1626
|
+
// the current thread.
|
|
1627
|
+
//
|
|
1628
|
+
// Assumptions for this function are same as for `thread_context_collector_sample` except that this function is
|
|
1629
|
+
// expected to be called from a signal handler and to be async-signal-safe.
|
|
1630
|
+
//
|
|
1631
|
+
// Also, no allocation (Ruby or malloc) can happen.
|
|
1632
|
+
bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
1633
|
+
VALUE current_thread = rb_thread_current();
|
|
1634
|
+
per_thread_context *thread_context = get_per_thread_context(current_thread);
|
|
1635
|
+
if (thread_context == NULL) return false;
|
|
1636
|
+
|
|
1637
|
+
return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should never allocate in the
|
|
1641
|
+
// Ruby heap.
|
|
1642
|
+
//
|
|
1643
|
+
// Returns true if the after_allocation needs to be called (to do work that can't be done from inside the
|
|
1644
|
+
// tracepoint, such as allocate new objects), and false if it doesn't
|
|
1645
|
+
//
|
|
1646
|
+
// The callers must ensure thread_context is non-NULL.
|
|
1647
|
+
bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object) {
|
|
1440
1648
|
thread_context_collector_state *state;
|
|
1441
1649
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1442
1650
|
|
|
1443
|
-
VALUE current_thread = rb_thread_current();
|
|
1444
|
-
|
|
1445
1651
|
enum ruby_value_type type = rb_type(new_object);
|
|
1446
1652
|
|
|
1447
1653
|
// Tag samples with the VM internal types
|
|
@@ -1506,38 +1712,48 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
|
1506
1712
|
class_name = ruby_vm_type; // For other weird internal things we just use the VM type
|
|
1507
1713
|
}
|
|
1508
1714
|
|
|
1509
|
-
track_object(state->recorder_instance, new_object, sample_weight, class_name);
|
|
1715
|
+
bool needs_after_allocation = track_object(state->recorder_instance, new_object, sample_weight, class_name);
|
|
1510
1716
|
|
|
1511
|
-
|
|
1717
|
+
VALUE current_thread = rb_thread_current();
|
|
1512
1718
|
|
|
1513
1719
|
trigger_sample_for_thread(
|
|
1514
1720
|
state,
|
|
1515
|
-
|
|
1516
|
-
/* stack_from_thread: */ current_thread,
|
|
1721
|
+
current_thread,
|
|
1517
1722
|
thread_context,
|
|
1518
|
-
thread_context->sampling_buffer,
|
|
1519
1723
|
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
|
|
1520
1724
|
INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
|
|
1521
1725
|
&ruby_vm_type,
|
|
1522
|
-
|
|
1726
|
+
&class_name,
|
|
1523
1727
|
/* is_gvl_waiting_state: */ false,
|
|
1524
1728
|
/* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
|
|
1525
1729
|
);
|
|
1730
|
+
|
|
1731
|
+
return needs_after_allocation;
|
|
1526
1732
|
}
|
|
1527
1733
|
|
|
1528
1734
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1529
1735
|
// It SHOULD NOT be used for other purposes.
|
|
1530
1736
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object) {
|
|
1737
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
1738
|
+
if (thread_context == NULL) {
|
|
1739
|
+
rb_raise(rb_eRuntimeError, "Missing per_thread_context for current thread in _native_sample_allocation");
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1531
1742
|
debug_enter_unsafe_context();
|
|
1532
1743
|
|
|
1533
|
-
thread_context_collector_sample_allocation(collector_instance, NUM2UINT(sample_weight), new_object);
|
|
1744
|
+
bool needs_after_allocation = thread_context_collector_sample_allocation(collector_instance, thread_context, NUM2UINT(sample_weight), new_object);
|
|
1534
1745
|
|
|
1535
1746
|
debug_leave_unsafe_context();
|
|
1536
1747
|
|
|
1537
|
-
|
|
1748
|
+
// We could instead choose to automatically trigger the after allocation here; yet, it seems kinda nice to keep it manual for
|
|
1749
|
+
// the tests so we can pull on each lever separately and observe "the sausage being made" in steps
|
|
1750
|
+
return needs_after_allocation ? Qtrue : Qfalse;
|
|
1538
1751
|
}
|
|
1539
1752
|
|
|
1540
|
-
static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
|
|
1753
|
+
static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
|
|
1754
|
+
rb_thread_sleep(INT_MAX);
|
|
1755
|
+
return Qnil;
|
|
1756
|
+
}
|
|
1541
1757
|
|
|
1542
1758
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1543
1759
|
// It SHOULD NOT be used for other purposes.
|
|
@@ -1752,7 +1968,7 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
|
1752
1968
|
VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
|
|
1753
1969
|
if (otel_current_span_key == Qnil) return;
|
|
1754
1970
|
|
|
1755
|
-
|
|
1971
|
+
long active_context_index = RARRAY_LEN(context_storage) - 1;
|
|
1756
1972
|
if (active_context_index < 0) return;
|
|
1757
1973
|
|
|
1758
1974
|
otel_span active_span = otel_span_from(rb_ary_entry(context_storage, active_context_index), otel_current_span_key);
|
|
@@ -1761,7 +1977,7 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
|
1761
1977
|
otel_span local_root_span = active_span;
|
|
1762
1978
|
|
|
1763
1979
|
// Now find the oldest span starting from the active span that still has the same trace id as the active span
|
|
1764
|
-
for (
|
|
1980
|
+
for (long i = active_context_index - 1; i >= 0; i--) {
|
|
1765
1981
|
otel_span checking_span = otel_span_from(rb_ary_entry(context_storage, i), otel_current_span_key);
|
|
1766
1982
|
if (checking_span.span == Qnil) return;
|
|
1767
1983
|
|
|
@@ -1830,57 +2046,153 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1830
2046
|
((uint64_t)span_bytes[7]);
|
|
1831
2047
|
}
|
|
1832
2048
|
|
|
2049
|
+
void thread_context_collector_stats(VALUE self_instance, VALUE stats_hash) {
|
|
2050
|
+
thread_context_collector_state *state;
|
|
2051
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2052
|
+
stats_to_ruby_hash(state, stats_hash);
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
2056
|
+
thread_context_collector_state *state;
|
|
2057
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2058
|
+
state->stats = (struct stats) {};
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
static void mark_thread_as_profiler_internal(per_thread_context *ctx) {
|
|
2062
|
+
ctx->is_profiler_internal_thread = true;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
void thread_context_collector_profiler_internal_thread_started(void) {
|
|
2066
|
+
per_thread_context *ctx = get_or_create_context_for(rb_thread_current());
|
|
2067
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2071
|
+
per_thread_context *ctx = get_or_create_context_for(thread);
|
|
2072
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2073
|
+
return Qnil;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
// Called via rb_ensure when a profiler-internal thread (worker or idle helper) is about to exit.
|
|
2077
|
+
// Records a final sample so the thread's accumulated cpu/wall time since the last on_serialize
|
|
2078
|
+
// flush is not lost. on_serialize (below) also flushes profiler-internal threads during periodic
|
|
2079
|
+
// serialization, but it can't help at shutdown: by the time the final serialize runs, these
|
|
2080
|
+
// threads are already dead and absent from thread_list.
|
|
2081
|
+
void thread_context_collector_profiler_internal_thread_done(VALUE self_instance) {
|
|
2082
|
+
thread_context_collector_state *state;
|
|
2083
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2084
|
+
|
|
2085
|
+
VALUE current_thread = rb_thread_current();
|
|
2086
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
2087
|
+
if (!thread_context->is_profiler_internal_thread) {
|
|
2088
|
+
rb_raise(rb_eRuntimeError, "current thread %"PRIsVALUE" is not profiler-internal thread", current_thread);
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2092
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2093
|
+
|
|
2094
|
+
update_metrics_and_sample(
|
|
2095
|
+
state,
|
|
2096
|
+
current_thread,
|
|
2097
|
+
thread_context,
|
|
2098
|
+
current_cpu_time_ns,
|
|
2099
|
+
current_monotonic_wall_time_ns,
|
|
2100
|
+
true);
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
// Flushes threads whose last per-tick sample was skipped (either by the SUSPENDED-skip
|
|
2104
|
+
// optimization, or by is_profiler_internal_thread) so their accumulated time is recorded.
|
|
2105
|
+
// Called by the stack recorder at the start of _native_serialize (regular periodic flush).
|
|
2106
|
+
void thread_context_collector_on_serialize(VALUE self_instance) {
|
|
2107
|
+
thread_context_collector_state *state;
|
|
2108
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2109
|
+
|
|
2110
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2111
|
+
VALUE threads = thread_list(state);
|
|
2112
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
2113
|
+
|
|
2114
|
+
for (long i = 0; i < thread_count; i++) {
|
|
2115
|
+
VALUE thread = RARRAY_AREF(threads, i);
|
|
2116
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2117
|
+
|
|
2118
|
+
if (thread_context != NULL && (thread_context->was_skipped_at_last_sample || thread_context->is_profiler_internal_thread)) {
|
|
2119
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2120
|
+
// We need to force_sample=true otherwise this sample would be skipped too
|
|
2121
|
+
update_metrics_and_sample(
|
|
2122
|
+
state,
|
|
2123
|
+
thread,
|
|
2124
|
+
thread_context,
|
|
2125
|
+
current_cpu_time_ns,
|
|
2126
|
+
current_monotonic_wall_time_ns,
|
|
2127
|
+
true);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
|
|
1833
2132
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
// per-thread context directly.
|
|
1838
|
-
//
|
|
1839
|
-
// Instead, we ask Ruby to hold the data we need in Ruby's own special per-thread context area
|
|
1840
|
-
// that's thread-safe and built for this kind of use
|
|
1841
|
-
//
|
|
1842
|
-
// Also, this function can get called on the non-main Ractor. We deal with this by checking if the value in the context
|
|
1843
|
-
// is non-zero, since only `initialize_context` ever sets the value from 0 to non-zero for threads it sees.
|
|
1844
|
-
intptr_t thread_being_profiled = gvl_profiling_state_get(thread);
|
|
1845
|
-
if (!thread_being_profiled) return;
|
|
2133
|
+
void thread_context_collector_on_gvl_released(per_thread_context *thread_context) {
|
|
2134
|
+
thread_context->gvl_state_change_count |= GVL_SUSPENDED;
|
|
2135
|
+
}
|
|
1846
2136
|
|
|
2137
|
+
void thread_context_collector_on_gvl_waiting(per_thread_context *thread_context) {
|
|
1847
2138
|
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
|
1848
|
-
if (current_monotonic_wall_time_ns <= 0
|
|
2139
|
+
if (current_monotonic_wall_time_ns <= 0) return;
|
|
1849
2140
|
|
|
1850
|
-
|
|
2141
|
+
thread_context->gvl_waiting_at = current_monotonic_wall_time_ns;
|
|
1851
2142
|
}
|
|
1852
2143
|
|
|
1853
|
-
// This function
|
|
2144
|
+
// This function runs on the passed thread and has the GVL because it gets called just after the Ruby thread acquired the GVL
|
|
1854
2145
|
__attribute__((warn_unused_result))
|
|
1855
|
-
on_gvl_running_result
|
|
1856
|
-
|
|
2146
|
+
on_gvl_running_result thread_context_collector_on_gvl_running(VALUE self_instance, VALUE thread, per_thread_context *thread_context) {
|
|
2147
|
+
thread_context_collector_state *state;
|
|
2148
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1857
2149
|
|
|
1858
|
-
//
|
|
1859
|
-
|
|
2150
|
+
// Bump the event counter and clears the state bit to "running"
|
|
2151
|
+
uint64_t counter_portion = thread_context->gvl_state_change_count >> 1;
|
|
2152
|
+
thread_context->gvl_state_change_count = ((counter_portion + 1) << 1) | GVL_RUNNING;
|
|
1860
2153
|
|
|
2154
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
2155
|
+
// Thread was not waiting on gvl
|
|
2156
|
+
if (gvl_waiting_at == 0) {
|
|
2157
|
+
return (on_gvl_running_result) {.action = ON_GVL_RUNNING_UNKNOWN, .waiting_for_gvl_duration_ns = 0};
|
|
2158
|
+
}
|
|
1861
2159
|
// @ivoanjo: I'm not sure if this can happen -- It means we should've sampled already but haven't gotten the chance yet?
|
|
1862
|
-
if (gvl_waiting_at < 0)
|
|
2160
|
+
if (gvl_waiting_at < 0) {
|
|
2161
|
+
return (on_gvl_running_result) {.action = ON_GVL_RUNNING_SAMPLE, .waiting_for_gvl_duration_ns = 0};
|
|
2162
|
+
}
|
|
1863
2163
|
|
|
1864
2164
|
long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - gvl_waiting_at;
|
|
1865
2165
|
|
|
1866
|
-
bool should_sample = waiting_for_gvl_duration_ns >= waiting_for_gvl_threshold_ns;
|
|
2166
|
+
bool should_sample = waiting_for_gvl_duration_ns >= state->waiting_for_gvl_threshold_ns;
|
|
1867
2167
|
|
|
1868
2168
|
if (should_sample) {
|
|
1869
2169
|
// We flip the gvl_waiting_at to negative to mark that the thread is now running and no longer waiting
|
|
1870
|
-
|
|
2170
|
+
long gvl_waiting_at_is_now_running = -gvl_waiting_at;
|
|
1871
2171
|
|
|
1872
|
-
|
|
2172
|
+
thread_context->gvl_waiting_at = gvl_waiting_at_is_now_running;
|
|
1873
2173
|
} else {
|
|
1874
|
-
|
|
1875
|
-
|
|
2174
|
+
thread_context->gvl_waiting_at = 0;
|
|
2175
|
+
|
|
2176
|
+
// Even though the GVL wait itself was below threshold, if the thread had skipped samples
|
|
2177
|
+
// (was suspended for a long time without the GVL), we still need to force a sample now.
|
|
2178
|
+
// Otherwise, the accumulated idle wall-time would be reported against whatever stack the
|
|
2179
|
+
// thread runs next, misrepresenting the time spent idle.
|
|
2180
|
+
if (thread_context->was_skipped_at_last_sample) {
|
|
2181
|
+
should_sample = true;
|
|
2182
|
+
}
|
|
1876
2183
|
}
|
|
1877
2184
|
|
|
1878
|
-
|
|
1879
|
-
|
|
2185
|
+
if (should_sample) {
|
|
2186
|
+
// We prepare the sample here because the postponed job might be called some time later,
|
|
2187
|
+
// possibly after some Ruby calls which change the Ruby stack,
|
|
2188
|
+
// and we want to attribute the time acquiring or without the GVL to the correct Ruby stack.
|
|
2189
|
+
prepare_sample_thread(thread, &thread_context->sampling_buffer);
|
|
2190
|
+
}
|
|
1880
2191
|
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2192
|
+
return (on_gvl_running_result) {
|
|
2193
|
+
.action = should_sample ? ON_GVL_RUNNING_SAMPLE : ON_GVL_RUNNING_DONT_SAMPLE,
|
|
2194
|
+
.waiting_for_gvl_duration_ns = waiting_for_gvl_duration_ns,
|
|
2195
|
+
};
|
|
1884
2196
|
}
|
|
1885
2197
|
|
|
1886
2198
|
// Why does this method need to exist?
|
|
@@ -1900,7 +2212,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1900
2212
|
//
|
|
1901
2213
|
// Stack:
|
|
1902
2214
|
// If the thread starts working without the end of the Waiting for GVL sample, then by the time the thread is sampled
|
|
1903
|
-
// via the regular cpu/wall-time samples mechanism, the stack can be
|
|
2215
|
+
// via the regular cpu/wall-time samples mechanism, the stack can be inaccurate (e.g. does not correctly pinpoint
|
|
1904
2216
|
// where the waiting happened).
|
|
1905
2217
|
//
|
|
1906
2218
|
// Arguably, the last sample after Waiting for GVL ended (when gvl_waiting_at < 0) should always come from this method
|
|
@@ -1909,17 +2221,19 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1909
2221
|
//
|
|
1910
2222
|
// ---
|
|
1911
2223
|
//
|
|
2224
|
+
// Always called with the GVL, either from a postponed_job or from tests.
|
|
2225
|
+
//
|
|
1912
2226
|
// NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
|
|
1913
2227
|
// exists to enable testing.
|
|
1914
2228
|
VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns) {
|
|
1915
2229
|
thread_context_collector_state *state;
|
|
1916
2230
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1917
2231
|
|
|
1918
|
-
|
|
2232
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
1919
2233
|
|
|
1920
|
-
|
|
2234
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
1921
2235
|
|
|
1922
|
-
if (gvl_waiting_at >= 0) {
|
|
2236
|
+
if (gvl_waiting_at >= 0 && !thread_context->was_skipped_at_last_sample) {
|
|
1923
2237
|
// @ivoanjo: I'm not sure if this can ever happen. This means that we're not on the same thread
|
|
1924
2238
|
// that ran `thread_context_collector_on_gvl_running` and made the decision to sample OR a regular sample was
|
|
1925
2239
|
// triggered ahead of us.
|
|
@@ -1927,9 +2241,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1927
2241
|
return Qfalse;
|
|
1928
2242
|
}
|
|
1929
2243
|
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may chose to push an
|
|
2244
|
+
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may choose to push an
|
|
1933
2245
|
// extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
|
|
1934
2246
|
// cpu-time of the thread and let `update_metrics_and_sample` decide what to do with it.
|
|
1935
2247
|
long cpu_time_for_thread = cpu_time_now_ns(thread_context);
|
|
@@ -1938,13 +2250,11 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1938
2250
|
|
|
1939
2251
|
update_metrics_and_sample(
|
|
1940
2252
|
state,
|
|
1941
|
-
|
|
1942
|
-
/* stack_from_thread: */ current_thread,
|
|
2253
|
+
current_thread,
|
|
1943
2254
|
thread_context,
|
|
1944
|
-
thread_context->sampling_buffer,
|
|
1945
2255
|
cpu_time_for_thread,
|
|
1946
|
-
current_monotonic_wall_time_ns
|
|
1947
|
-
|
|
2256
|
+
current_monotonic_wall_time_ns,
|
|
2257
|
+
false);
|
|
1948
2258
|
|
|
1949
2259
|
return Qtrue;
|
|
1950
2260
|
}
|
|
@@ -1955,14 +2265,12 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1955
2265
|
static bool handle_gvl_waiting(
|
|
1956
2266
|
thread_context_collector_state *state,
|
|
1957
2267
|
VALUE thread_being_sampled,
|
|
1958
|
-
VALUE stack_from_thread,
|
|
1959
2268
|
per_thread_context *thread_context,
|
|
1960
|
-
sampling_buffer* sampling_buffer,
|
|
1961
2269
|
long current_cpu_time_ns
|
|
1962
2270
|
) {
|
|
1963
|
-
|
|
2271
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
1964
2272
|
|
|
1965
|
-
bool is_gvl_waiting_state = gvl_waiting_at != 0
|
|
2273
|
+
bool is_gvl_waiting_state = gvl_waiting_at != 0;
|
|
1966
2274
|
|
|
1967
2275
|
if (!is_gvl_waiting_state) return false;
|
|
1968
2276
|
|
|
@@ -1975,17 +2283,17 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1975
2283
|
// ...──────────────┬───────────────────...
|
|
1976
2284
|
// Other state │ Waiting for GVL
|
|
1977
2285
|
// ...──────────────┴───────────────────...
|
|
1978
|
-
// ▲
|
|
2286
|
+
// ▲ ▲
|
|
1979
2287
|
// └─ Previous sample └─ Regular sample (caller)
|
|
1980
2288
|
//
|
|
1981
2289
|
// In this case, we'll want to push two samples: a) one for the current time (handled by the caller), b) an extra sample
|
|
1982
|
-
// to represent the remaining cpu/wall time before the "Waiting for GVL" started:
|
|
2290
|
+
// to represent the remaining cpu/wall time before the "Waiting for GVL" started (for timeline purposes):
|
|
1983
2291
|
//
|
|
1984
2292
|
// time ─────►
|
|
1985
2293
|
// ...──────────────┬───────────────────...
|
|
1986
2294
|
// Other state │ Waiting for GVL
|
|
1987
2295
|
// ...──────────────┴───────────────────...
|
|
1988
|
-
// ▲
|
|
2296
|
+
// ▲ ▲ ▲
|
|
1989
2297
|
// └─ Prev... └─ Extra sample └─ Regular sample (caller)
|
|
1990
2298
|
//
|
|
1991
2299
|
// 2. The current sample is the n-th one after we entered the "Waiting for GVL" state
|
|
@@ -1995,7 +2303,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1995
2303
|
// ...──────────────┬───────────────────────────────────────────────...
|
|
1996
2304
|
// Other state │ Waiting for GVL
|
|
1997
2305
|
// ...──────────────┴───────────────────────────────────────────────...
|
|
1998
|
-
// ▲
|
|
2306
|
+
// ▲ ▲ ▲
|
|
1999
2307
|
// └─ Previous sample └─ Previous sample └─ Regular sample (caller)
|
|
2000
2308
|
//
|
|
2001
2309
|
// In this case, we just report back to the caller that the thread is in the "Waiting for GVL" state.
|
|
@@ -2010,7 +2318,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2010
2318
|
|
|
2011
2319
|
if (gvl_waiting_at < 0) {
|
|
2012
2320
|
// Negative means the waiting for GVL just ended, so we clear the state, so next samples no longer represent waiting
|
|
2013
|
-
|
|
2321
|
+
thread_context->gvl_waiting_at = 0;
|
|
2014
2322
|
}
|
|
2015
2323
|
|
|
2016
2324
|
long gvl_waiting_started_wall_time_ns = labs(gvl_waiting_at);
|
|
@@ -2020,7 +2328,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2020
2328
|
&thread_context->cpu_time_at_previous_sample_ns,
|
|
2021
2329
|
current_cpu_time_ns,
|
|
2022
2330
|
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
2023
|
-
|
|
2331
|
+
IS_CPU_TIME
|
|
2024
2332
|
);
|
|
2025
2333
|
|
|
2026
2334
|
long duration_until_start_of_gvl_waiting_ns = update_time_since_previous_sample(
|
|
@@ -2034,9 +2342,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2034
2342
|
trigger_sample_for_thread(
|
|
2035
2343
|
state,
|
|
2036
2344
|
thread_being_sampled,
|
|
2037
|
-
stack_from_thread,
|
|
2038
2345
|
thread_context,
|
|
2039
|
-
sampling_buffer,
|
|
2040
2346
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = duration_until_start_of_gvl_waiting_ns},
|
|
2041
2347
|
gvl_waiting_started_wall_time_ns,
|
|
2042
2348
|
NULL,
|
|
@@ -2054,7 +2360,8 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2054
2360
|
|
|
2055
2361
|
debug_enter_unsafe_context();
|
|
2056
2362
|
|
|
2057
|
-
|
|
2363
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2364
|
+
if (thread_context) thread_context_collector_on_gvl_waiting(thread_context);
|
|
2058
2365
|
|
|
2059
2366
|
debug_leave_unsafe_context();
|
|
2060
2367
|
|
|
@@ -2066,66 +2373,83 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2066
2373
|
|
|
2067
2374
|
debug_enter_unsafe_context();
|
|
2068
2375
|
|
|
2069
|
-
|
|
2376
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2377
|
+
VALUE result = thread_context ? LONG2NUM(thread_context->gvl_waiting_at) : Qnil;
|
|
2070
2378
|
|
|
2071
2379
|
debug_leave_unsafe_context();
|
|
2072
2380
|
|
|
2073
|
-
return
|
|
2381
|
+
return result;
|
|
2074
2382
|
}
|
|
2075
2383
|
|
|
2076
|
-
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2384
|
+
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
|
|
2077
2385
|
ENFORCE_THREAD(thread);
|
|
2078
2386
|
|
|
2079
2387
|
debug_enter_unsafe_context();
|
|
2080
2388
|
|
|
2081
|
-
|
|
2389
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2390
|
+
VALUE result;
|
|
2391
|
+
if (thread_context) {
|
|
2392
|
+
result = thread_context_collector_on_gvl_running(collector_instance, thread, thread_context).action == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
|
|
2393
|
+
} else {
|
|
2394
|
+
result = Qfalse;
|
|
2395
|
+
}
|
|
2082
2396
|
|
|
2083
2397
|
debug_leave_unsafe_context();
|
|
2084
2398
|
|
|
2085
2399
|
return result;
|
|
2086
2400
|
}
|
|
2087
2401
|
|
|
2088
|
-
static VALUE
|
|
2402
|
+
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2089
2403
|
ENFORCE_THREAD(thread);
|
|
2090
2404
|
|
|
2091
2405
|
debug_enter_unsafe_context();
|
|
2092
2406
|
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
thread,
|
|
2096
|
-
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
2097
|
-
);
|
|
2407
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2408
|
+
if (thread_context) thread_context_collector_on_gvl_released(thread_context);
|
|
2098
2409
|
|
|
2099
2410
|
debug_leave_unsafe_context();
|
|
2100
2411
|
|
|
2101
|
-
return
|
|
2412
|
+
return Qnil;
|
|
2102
2413
|
}
|
|
2103
2414
|
|
|
2104
|
-
static VALUE
|
|
2415
|
+
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE allow_exception) {
|
|
2105
2416
|
ENFORCE_THREAD(thread);
|
|
2417
|
+
ENFORCE_BOOLEAN(allow_exception);
|
|
2106
2418
|
|
|
2107
|
-
|
|
2108
|
-
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2419
|
+
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
2109
2420
|
|
|
2110
|
-
|
|
2111
|
-
|
|
2421
|
+
VALUE result = thread_context_collector_sample_after_gvl_running(
|
|
2422
|
+
collector_instance,
|
|
2423
|
+
thread,
|
|
2424
|
+
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
2425
|
+
);
|
|
2112
2426
|
|
|
2113
|
-
|
|
2427
|
+
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
|
2114
2428
|
|
|
2115
|
-
return
|
|
2429
|
+
return result;
|
|
2116
2430
|
}
|
|
2117
2431
|
|
|
2118
2432
|
#else
|
|
2119
2433
|
static bool handle_gvl_waiting(
|
|
2120
2434
|
DDTRACE_UNUSED thread_context_collector_state *state,
|
|
2121
2435
|
DDTRACE_UNUSED VALUE thread_being_sampled,
|
|
2122
|
-
DDTRACE_UNUSED VALUE stack_from_thread,
|
|
2123
2436
|
DDTRACE_UNUSED per_thread_context *thread_context,
|
|
2124
|
-
DDTRACE_UNUSED sampling_buffer* sampling_buffer,
|
|
2125
2437
|
DDTRACE_UNUSED long current_cpu_time_ns
|
|
2126
2438
|
) { return false; }
|
|
2439
|
+
|
|
2127
2440
|
#endif // NO_GVL_INSTRUMENTATION
|
|
2128
2441
|
|
|
2442
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns) {
|
|
2443
|
+
ENFORCE_THREAD(thread);
|
|
2444
|
+
|
|
2445
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2446
|
+
if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2447
|
+
|
|
2448
|
+
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
|
2449
|
+
|
|
2450
|
+
return Qtrue;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2129
2453
|
#define MAX_SAFE_LOOKUP_SIZE 16
|
|
2130
2454
|
|
|
2131
2455
|
typedef struct { VALUE lookup_key; VALUE result; } safe_lookup_hash_state;
|
|
@@ -2169,3 +2493,7 @@ static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE c
|
|
|
2169
2493
|
|
|
2170
2494
|
return LONG2NUM(system_epoch_time_ns);
|
|
2171
2495
|
}
|
|
2496
|
+
|
|
2497
|
+
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self) {
|
|
2498
|
+
return thread_context_collector_prepare_sample_inside_signal_handler() ? Qtrue : Qfalse;
|
|
2499
|
+
}
|