datadog 2.21.0 → 2.38.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 +574 -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 +301 -82
- 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 +63 -32
- data/ext/datadog_profiling_native_extension/collectors_stack.h +7 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +728 -436
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +19 -6
- 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 +41 -37
- 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 +252 -103
- data/ext/datadog_profiling_native_extension/heap_recorder.h +12 -1
- data/ext/datadog_profiling_native_extension/http_transport.c +66 -68
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +3 -22
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +0 -5
- 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 +40 -43
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +6 -0
- data/ext/datadog_profiling_native_extension/profiling.c +65 -19
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +63 -46
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +17 -5
- 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 +110 -85
- data/ext/datadog_profiling_native_extension/stack_recorder.h +3 -1
- data/ext/datadog_profiling_native_extension/time_helpers.h +13 -7
- 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 +18 -24
- 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 +34 -25
- data/ext/libdatadog_api/process_discovery.c +19 -13
- data/ext/libdatadog_extconf_helpers.rb +94 -11
- 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 +38 -9
- data/lib/datadog/appsec/api_security/sampler.rb +7 -4
- 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 +359 -4
- data/lib/datadog/appsec/assets/waf_rules/strict.json +43 -2
- data/lib/datadog/appsec/assets.rb +1 -1
- data/lib/datadog/appsec/autoload.rb +1 -1
- data/lib/datadog/appsec/component.rb +16 -9
- data/lib/datadog/appsec/compressed_json.rb +3 -3
- data/lib/datadog/appsec/configuration.rb +427 -1
- data/lib/datadog/appsec/context.rb +51 -13
- 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 +2 -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 -12
- 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 -12
- 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 +52 -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 +48 -33
- data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +40 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +26 -5
- data/lib/datadog/appsec/contrib/rails/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/patcher.rb +38 -0
- data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +2 -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 -11
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +43 -13
- data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +4 -4
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +1 -1
- data/lib/datadog/appsec/counter_sampler.rb +25 -0
- data/lib/datadog/appsec/default_header_tags.rb +52 -0
- data/lib/datadog/appsec/event.rb +13 -31
- data/lib/datadog/appsec/ext.rb +3 -0
- 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 +43 -5
- data/lib/datadog/appsec/metrics/exporter.rb +16 -3
- data/lib/datadog/appsec/metrics/telemetry.rb +20 -3
- data/lib/datadog/appsec/metrics/telemetry_exporter.rb +20 -1
- data/lib/datadog/appsec/metrics.rb +5 -5
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +8 -6
- data/lib/datadog/appsec/remote.rb +44 -29
- 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 +26 -5
- data/lib/datadog/appsec/security_engine/result.rb +29 -9
- data/lib/datadog/appsec/security_engine/runner.rb +20 -10
- data/lib/datadog/appsec/security_event.rb +5 -7
- data/lib/datadog/appsec/trace_keeper.rb +18 -6
- 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 +6 -3
- data/lib/datadog/core/configuration/agentless_settings_resolver.rb +2 -2
- data/lib/datadog/core/configuration/base.rb +17 -5
- data/lib/datadog/core/configuration/components.rb +185 -17
- data/lib/datadog/core/configuration/components_state.rb +6 -1
- data/lib/datadog/core/configuration/config_helper.rb +11 -2
- data/lib/datadog/core/configuration/deprecations.rb +2 -2
- data/lib/datadog/core/configuration/option.rb +32 -6
- data/lib/datadog/core/configuration/option_definition.rb +42 -14
- data/lib/datadog/core/configuration/options.rb +49 -12
- data/lib/datadog/core/configuration/settings.rb +115 -16
- data/lib/datadog/core/configuration/stable_config.rb +10 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +379 -300
- data/lib/datadog/core/configuration.rb +3 -3
- 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 +6 -0
- data/lib/datadog/core/ddsketch.rb +19 -0
- 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 -0
- 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/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 +59 -0
- data/lib/datadog/core/logger.rb +1 -1
- data/lib/datadog/core/metrics/client.rb +6 -6
- data/lib/datadog/core/metrics/logging.rb +1 -1
- data/lib/datadog/core/pin.rb +8 -8
- data/lib/datadog/core/process_discovery.rb +21 -18
- 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 +1 -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/metrics.rb +11 -2
- data/lib/datadog/core/semaphore.rb +1 -4
- data/lib/datadog/core/tag_builder.rb +0 -4
- data/lib/datadog/core/tag_normalizer.rb +84 -0
- data/lib/datadog/core/telemetry/component.rb +59 -15
- data/lib/datadog/core/telemetry/emitter.rb +6 -6
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +2 -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 +177 -171
- 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 +4 -2
- data/lib/datadog/core/telemetry/logging.rb +17 -5
- 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/api/endpoint.rb +9 -4
- data/lib/datadog/core/transport/http/api/instance.rb +4 -21
- data/lib/datadog/core/transport/http/builder.rb +10 -6
- data/lib/datadog/core/transport/http/client.rb +80 -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 +2 -2
- 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 -21
- 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 +6 -2
- data/lib/datadog/core/utils.rb +3 -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 +10 -3
- data/lib/datadog/data_streams/configuration.rb +50 -0
- data/lib/datadog/data_streams/ext.rb +11 -0
- data/lib/datadog/data_streams/extensions.rb +16 -0
- data/lib/datadog/data_streams/pathway_context.rb +169 -0
- data/lib/datadog/data_streams/processor.rb +511 -0
- data/lib/datadog/data_streams/transport/http/stats.rb +58 -0
- data/lib/datadog/data_streams/transport/http.rb +36 -0
- data/lib/datadog/data_streams/transport/stats.rb +39 -0
- data/lib/datadog/data_streams.rb +100 -0
- data/lib/datadog/di/base.rb +15 -7
- data/lib/datadog/di/boot.rb +7 -2
- data/lib/datadog/di/capture_expression.rb +21 -0
- data/lib/datadog/di/capture_expression_evaluator.rb +71 -0
- data/lib/datadog/di/capture_limits.rb +41 -0
- data/lib/datadog/di/code_tracker.rb +217 -15
- data/lib/datadog/di/component.rb +139 -51
- data/lib/datadog/di/configuration.rb +241 -2
- data/lib/datadog/di/context.rb +82 -0
- data/lib/datadog/di/contrib/active_record.rb +34 -5
- data/lib/datadog/di/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 +623 -147
- data/lib/datadog/di/logger.rb +2 -2
- data/lib/datadog/di/probe.rb +122 -14
- data/lib/datadog/di/probe_builder.rb +124 -3
- data/lib/datadog/di/probe_file_loader/railtie.rb +1 -1
- data/lib/datadog/di/probe_file_loader.rb +12 -7
- data/lib/datadog/di/probe_manager.rb +195 -100
- data/lib/datadog/di/probe_notification_builder.rb +321 -90
- data/lib/datadog/di/probe_notifier_worker.rb +112 -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 -93
- data/lib/datadog/di/serializer.rb +310 -29
- 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 +2 -34
- data/lib/datadog/di/transport/http.rb +43 -19
- data/lib/datadog/di/transport/input.rb +112 -38
- data/lib/datadog/di/utils.rb +42 -14
- data/lib/datadog/di.rb +204 -4
- 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 +60 -3
- data/lib/datadog/kit/enable_core_dumps.rb +1 -1
- 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 +1 -1
- 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 +65 -1
- 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 +9 -3
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +4 -1
- data/lib/datadog/opentelemetry/sdk.rb +19 -0
- data/lib/datadog/opentelemetry/signal_configuration.rb +53 -0
- data/lib/datadog/opentelemetry.rb +4 -0
- data/lib/datadog/profiling/collectors/code_provenance.rb +71 -12
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +52 -4
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +13 -5
- data/lib/datadog/profiling/collectors/info.rb +18 -4
- data/lib/datadog/profiling/collectors/thread_context.rb +10 -5
- data/lib/datadog/profiling/component.rb +82 -40
- data/lib/datadog/profiling/exporter.rb +41 -12
- 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 +0 -2
- data/lib/datadog/profiling/flush.rb +23 -11
- data/lib/datadog/profiling/http_transport.rb +21 -8
- data/lib/datadog/profiling/load_native_extension.rb +2 -2
- data/lib/datadog/profiling/profiler.rb +31 -14
- data/lib/datadog/profiling/scheduler.rb +10 -9
- data/lib/datadog/profiling/stack_recorder.rb +0 -8
- data/lib/datadog/profiling/tag_builder.rb +37 -3
- data/lib/datadog/profiling/tasks/exec.rb +8 -3
- data/lib/datadog/profiling/tasks/help.rb +1 -0
- data/lib/datadog/profiling/tasks/setup.rb +2 -2
- data/lib/datadog/profiling.rb +1 -3
- data/lib/datadog/ruby_version.rb +25 -0
- data/lib/datadog/single_step_instrument.rb +2 -2
- data/lib/datadog/symbol_database/component.rb +734 -0
- data/lib/datadog/symbol_database/configuration.rb +73 -0
- data/lib/datadog/symbol_database/extensions.rb +19 -0
- data/lib/datadog/symbol_database/extractor.rb +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/buffer.rb +3 -3
- data/lib/datadog/tracing/component.rb +17 -17
- data/lib/datadog/tracing/configuration/dynamic.rb +2 -2
- data/lib/datadog/tracing/configuration/ext.rb +22 -0
- data/lib/datadog/tracing/configuration/settings.rb +96 -4
- 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_mailer/event.rb +4 -1
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +10 -7
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +28 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +3 -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 +5 -2
- data/lib/datadog/tracing/contrib/active_job/data_streams.rb +62 -0
- data/lib/datadog/tracing/contrib/active_job/events/discard.rb +5 -2
- data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +5 -2
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +5 -2
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +5 -2
- data/lib/datadog/tracing/contrib/active_job/events/perform.rb +5 -2
- data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +5 -2
- data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
- data/lib/datadog/tracing/contrib/active_job/patcher.rb +17 -1
- data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
- data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +3 -4
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +2 -2
- data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +2 -5
- data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +2 -5
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +1 -4
- data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +6 -8
- data/lib/datadog/tracing/contrib/component.rb +3 -3
- data/lib/datadog/tracing/contrib/configurable.rb +18 -3
- data/lib/datadog/tracing/contrib/configuration/resolver.rb +14 -4
- data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +1 -5
- data/lib/datadog/tracing/contrib/dalli/integration.rb +4 -1
- data/lib/datadog/tracing/contrib/dalli/quantize.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/patcher.rb +2 -6
- data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +2 -2
- data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +5 -1
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +5 -6
- data/lib/datadog/tracing/contrib/ethon/ext.rb +1 -0
- 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 +1 -0
- data/lib/datadog/tracing/contrib/excon/middleware.rb +3 -7
- data/lib/datadog/tracing/contrib/ext.rb +3 -1
- data/lib/datadog/tracing/contrib/extensions.rb +19 -2
- data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +16 -9
- data/lib/datadog/tracing/contrib/faraday/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +3 -7
- data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +10 -7
- data/lib/datadog/tracing/contrib/grape/instrumentation.rb +13 -8
- 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/unified_trace.rb +75 -44
- data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +3 -7
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +3 -7
- 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.rb +1 -0
- data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +1 -0
- data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +1 -0
- data/lib/datadog/tracing/contrib/http/configuration/settings.rb +16 -5
- data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/http/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +4 -8
- 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 +1 -0
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +7 -7
- data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +16 -5
- data/lib/datadog/tracing/contrib/httprb/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +4 -8
- data/lib/datadog/tracing/contrib/kafka/event.rb +1 -0
- 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/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 +11 -0
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +35 -4
- data/lib/datadog/tracing/contrib/karafka.rb +1 -0
- data/lib/datadog/tracing/contrib/mongodb/parsers.rb +5 -5
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +5 -8
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +3 -6
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +2 -6
- data/lib/datadog/tracing/contrib/opensearch/quantize.rb +2 -2
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +15 -18
- data/lib/datadog/tracing/contrib/presto/instrumentation.rb +6 -8
- 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 +24 -0
- 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/tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/racecar/event.rb +1 -5
- 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_tagging.rb +23 -0
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +62 -27
- data/lib/datadog/tracing/contrib/rack/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/rack/request_queue.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +67 -0
- data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +119 -1
- data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/rails/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
- data/lib/datadog/tracing/contrib/rails/middlewares.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/rails/runner.rb +3 -1
- data/lib/datadog/tracing/contrib/rake/instrumentation.rb +6 -4
- data/lib/datadog/tracing/contrib/redis/quantize.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/tags.rb +1 -6
- data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +2 -0
- data/lib/datadog/tracing/contrib/registerable.rb +11 -0
- data/lib/datadog/tracing/contrib/resque/resque_job.rb +1 -0
- data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +5 -2
- data/lib/datadog/tracing/contrib/rest_client/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +5 -6
- data/lib/datadog/tracing/contrib/roda/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/roda/instrumentation.rb +7 -2
- data/lib/datadog/tracing/contrib/sequel/database.rb +1 -0
- data/lib/datadog/tracing/contrib/sequel/dataset.rb +1 -0
- data/lib/datadog/tracing/contrib/sequel/utils.rb +2 -7
- 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 +1 -0
- 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 +3 -2
- 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/framework.rb +1 -1
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +1 -0
- data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +4 -1
- 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/status_range_matcher.rb +13 -1
- data/lib/datadog/tracing/contrib/stripe/request.rb +1 -1
- data/lib/datadog/tracing/contrib/sucker_punch/instrumentation.rb +1 -0
- data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -6
- data/lib/datadog/tracing/contrib/utils/quantization/{hash.rb → hash_formatter.rb} +4 -2
- 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 +4 -2
- data/lib/datadog/tracing/distributed/baggage.rb +64 -8
- data/lib/datadog/tracing/distributed/datadog.rb +13 -11
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +1 -14
- data/lib/datadog/tracing/distributed/propagation.rb +35 -3
- data/lib/datadog/tracing/distributed/trace_context.rb +85 -62
- data/lib/datadog/tracing/event.rb +1 -1
- data/lib/datadog/tracing/ext.rb +9 -0
- data/lib/datadog/tracing/metadata/ext.rb +23 -1
- data/lib/datadog/tracing/metadata/tagging.rb +2 -2
- data/lib/datadog/tracing/pipeline.rb +1 -1
- data/lib/datadog/tracing/remote.rb +36 -13
- data/lib/datadog/tracing/sampling/ext.rb +2 -0
- 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 +54 -25
- data/lib/datadog/tracing/sampling/span/rule_parser.rb +2 -2
- data/lib/datadog/tracing/span.rb +1 -1
- data/lib/datadog/tracing/span_event.rb +2 -2
- data/lib/datadog/tracing/span_operation.rb +34 -10
- data/lib/datadog/tracing/sync_writer.rb +0 -1
- data/lib/datadog/tracing/trace_digest.rb +7 -0
- data/lib/datadog/tracing/trace_operation.rb +123 -16
- data/lib/datadog/tracing/tracer.rb +82 -23
- data/lib/datadog/tracing/transport/http/client.rb +12 -26
- data/lib/datadog/tracing/transport/http/traces.rb +2 -50
- data/lib/datadog/tracing/transport/http.rb +15 -9
- data/lib/datadog/tracing/transport/io/client.rb +6 -9
- data/lib/datadog/tracing/transport/io/traces.rb +28 -34
- data/lib/datadog/tracing/transport/trace_formatter.rb +22 -0
- data/lib/datadog/tracing/transport/traces.rb +13 -81
- data/lib/datadog/tracing/workers.rb +2 -1
- data/lib/datadog/tracing/writer.rb +1 -1
- data/lib/datadog/version.rb +2 -2
- data/lib/datadog.rb +8 -1
- metadata +185 -36
- 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/configuration/settings.rb +0 -400
- data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
- 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,6 +9,7 @@
|
|
|
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"
|
|
@@ -41,11 +43,8 @@
|
|
|
41
43
|
// When `thread_context_collector_on_gc_start` gets called, the current cpu and wall-time get recorded to the thread
|
|
42
44
|
// context: `cpu_time_at_gc_start_ns` and `wall_time_at_gc_start_ns`.
|
|
43
45
|
//
|
|
44
|
-
// While `cpu_time_at_gc_start_ns` is set,
|
|
45
|
-
//
|
|
46
|
-
// Wall-time accounting is not affected by this (e.g. we still record 60 seconds every 60 seconds).
|
|
47
|
-
//
|
|
48
|
-
// (Regular samples can still account for the cpu-time between the previous sample and the start of GC.)
|
|
46
|
+
// While `cpu_time_at_gc_start_ns` is set, we don't expect the thread to be sampled: the VM is doing GC
|
|
47
|
+
// on the thread holding the GVL so no other samples can/will be triggered until GC finishes.
|
|
49
48
|
//
|
|
50
49
|
// When `thread_context_collector_on_gc_finish` gets called, the cpu-time and wall-time spent during GC gets recorded
|
|
51
50
|
// into the global gc_tracking structure, and further samples are not affected. (The `cpu_time_at_previous_sample_ns`
|
|
@@ -74,15 +73,14 @@
|
|
|
74
73
|
|
|
75
74
|
#define THREAD_ID_LIMIT_CHARS 44 // Why 44? "#{2**64} (#{2**64})".size + 1 for \0
|
|
76
75
|
#define THREAD_INVOKE_LOCATION_LIMIT_CHARS 512
|
|
77
|
-
#define IS_WALL_TIME true
|
|
78
|
-
#define IS_NOT_WALL_TIME false
|
|
79
76
|
#define MISSING_TRACER_CONTEXT_KEY 0
|
|
80
77
|
#define TIME_BETWEEN_GC_EVENTS_NS MILLIS_AS_NS(10)
|
|
78
|
+
#define GVL_SUSPENDED ((uint64_t)1)
|
|
79
|
+
#define GVL_RUNNING ((uint64_t)0)
|
|
80
|
+
|
|
81
|
+
#define MAX(a, b) ((a) < (b) ? (b) : (a))
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
// (e.g. to avoid trying to gvl profile threads that are not from the main Ractor)
|
|
84
|
-
// and for which there's no data yet
|
|
85
|
-
#define GVL_WAITING_ENABLED_EMPTY RUBY_FIXNUM_MAX
|
|
83
|
+
static ID dd_per_thread_context_id; // Hidden ivar (no @ prefix, inaccessible from Ruby)
|
|
86
84
|
|
|
87
85
|
static ID at_active_span_id; // id of :@active_span in Ruby
|
|
88
86
|
static ID at_active_trace_id; // id of :@active_trace in Ruby
|
|
@@ -106,12 +104,20 @@ static ID server_id; // id of :server in Ruby
|
|
|
106
104
|
static ID otel_context_storage_id; // id of :__opentelemetry_context_storage__ in Ruby
|
|
107
105
|
static ID otel_fiber_context_storage_id; // id of :@opentelemetry_context in Ruby
|
|
108
106
|
|
|
109
|
-
// This is
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
107
|
+
// This is mutable and gets set last-writer-wins style by
|
|
108
|
+
// `thread_context_collector_reset_all_per_thread_contexts`, which is called whenever
|
|
109
|
+
// profiling is starting or restating.
|
|
110
|
+
//
|
|
111
|
+
// Note: We must be careful to not change this value while the profiler is still running,
|
|
112
|
+
// otherwise new threads can get the new value and cause profiling to stop with an
|
|
113
|
+
// exception due to the mismatched size.
|
|
114
|
+
//
|
|
115
|
+
// The initial value should be kept in sync with the default for DD_PROFILING_MAX_FRAMES
|
|
116
|
+
// in settings.rb. See `initialize_context` for details on why this is needed/used.
|
|
117
|
+
static uint16_t latest_max_frames = 400;
|
|
118
|
+
|
|
119
|
+
// Global tracepoint for RUBY_EVENT_THREAD_BEGIN. Created and enabled once when the first ThreadContext collector is initialized.
|
|
120
|
+
static VALUE thread_begin_tracepoint = Qnil;
|
|
115
121
|
|
|
116
122
|
typedef enum { OTEL_CONTEXT_ENABLED_FALSE, OTEL_CONTEXT_ENABLED_ONLY, OTEL_CONTEXT_ENABLED_BOTH } otel_context_enabled;
|
|
117
123
|
typedef enum { OTEL_CONTEXT_SOURCE_UNKNOWN, OTEL_CONTEXT_SOURCE_FIBER_IVAR, OTEL_CONTEXT_SOURCE_FIBER_LOCAL } otel_context_source;
|
|
@@ -121,28 +127,18 @@ typedef struct {
|
|
|
121
127
|
// Note: Places in this file that usually need to be changed when this struct is changed are tagged with
|
|
122
128
|
// "Update this when modifying state struct"
|
|
123
129
|
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
uint16_t max_frames;
|
|
127
|
-
// Hashmap <Thread Object, per_thread_context>
|
|
128
|
-
// Note: Be very careful when mutating this map, as it gets read e.g. in the middle of GC and signal handlers.
|
|
129
|
-
st_table *hash_map_per_thread_context;
|
|
130
|
+
// Output buffer for stack traces, passed to sample_thread()
|
|
131
|
+
sample_locations locations;
|
|
130
132
|
// Datadog::Profiling::StackRecorder instance
|
|
131
133
|
VALUE recorder_instance;
|
|
132
134
|
// If the tracer is available and enabled, this will be the fiber-local symbol for accessing its running context,
|
|
133
135
|
// to enable code hotspots and endpoint aggregation.
|
|
134
136
|
// When not available, this is set to MISSING_TRACER_CONTEXT_KEY.
|
|
135
137
|
ID tracer_context_key;
|
|
136
|
-
// Track how many regular samples we've taken. Does not include garbage collection samples.
|
|
137
|
-
// Currently **outside** of stats struct because we also use it to decide when to clean the contexts, and thus this
|
|
138
|
-
// is not (just) a stat.
|
|
139
|
-
unsigned int sample_count;
|
|
140
138
|
// Reusable array to get list of threads
|
|
141
139
|
VALUE thread_list_buffer;
|
|
142
140
|
// Used to omit endpoint names (retrieved from tracer) from collected data
|
|
143
141
|
bool endpoint_collection_enabled;
|
|
144
|
-
// Used to omit timestamps / timeline events from collected data
|
|
145
|
-
bool timeline_enabled;
|
|
146
142
|
// Used to control context collection
|
|
147
143
|
otel_context_enabled otel_context_enabled;
|
|
148
144
|
// Used to remember where otel context is being stored after we observe it the first time
|
|
@@ -159,12 +155,22 @@ typedef struct {
|
|
|
159
155
|
bool native_filenames_enabled;
|
|
160
156
|
// Used to cache native filename lookup results (Map[void *function_pointer, char *filename])
|
|
161
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;
|
|
162
162
|
|
|
163
163
|
struct stats {
|
|
164
|
+
// Track how many regular samples we've taken. Does not include garbage collection samples.
|
|
165
|
+
unsigned int sample_count;
|
|
164
166
|
// Track how many garbage collection samples we've taken.
|
|
165
167
|
unsigned int gc_samples;
|
|
166
168
|
// See thread_context_collector_on_gc_start for details
|
|
167
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;
|
|
168
174
|
} stats;
|
|
169
175
|
|
|
170
176
|
struct {
|
|
@@ -176,16 +182,90 @@ typedef struct {
|
|
|
176
182
|
} gc_tracking;
|
|
177
183
|
} thread_context_collector_state;
|
|
178
184
|
|
|
179
|
-
// Tracks per-thread state
|
|
180
|
-
|
|
185
|
+
// Tracks per-thread state.
|
|
186
|
+
// This state is global and lives forever on the Ruby Thread (until the Thread is GC'd).
|
|
187
|
+
// The state is created early on for all threads on the main Ractor
|
|
188
|
+
// (enabling a TracePoint only enables it for the current Ractor).
|
|
189
|
+
// The state is either created when the Thread starts running (via the RUBY_EVENT_THREAD_BEGIN TracePoint),
|
|
190
|
+
// or via `thread_context_collector_reset_all_per_thread_contexts` when the profiler starts.
|
|
191
|
+
//
|
|
192
|
+
// Unfortunately that RUBY_EVENT_THREAD_BEGIN TracePoint still fires after some other events:
|
|
193
|
+
// * RUBY_INTERNAL_THREAD_EVENT_RESUMED for the Thread acquiring the GVL for the first time
|
|
194
|
+
// * an early SIGPROF calling handle_sampling_signal()
|
|
195
|
+
// * Ruby might check for interrupts and run postponed jobs (e.g. thread_context_collector_sample)
|
|
196
|
+
// * another RUBY_EVENT_THREAD_BEGIN TracePoint which might run before ours
|
|
197
|
+
// * etc
|
|
198
|
+
// For those cases we have to ignore those events and we cannot assume the state is always set,
|
|
199
|
+
// however this only matters for a very short period when a thread starts.
|
|
200
|
+
struct per_thread_context {
|
|
181
201
|
sampling_buffer sampling_buffer;
|
|
182
202
|
char thread_id[THREAD_ID_LIMIT_CHARS];
|
|
183
203
|
ddog_CharSlice thread_id_char_slice;
|
|
184
204
|
char thread_invoke_location[THREAD_INVOKE_LOCATION_LIMIT_CHARS];
|
|
185
205
|
ddog_CharSlice thread_invoke_location_char_slice;
|
|
186
206
|
thread_cpu_time_id thread_cpu_time_id;
|
|
187
|
-
long cpu_time_at_previous_sample_ns;
|
|
188
|
-
long wall_time_at_previous_sample_ns;
|
|
207
|
+
long cpu_time_at_previous_sample_ns;
|
|
208
|
+
long wall_time_at_previous_sample_ns;
|
|
209
|
+
|
|
210
|
+
// There are 3 possible states for the GVL (per thread), and 3 transitions for which we receive GVL events:
|
|
211
|
+
// Thread holds the GVL
|
|
212
|
+
// on_gvl_released() the thread releases the GVL (RUBY_INTERNAL_THREAD_EVENT_SUSPENDED)
|
|
213
|
+
// Thread runs without the GVL
|
|
214
|
+
// on_gvl_waiting() the thread wants the GVL (RUBY_INTERNAL_THREAD_EVENT_READY)
|
|
215
|
+
// Thread is "Waiting for GVL"
|
|
216
|
+
// on_gvl_running() the thread now got the GVL (RUBY_INTERNAL_THREAD_EVENT_RESUMED)
|
|
217
|
+
// ... and the cycle restarts
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
// --- GVL waiting tracking state machine ---
|
|
221
|
+
//
|
|
222
|
+
// gvl_waiting_at tracks the GVL wait state for each profiled thread:
|
|
223
|
+
//
|
|
224
|
+
// ┌───────────────────────────────────┐
|
|
225
|
+
// │ on_gvl_waiting │
|
|
226
|
+
// │ ▼
|
|
227
|
+
// Not Waiting (0) ◀────────────────── Waiting (> 0)
|
|
228
|
+
// ▲ on_gvl_running │
|
|
229
|
+
// │ (below threshold) │ on_gvl_running (above threshold)
|
|
230
|
+
// │ ▼
|
|
231
|
+
// └─────────────────────────── Sample Pending (< 0)
|
|
232
|
+
// sample / sample_after_gvl_running
|
|
233
|
+
//
|
|
234
|
+
// Not Waiting (0): thread is running or not waiting for the GVL
|
|
235
|
+
// Waiting (> 0): monotonic wall time (ns) when the thread started waiting
|
|
236
|
+
// Sample Pending (< 0): negated timestamp; the wait ended and a sample is pending
|
|
237
|
+
//
|
|
238
|
+
// The field is accessed under the GVL for most functions EXCEPT on_gvl_waiting() which writes to it without the GVL.
|
|
239
|
+
// So we need to pack the above state in a single long to ensure atomicity.
|
|
240
|
+
long gvl_waiting_at;
|
|
241
|
+
|
|
242
|
+
// Per-thread "state + version" word, updated on every GVL state transition. The encoding is:
|
|
243
|
+
// - low bit: current state (1 = currently suspended, 0 = currently running)
|
|
244
|
+
// - bits 1+: monotonic event counter (incremented on every RESUMED)
|
|
245
|
+
// The hooks set the state bit explicitly rather than relying on parity, so the encoding stays
|
|
246
|
+
// correct even when events are not paired properly (as in tests).
|
|
247
|
+
//
|
|
248
|
+
// Note that SUSPENDED can happen multiple times in a row on Ruby 3.2,
|
|
249
|
+
// see https://github.com/DataDog/dd-trace-rb/pull/5777#discussion_r3388560254,
|
|
250
|
+
// the encoding is designed to naturally not change the field in such a case.
|
|
251
|
+
uint64_t gvl_state_change_count;
|
|
252
|
+
// Snapshot of the thread's gvl_state_change_count at the moment we last sampled it.
|
|
253
|
+
// Equality with this snapshot means no GVL transition since the last sample.
|
|
254
|
+
uint64_t gvl_state_change_count_at_previous_sample;
|
|
255
|
+
// True when the previous per-tick sample was skipped by the SUSPENDED-skip optimization, so the
|
|
256
|
+
// flush-before-serialize pass knows it needs to report this thread.
|
|
257
|
+
// As a result, we will accumulate all wall & CPU time as a single batch per reporting period,
|
|
258
|
+
// but this is deemed worth it for this optimization. In any case we don't know exactly
|
|
259
|
+
// at what time a thread was doing CPU work (unless it's on CPU 100% of the time).
|
|
260
|
+
bool was_skipped_at_last_sample;
|
|
261
|
+
// Set as true for CpuAndWallTimeWorker and IdleSamplingHelper threads.
|
|
262
|
+
// When true, per-tick samples are skipped entirely; the thread is sampled only once per
|
|
263
|
+
// reporting period during the on_serialize flush.
|
|
264
|
+
//
|
|
265
|
+
// These threads are always in native code so their stacks aren't interesting;
|
|
266
|
+
// the Profiling::Scheduler thread on the other hand does a lot of different
|
|
267
|
+
// things using a mix of Ruby and native code, so that one isn't considered internal.
|
|
268
|
+
bool is_profiler_internal_thread;
|
|
189
269
|
|
|
190
270
|
struct {
|
|
191
271
|
// Both of these fields are set by on_gc_start and kept until on_gc_finish is called.
|
|
@@ -193,7 +273,7 @@ typedef struct {
|
|
|
193
273
|
long cpu_time_at_start_ns;
|
|
194
274
|
long wall_time_at_start_ns;
|
|
195
275
|
} gc_tracking;
|
|
196
|
-
}
|
|
276
|
+
};
|
|
197
277
|
|
|
198
278
|
// Used to correlate profiles with traces
|
|
199
279
|
typedef struct {
|
|
@@ -211,29 +291,26 @@ typedef struct {
|
|
|
211
291
|
|
|
212
292
|
static void thread_context_collector_typed_data_mark(void *state_ptr);
|
|
213
293
|
static void thread_context_collector_typed_data_free(void *state_ptr);
|
|
214
|
-
static
|
|
215
|
-
static
|
|
294
|
+
static void per_thread_context_typed_data_mark(void *ctx_ptr);
|
|
295
|
+
static void per_thread_context_typed_data_free(void *ctx_ptr);
|
|
216
296
|
static VALUE _native_new(VALUE klass);
|
|
217
297
|
static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
|
218
|
-
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE
|
|
298
|
+
static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE allow_exception);
|
|
219
299
|
static VALUE _native_on_gc_start(VALUE self, VALUE collector_instance);
|
|
220
300
|
static VALUE _native_on_gc_finish(VALUE self, VALUE collector_instance);
|
|
221
301
|
static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE allow_exception);
|
|
222
302
|
static void update_metrics_and_sample(
|
|
223
303
|
thread_context_collector_state *state,
|
|
224
304
|
VALUE thread_being_sampled,
|
|
225
|
-
VALUE stack_from_thread,
|
|
226
305
|
per_thread_context *thread_context,
|
|
227
|
-
sampling_buffer* sampling_buffer,
|
|
228
306
|
long current_cpu_time_ns,
|
|
229
|
-
long current_monotonic_wall_time_ns
|
|
307
|
+
long current_monotonic_wall_time_ns,
|
|
308
|
+
bool force_sample
|
|
230
309
|
);
|
|
231
310
|
static void trigger_sample_for_thread(
|
|
232
311
|
thread_context_collector_state *state,
|
|
233
|
-
VALUE
|
|
234
|
-
VALUE stack_from_thread,
|
|
312
|
+
VALUE thread_being_sampled,
|
|
235
313
|
per_thread_context *thread_context,
|
|
236
|
-
sampling_buffer* sampling_buffer,
|
|
237
314
|
sample_values values,
|
|
238
315
|
long current_monotonic_wall_time_ns,
|
|
239
316
|
ddog_CharSlice *ruby_vm_type,
|
|
@@ -242,19 +319,16 @@ static void trigger_sample_for_thread(
|
|
|
242
319
|
bool is_safe_to_allocate_objects
|
|
243
320
|
);
|
|
244
321
|
static VALUE _native_thread_list(VALUE self);
|
|
245
|
-
static
|
|
246
|
-
static per_thread_context *
|
|
247
|
-
static void initialize_context(VALUE thread, per_thread_context *thread_context
|
|
248
|
-
static void free_context(per_thread_context* thread_context);
|
|
322
|
+
static void check_frozen_thread(VALUE thread);
|
|
323
|
+
static per_thread_context *get_or_create_context_for(VALUE thread);
|
|
324
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context);
|
|
249
325
|
static VALUE _native_inspect(VALUE self, VALUE collector_instance);
|
|
250
|
-
static VALUE
|
|
251
|
-
static
|
|
252
|
-
static VALUE stats_as_ruby_hash(thread_context_collector_state *state);
|
|
326
|
+
static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context);
|
|
327
|
+
static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash);
|
|
253
328
|
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state);
|
|
254
|
-
static void remove_context_for_dead_threads(thread_context_collector_state *state);
|
|
255
|
-
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, st_data_t _argument);
|
|
256
329
|
static VALUE _native_per_thread_context(VALUE self, VALUE collector_instance);
|
|
257
|
-
static long
|
|
330
|
+
static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns);
|
|
331
|
+
static long update_wall_time_since_previous_sample(per_thread_context *thread_context, long current_wall_time_ns);
|
|
258
332
|
static long cpu_time_now_ns(per_thread_context *thread_context);
|
|
259
333
|
static long thread_id_for(VALUE thread);
|
|
260
334
|
static VALUE _native_stats(VALUE self, VALUE collector_instance);
|
|
@@ -284,16 +358,17 @@ static VALUE _native_sample_skipped_allocation_samples(DDTRACE_UNUSED VALUE self
|
|
|
284
358
|
static bool handle_gvl_waiting(
|
|
285
359
|
thread_context_collector_state *state,
|
|
286
360
|
VALUE thread_being_sampled,
|
|
287
|
-
VALUE stack_from_thread,
|
|
288
361
|
per_thread_context *thread_context,
|
|
289
|
-
sampling_buffer* sampling_buffer,
|
|
290
362
|
long current_cpu_time_ns
|
|
291
363
|
);
|
|
292
364
|
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
293
|
-
static VALUE
|
|
294
|
-
|
|
295
|
-
static VALUE
|
|
296
|
-
static VALUE
|
|
365
|
+
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
366
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
|
367
|
+
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
368
|
+
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread);
|
|
369
|
+
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE allow_exception);
|
|
370
|
+
#endif
|
|
371
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns);
|
|
297
372
|
static void otel_without_ddtrace_trace_identifiers_for(
|
|
298
373
|
thread_context_collector_state *state,
|
|
299
374
|
VALUE thread,
|
|
@@ -304,11 +379,17 @@ static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key)
|
|
|
304
379
|
static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
|
|
305
380
|
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
|
|
306
381
|
static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
307
|
-
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self
|
|
382
|
+
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
383
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
384
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
385
|
+
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
386
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample);
|
|
387
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused);
|
|
308
388
|
|
|
309
389
|
void collectors_thread_context_init(VALUE profiling_module) {
|
|
310
390
|
VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
|
|
311
391
|
VALUE collectors_thread_context_class = rb_define_class_under(collectors_module, "ThreadContext", rb_cObject);
|
|
392
|
+
|
|
312
393
|
// Hosts methods used for testing the native code using RSpec
|
|
313
394
|
VALUE testing_module = rb_define_module_under(collectors_thread_context_class, "Testing");
|
|
314
395
|
|
|
@@ -325,7 +406,7 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
325
406
|
rb_define_singleton_method(collectors_thread_context_class, "_native_initialize", _native_initialize, -1);
|
|
326
407
|
rb_define_singleton_method(collectors_thread_context_class, "_native_inspect", _native_inspect, 1);
|
|
327
408
|
rb_define_singleton_method(collectors_thread_context_class, "_native_reset_after_fork", _native_reset_after_fork, 1);
|
|
328
|
-
rb_define_singleton_method(testing_module, "_native_sample", _native_sample,
|
|
409
|
+
rb_define_singleton_method(testing_module, "_native_sample", _native_sample, 2);
|
|
329
410
|
rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
|
|
330
411
|
rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
|
|
331
412
|
rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
|
|
@@ -337,14 +418,18 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
337
418
|
rb_define_singleton_method(testing_module, "_native_new_empty_thread", _native_new_empty_thread, 0);
|
|
338
419
|
rb_define_singleton_method(testing_module, "_native_sample_skipped_allocation_samples", _native_sample_skipped_allocation_samples, 2);
|
|
339
420
|
rb_define_singleton_method(testing_module, "_native_system_epoch_time_now_ns", _native_system_epoch_time_now_ns, 1);
|
|
340
|
-
rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler,
|
|
421
|
+
rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler, 0);
|
|
422
|
+
rb_define_singleton_method(testing_module, "_native_remove_per_thread_context_for", _native_remove_per_thread_context_for, 1);
|
|
423
|
+
rb_define_singleton_method(testing_module, "_native_global_reset_per_thread_context", _native_global_reset_per_thread_context, 1);
|
|
424
|
+
rb_define_singleton_method(testing_module, "_native_mark_thread_as_profiler_internal", _native_mark_thread_as_profiler_internal, 1);
|
|
425
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
426
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_released", _native_on_gvl_released, 1);
|
|
341
427
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
342
|
-
rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
|
|
343
428
|
rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
|
|
344
|
-
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running,
|
|
345
|
-
rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running,
|
|
346
|
-
rb_define_singleton_method(testing_module, "_native_apply_delta_to_cpu_time_at_previous_sample_ns", _native_apply_delta_to_cpu_time_at_previous_sample_ns, 3);
|
|
429
|
+
rb_define_singleton_method(testing_module, "_native_on_gvl_running", _native_on_gvl_running, 2);
|
|
430
|
+
rb_define_singleton_method(testing_module, "_native_sample_after_gvl_running", _native_sample_after_gvl_running, 3);
|
|
347
431
|
#endif
|
|
432
|
+
rb_define_singleton_method(testing_module, "_native_apply_delta_to_cpu_time_at_previous_sample_ns", _native_apply_delta_to_cpu_time_at_previous_sample_ns, 2);
|
|
348
433
|
|
|
349
434
|
at_active_span_id = rb_intern_const("@active_span");
|
|
350
435
|
at_active_trace_id = rb_intern_const("@active_trace");
|
|
@@ -365,10 +450,12 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
365
450
|
otel_context_storage_id = rb_intern_const("__opentelemetry_context_storage__");
|
|
366
451
|
otel_fiber_context_storage_id = rb_intern_const("@opentelemetry_context");
|
|
367
452
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
453
|
+
dd_per_thread_context_id = rb_intern_const("dd_per_thread_context");
|
|
454
|
+
|
|
455
|
+
// This will raise if Ruby already ran out of thread-local keys
|
|
456
|
+
per_thread_context_tls_init();
|
|
457
|
+
|
|
458
|
+
rb_global_variable(&thread_begin_tracepoint);
|
|
372
459
|
|
|
373
460
|
gc_profiling_init();
|
|
374
461
|
}
|
|
@@ -393,10 +480,10 @@ static void thread_context_collector_typed_data_mark(void *state_ptr) {
|
|
|
393
480
|
|
|
394
481
|
// Update this when modifying state struct
|
|
395
482
|
rb_gc_mark(state->recorder_instance);
|
|
396
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_mark, 0 /* unused */);
|
|
397
483
|
rb_gc_mark(state->thread_list_buffer);
|
|
398
484
|
rb_gc_mark(state->main_thread);
|
|
399
485
|
rb_gc_mark(state->otel_current_span_key);
|
|
486
|
+
rb_gc_mark(state->overhead_filename);
|
|
400
487
|
}
|
|
401
488
|
|
|
402
489
|
static void thread_context_collector_typed_data_free(void *state_ptr) {
|
|
@@ -406,36 +493,54 @@ static void thread_context_collector_typed_data_free(void *state_ptr) {
|
|
|
406
493
|
|
|
407
494
|
// Important: Remember that we're only guaranteed to see here what's been set in _native_new, aka
|
|
408
495
|
// pointers that have been set NULL there may still be NULL here.
|
|
409
|
-
if (state->locations != NULL) ruby_xfree(state->locations);
|
|
410
|
-
|
|
411
|
-
// Free each entry in the map
|
|
412
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
|
413
|
-
// ...and then the map
|
|
414
|
-
st_free_table(state->hash_map_per_thread_context);
|
|
496
|
+
if (state->locations.ptr != NULL) ruby_xfree(state->locations.ptr);
|
|
415
497
|
|
|
416
498
|
st_free_table(state->native_filenames_cache);
|
|
417
499
|
|
|
418
500
|
ruby_xfree(state);
|
|
419
501
|
}
|
|
420
502
|
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
503
|
+
// per_thread_context is wrapped in a TypedData Ruby object stored as an ivar on each Ruby Thread.
|
|
504
|
+
// This gives us automatic GC marking (for sampling_buffer iseq VALUEs) and lifecycle management.
|
|
505
|
+
static const rb_data_type_t per_thread_context_typed_data = {
|
|
506
|
+
.wrap_struct_name = "Datadog::Profiling::PerThreadContext",
|
|
507
|
+
.function = {
|
|
508
|
+
.dmark = per_thread_context_typed_data_mark,
|
|
509
|
+
.dfree = per_thread_context_typed_data_free,
|
|
510
|
+
.dsize = NULL,
|
|
511
|
+
},
|
|
512
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
513
|
+
};
|
|
425
514
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
515
|
+
static void per_thread_context_typed_data_mark(void *ctx_ptr) {
|
|
516
|
+
per_thread_context *ctx = (per_thread_context *) ctx_ptr;
|
|
517
|
+
if (sampling_buffer_needs_marking(&ctx->sampling_buffer)) {
|
|
518
|
+
sampling_buffer_mark(&ctx->sampling_buffer);
|
|
429
519
|
}
|
|
520
|
+
}
|
|
430
521
|
|
|
431
|
-
|
|
522
|
+
static void per_thread_context_typed_data_free(void *ctx_ptr) {
|
|
523
|
+
per_thread_context *ctx = (per_thread_context *) ctx_ptr;
|
|
524
|
+
sampling_buffer_free(&ctx->sampling_buffer);
|
|
525
|
+
free(ctx);
|
|
432
526
|
}
|
|
433
527
|
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
528
|
+
// Only for testing: removes the per-thread context without recreating it, so the thread has no context.
|
|
529
|
+
// This simulates situations where we observe a thread running before our RUBY_EVENT_THREAD_BEGIN tracepoint fires,
|
|
530
|
+
// so that we can test our `get_per_thread_context(...) => null` code paths.
|
|
531
|
+
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
532
|
+
check_frozen_thread(thread);
|
|
533
|
+
per_thread_context *ctx = get_per_thread_context(thread);
|
|
534
|
+
if (ctx != NULL) {
|
|
535
|
+
set_per_thread_context(thread, NULL);
|
|
536
|
+
rb_ivar_set(thread, dd_per_thread_context_id, Qnil);
|
|
537
|
+
}
|
|
538
|
+
return Qnil;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
542
|
+
thread_context_collector_reset_all_per_thread_contexts(collector_instance);
|
|
543
|
+
return Qnil;
|
|
439
544
|
}
|
|
440
545
|
|
|
441
546
|
static VALUE _native_new(VALUE klass) {
|
|
@@ -445,17 +550,13 @@ static VALUE _native_new(VALUE klass) {
|
|
|
445
550
|
// being leaked.
|
|
446
551
|
|
|
447
552
|
// Update this when modifying state struct
|
|
448
|
-
state->locations = NULL;
|
|
449
|
-
state->
|
|
450
|
-
state->hash_map_per_thread_context =
|
|
451
|
-
// "numtable" is an awful name, but TL;DR it's what should be used when keys are `VALUE`s.
|
|
452
|
-
st_init_numtable();
|
|
553
|
+
state->locations.ptr = NULL;
|
|
554
|
+
state->locations.len = 0;
|
|
453
555
|
state->recorder_instance = Qnil;
|
|
454
556
|
state->tracer_context_key = MISSING_TRACER_CONTEXT_KEY;
|
|
455
557
|
VALUE thread_list_buffer = rb_ary_new();
|
|
456
558
|
state->thread_list_buffer = thread_list_buffer;
|
|
457
559
|
state->endpoint_collection_enabled = true;
|
|
458
|
-
state->timeline_enabled = true;
|
|
459
560
|
state->native_filenames_enabled = false;
|
|
460
561
|
state->native_filenames_cache = st_init_numtable();
|
|
461
562
|
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
|
|
@@ -489,28 +590,30 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
489
590
|
VALUE max_frames = rb_hash_fetch(options, ID2SYM(rb_intern("max_frames")));
|
|
490
591
|
VALUE tracer_context_key = rb_hash_fetch(options, ID2SYM(rb_intern("tracer_context_key")));
|
|
491
592
|
VALUE endpoint_collection_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("endpoint_collection_enabled")));
|
|
492
|
-
VALUE timeline_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("timeline_enabled")));
|
|
493
593
|
VALUE waiting_for_gvl_threshold_ns = rb_hash_fetch(options, ID2SYM(rb_intern("waiting_for_gvl_threshold_ns")));
|
|
494
594
|
VALUE otel_context_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("otel_context_enabled")));
|
|
495
595
|
VALUE native_filenames_enabled = rb_hash_fetch(options, ID2SYM(rb_intern("native_filenames_enabled")));
|
|
596
|
+
VALUE overhead_filename = rb_hash_fetch(options, ID2SYM(rb_intern("overhead_filename")));
|
|
496
597
|
|
|
497
598
|
ENFORCE_TYPE(max_frames, T_FIXNUM);
|
|
498
599
|
ENFORCE_BOOLEAN(endpoint_collection_enabled);
|
|
499
|
-
ENFORCE_BOOLEAN(timeline_enabled);
|
|
500
600
|
ENFORCE_TYPE(waiting_for_gvl_threshold_ns, T_FIXNUM);
|
|
501
601
|
ENFORCE_BOOLEAN(native_filenames_enabled);
|
|
602
|
+
ENFORCE_TYPE(overhead_filename, T_STRING);
|
|
603
|
+
|
|
604
|
+
uint16_t max_frame_int = sampling_buffer_check_max_frames(NUM2INT(max_frames));
|
|
502
605
|
|
|
503
606
|
thread_context_collector_state *state;
|
|
504
607
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
505
608
|
|
|
506
609
|
// Update this when modifying state struct
|
|
507
|
-
state->
|
|
508
|
-
state->locations = ruby_xcalloc(
|
|
509
|
-
// hash_map_per_thread_context is already initialized, nothing to do here
|
|
610
|
+
state->locations.len = max_frame_int;
|
|
611
|
+
state->locations.ptr = ruby_xcalloc(max_frame_int, sizeof(ddog_prof_Location));
|
|
510
612
|
state->recorder_instance = enforce_recorder_instance(recorder_instance);
|
|
613
|
+
recorder_install_on_serialize(recorder_instance, self_instance);
|
|
511
614
|
state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue);
|
|
512
|
-
state->timeline_enabled = (timeline_enabled == Qtrue);
|
|
513
615
|
state->native_filenames_enabled = (native_filenames_enabled == Qtrue);
|
|
616
|
+
state->overhead_filename = overhead_filename;
|
|
514
617
|
if (otel_context_enabled == Qfalse || otel_context_enabled == Qnil) {
|
|
515
618
|
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_FALSE;
|
|
516
619
|
} else if (otel_context_enabled == ID2SYM(rb_intern("only"))) {
|
|
@@ -518,10 +621,10 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
518
621
|
} else if (otel_context_enabled == ID2SYM(rb_intern("both"))) {
|
|
519
622
|
state->otel_context_enabled = OTEL_CONTEXT_ENABLED_BOTH;
|
|
520
623
|
} else {
|
|
521
|
-
|
|
624
|
+
raise_error(rb_eArgError, "Unexpected value for otel_context_enabled: %+" PRIsVALUE, otel_context_enabled);
|
|
522
625
|
}
|
|
523
626
|
|
|
524
|
-
|
|
627
|
+
state->waiting_for_gvl_threshold_ns = NUM2UINT(waiting_for_gvl_threshold_ns);
|
|
525
628
|
|
|
526
629
|
if (RTEST(tracer_context_key)) {
|
|
527
630
|
ENFORCE_TYPE(tracer_context_key, T_SYMBOL);
|
|
@@ -531,19 +634,22 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
531
634
|
state->tracer_context_key = rb_to_id(tracer_context_key);
|
|
532
635
|
}
|
|
533
636
|
|
|
637
|
+
if (thread_begin_tracepoint == Qnil) {
|
|
638
|
+
thread_begin_tracepoint = rb_tracepoint_new(Qnil, RUBY_EVENT_THREAD_BEGIN, on_thread_begin_event, NULL);
|
|
639
|
+
rb_tracepoint_enable(thread_begin_tracepoint);
|
|
640
|
+
}
|
|
641
|
+
|
|
534
642
|
return Qtrue;
|
|
535
643
|
}
|
|
536
644
|
|
|
537
645
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
538
646
|
// It SHOULD NOT be used for other purposes.
|
|
539
|
-
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE
|
|
647
|
+
static VALUE _native_sample(DDTRACE_UNUSED VALUE _self, VALUE collector_instance, VALUE allow_exception) {
|
|
540
648
|
ENFORCE_BOOLEAN(allow_exception);
|
|
541
649
|
|
|
542
|
-
if (!is_thread_alive(profiler_overhead_stack_thread)) rb_raise(rb_eArgError, "Unexpected: profiler_overhead_stack_thread is not alive");
|
|
543
|
-
|
|
544
650
|
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
545
651
|
|
|
546
|
-
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
652
|
+
thread_context_collector_sample(collector_instance, monotonic_wall_time_now_ns(RAISE_ON_FAILURE));
|
|
547
653
|
|
|
548
654
|
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
|
549
655
|
|
|
@@ -586,6 +692,44 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_
|
|
|
586
692
|
return Qtrue;
|
|
587
693
|
}
|
|
588
694
|
|
|
695
|
+
// Record profiler sampling overhead as a placeholder stack
|
|
696
|
+
static void record_sampling_overhead(thread_context_collector_state *state, per_thread_context *current_thread_context) {
|
|
697
|
+
long wall_time_after_sampling = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
698
|
+
long cpu_time_after_sampling = cpu_time_now_ns(current_thread_context);
|
|
699
|
+
|
|
700
|
+
long overhead_cpu_time_ns = update_cpu_time_since_previous_sample(current_thread_context, cpu_time_after_sampling);
|
|
701
|
+
long overhead_wall_time_ns = update_wall_time_since_previous_sample(current_thread_context, wall_time_after_sampling);
|
|
702
|
+
|
|
703
|
+
ddog_prof_Label overhead_labels[] = {
|
|
704
|
+
{.key = DDOG_CHARSLICE_C("thread id"), .str = DDOG_CHARSLICE_C("0"), .num = 0},
|
|
705
|
+
{.key = DDOG_CHARSLICE_C("thread name"), .str = DDOG_CHARSLICE_C("Datadog::Profiling::Sampling"), .num = 0},
|
|
706
|
+
{.key = DDOG_CHARSLICE_C("state"), .str = DDOG_CHARSLICE_C("had cpu"), .num = 0},
|
|
707
|
+
{.key = DDOG_CHARSLICE_C("profiler overhead"), .num = 1},
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
int64_t end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, wall_time_after_sampling);
|
|
711
|
+
|
|
712
|
+
ddog_prof_Location overhead_location = {
|
|
713
|
+
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
|
714
|
+
.function = {
|
|
715
|
+
.name = DDOG_CHARSLICE_C("sampling"),
|
|
716
|
+
.filename = char_slice_from_ruby_string(state->overhead_filename),
|
|
717
|
+
},
|
|
718
|
+
.line = 0,
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
record_sample(
|
|
722
|
+
state->recorder_instance,
|
|
723
|
+
(ddog_prof_Slice_Location) {.ptr = &overhead_location, .len = 1},
|
|
724
|
+
(sample_values) {.cpu_time_ns = overhead_cpu_time_ns, .cpu_or_wall_samples = 1, .wall_time_ns = overhead_wall_time_ns},
|
|
725
|
+
(sample_labels) {
|
|
726
|
+
.labels = (ddog_prof_Slice_Label) {.ptr = overhead_labels, .len = sizeof(overhead_labels) / sizeof(overhead_labels[0])},
|
|
727
|
+
.state_label = NULL,
|
|
728
|
+
.end_timestamp_ns = end_timestamp_ns,
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
|
|
589
733
|
// This function gets called from the Collectors::CpuAndWallTimeWorker to trigger the actual sampling.
|
|
590
734
|
//
|
|
591
735
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
@@ -594,14 +738,12 @@ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_
|
|
|
594
738
|
// Assumption 4: This function IS NOT called in a reentrant way.
|
|
595
739
|
// Assumption 5: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
596
740
|
//
|
|
597
|
-
|
|
598
|
-
// (belonging to ddtrace), so that the overhead is visible in the profile rather than blamed on user code.
|
|
599
|
-
void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns, VALUE profiler_overhead_stack_thread) {
|
|
741
|
+
void thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns) {
|
|
600
742
|
thread_context_collector_state *state;
|
|
601
743
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
602
744
|
|
|
603
745
|
VALUE current_thread = rb_thread_current();
|
|
604
|
-
per_thread_context *current_thread_context = get_or_create_context_for(current_thread
|
|
746
|
+
per_thread_context *current_thread_context = get_or_create_context_for(current_thread);
|
|
605
747
|
long cpu_time_at_sample_start_for_current_thread = cpu_time_now_ns(current_thread_context);
|
|
606
748
|
|
|
607
749
|
VALUE threads = thread_list(state);
|
|
@@ -609,76 +751,57 @@ void thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
|
609
751
|
const long thread_count = RARRAY_LEN(threads);
|
|
610
752
|
for (long i = 0; i < thread_count; i++) {
|
|
611
753
|
VALUE thread = RARRAY_AREF(threads, i);
|
|
612
|
-
per_thread_context *thread_context = get_or_create_context_for(thread
|
|
754
|
+
per_thread_context *thread_context = get_or_create_context_for(thread);
|
|
613
755
|
|
|
614
|
-
// We account for cpu-time for the current thread in a different way
|
|
615
|
-
// blaming the time the profiler took on whatever
|
|
616
|
-
|
|
756
|
+
// We account for cpu-time for the current thread in a different way: we use the cpu-time at sampling start,
|
|
757
|
+
// to avoid blaming the time the profiler took on whatever is currently running on the thread,
|
|
758
|
+
// and instead we report that time the profiler took as sampling overhead below.
|
|
759
|
+
long current_cpu_time_ns = (thread == current_thread) ? cpu_time_at_sample_start_for_current_thread : cpu_time_now_ns(thread_context);
|
|
617
760
|
|
|
618
761
|
update_metrics_and_sample(
|
|
619
762
|
state,
|
|
620
|
-
|
|
621
|
-
/* stack_from_thread: */ thread,
|
|
763
|
+
thread,
|
|
622
764
|
thread_context,
|
|
623
|
-
&thread_context->sampling_buffer,
|
|
624
765
|
current_cpu_time_ns,
|
|
625
|
-
current_monotonic_wall_time_ns
|
|
626
|
-
|
|
766
|
+
current_monotonic_wall_time_ns,
|
|
767
|
+
false);
|
|
627
768
|
}
|
|
628
769
|
|
|
629
|
-
state->sample_count++;
|
|
770
|
+
state->stats.sample_count++;
|
|
630
771
|
|
|
631
|
-
//
|
|
632
|
-
//
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
// Here we use the overhead thread's sampling buffer so as to not invalidate the cache in the buffer of the thread being sampled
|
|
641
|
-
&get_or_create_context_for(profiler_overhead_stack_thread, state)->sampling_buffer,
|
|
642
|
-
cpu_time_now_ns(current_thread_context),
|
|
643
|
-
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
644
|
-
);
|
|
772
|
+
// If the current thread is a profiler-internal thread, we don't use record_sampling_overhead()
|
|
773
|
+
// and accept the sampling overhead is attributed to the profiler-internal thread
|
|
774
|
+
// (both are under the same datadog group in the flamegraph).
|
|
775
|
+
// The reason we need to do this is profiler-internal threads are skipped during per-tick sampling
|
|
776
|
+
// so their timestamps are not updated. record_sampling_overhead() would see the stale previous-sample timestamps and
|
|
777
|
+
// attribute the entire sleep interval incorrectly as overhead instead of just the sampling work.
|
|
778
|
+
if (!current_thread_context->is_profiler_internal_thread) {
|
|
779
|
+
record_sampling_overhead(state, current_thread_context);
|
|
780
|
+
}
|
|
645
781
|
}
|
|
646
782
|
|
|
647
783
|
static void update_metrics_and_sample(
|
|
648
784
|
thread_context_collector_state *state,
|
|
649
785
|
VALUE thread_being_sampled,
|
|
650
|
-
VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
|
|
651
786
|
per_thread_context *thread_context,
|
|
652
|
-
sampling_buffer* sampling_buffer,
|
|
653
787
|
long current_cpu_time_ns,
|
|
654
|
-
long current_monotonic_wall_time_ns
|
|
788
|
+
long current_monotonic_wall_time_ns,
|
|
789
|
+
bool force_sample
|
|
655
790
|
) {
|
|
656
791
|
bool is_gvl_waiting_state =
|
|
657
|
-
handle_gvl_waiting(state, thread_being_sampled,
|
|
792
|
+
handle_gvl_waiting(state, thread_being_sampled, thread_context, current_cpu_time_ns);
|
|
793
|
+
|
|
794
|
+
if (skip_sample(state, thread_context, is_gvl_waiting_state, force_sample)) return;
|
|
658
795
|
|
|
659
796
|
// Don't assign/update cpu during "Waiting for GVL"
|
|
660
|
-
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 :
|
|
661
|
-
&thread_context->cpu_time_at_previous_sample_ns,
|
|
662
|
-
current_cpu_time_ns,
|
|
663
|
-
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
664
|
-
IS_NOT_WALL_TIME
|
|
665
|
-
);
|
|
797
|
+
long cpu_time_elapsed_ns = is_gvl_waiting_state ? 0 : update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
666
798
|
|
|
667
|
-
long wall_time_elapsed_ns =
|
|
668
|
-
&thread_context->wall_time_at_previous_sample_ns,
|
|
669
|
-
current_monotonic_wall_time_ns,
|
|
670
|
-
// We explicitly pass in `INVALID_TIME` as an argument for `gc_start_time_ns` here because we don't want wall-time
|
|
671
|
-
// accounting to change during GC.
|
|
672
|
-
// E.g. if 60 seconds pass in the real world, 60 seconds of wall-time are recorded, regardless of the thread doing
|
|
673
|
-
// GC or not.
|
|
674
|
-
INVALID_TIME,
|
|
675
|
-
IS_WALL_TIME
|
|
676
|
-
);
|
|
799
|
+
long wall_time_elapsed_ns = update_wall_time_since_previous_sample(thread_context, current_monotonic_wall_time_ns);
|
|
677
800
|
|
|
678
801
|
// A thread enters "Waiting for GVL", well, as the name implies, without the GVL.
|
|
679
802
|
//
|
|
680
803
|
// As a consequence, it's possible that a thread enters "Waiting for GVL" in parallel with the current thread working
|
|
681
|
-
// on sampling, and thus for the
|
|
804
|
+
// on sampling, and thus for the `current_monotonic_wall_time_ns` (which is recorded at the start of sampling)
|
|
682
805
|
// to be < the time at which we started Waiting for GVL.
|
|
683
806
|
//
|
|
684
807
|
// All together, this means that when `handle_gvl_waiting` creates an extra sample (see comments on that function for
|
|
@@ -693,9 +816,7 @@ static void update_metrics_and_sample(
|
|
|
693
816
|
trigger_sample_for_thread(
|
|
694
817
|
state,
|
|
695
818
|
thread_being_sampled,
|
|
696
|
-
stack_from_thread,
|
|
697
819
|
thread_context,
|
|
698
|
-
sampling_buffer,
|
|
699
820
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = wall_time_elapsed_ns},
|
|
700
821
|
current_monotonic_wall_time_ns,
|
|
701
822
|
NULL,
|
|
@@ -705,6 +826,37 @@ static void update_metrics_and_sample(
|
|
|
705
826
|
);
|
|
706
827
|
}
|
|
707
828
|
|
|
829
|
+
static bool skip_sample(thread_context_collector_state *state, per_thread_context *thread_context, bool is_gvl_waiting_state, bool force_sample) {
|
|
830
|
+
if (!force_sample && thread_context->is_profiler_internal_thread) {
|
|
831
|
+
state->stats.profiler_thread_samples_skipped++;
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Racy read but harmless, can only cause an extra sample
|
|
836
|
+
uint64_t gvl_state_change_count = thread_context->gvl_state_change_count;
|
|
837
|
+
|
|
838
|
+
// Skip this per-tick sample entirely when the thread does not have the GVL and did not acquire
|
|
839
|
+
// it since the previous sample: its Ruby-level stack has not changed. The skipped wall-time will
|
|
840
|
+
// be picked up by either by an extra sample when the thread acquires the GVL, or by
|
|
841
|
+
// the on-serialize flush in the stack recorder (using was_skipped_at_last_sample).
|
|
842
|
+
// The check is gated by `!is_gvl_waiting_state` so the existing "Waiting for GVL" machinery
|
|
843
|
+
// in handle_gvl_waiting (situation 1 extra sample, situation 2 regular sample) keeps running.
|
|
844
|
+
// TODO: we could probably also skip while "Waiting for GVL"
|
|
845
|
+
if (!is_gvl_waiting_state &&
|
|
846
|
+
!force_sample &&
|
|
847
|
+
(gvl_state_change_count & GVL_SUSPENDED) &&
|
|
848
|
+
gvl_state_change_count == thread_context->gvl_state_change_count_at_previous_sample) {
|
|
849
|
+
state->stats.inactive_thread_samples_skipped++;
|
|
850
|
+
thread_context->was_skipped_at_last_sample = true;
|
|
851
|
+
return true; // Do NOT update wall_time_at_previous_sample_ns or cpu_time_at_previous_sample_ns
|
|
852
|
+
} else {
|
|
853
|
+
// We are going to sample, update the state accordingly:
|
|
854
|
+
thread_context->gvl_state_change_count_at_previous_sample = gvl_state_change_count;
|
|
855
|
+
thread_context->was_skipped_at_last_sample = false;
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
708
860
|
// This function gets called when Ruby is about to start running the Garbage Collector on the current thread.
|
|
709
861
|
// It updates the per_thread_context of the current thread to include the current cpu/wall times, to be used to later
|
|
710
862
|
// create an event including the cpu/wall time spent in garbage collector work.
|
|
@@ -718,15 +870,13 @@ static void update_metrics_and_sample(
|
|
|
718
870
|
void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
719
871
|
thread_context_collector_state *state;
|
|
720
872
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return;
|
|
721
|
-
// This should never fail
|
|
873
|
+
// This should never fail when the above check passes
|
|
722
874
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
723
875
|
|
|
724
|
-
per_thread_context *thread_context =
|
|
876
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
725
877
|
|
|
726
|
-
//
|
|
727
|
-
//
|
|
728
|
-
// GC" is a rare enough case that we can just ignore it.
|
|
729
|
-
// We can always improve this later if we find that this happens often (and we have the counter to help us figure that out)!
|
|
878
|
+
// Context is created eagerly via on_thread_begin_event, so this should not normally be NULL.
|
|
879
|
+
// We can't get_or_create_context_for() here since we can't allocate (GC context).
|
|
730
880
|
if (thread_context == NULL) {
|
|
731
881
|
state->stats.gc_samples_missed_due_to_missing_context++;
|
|
732
882
|
return;
|
|
@@ -751,13 +901,12 @@ __attribute__((warn_unused_result))
|
|
|
751
901
|
bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
752
902
|
thread_context_collector_state *state;
|
|
753
903
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
|
754
|
-
// This should never fail
|
|
904
|
+
// This should never fail when the above check passes
|
|
755
905
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
756
906
|
|
|
757
|
-
per_thread_context *thread_context =
|
|
907
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
758
908
|
|
|
759
|
-
//
|
|
760
|
-
// how often this happens -- see on_gc_start.
|
|
909
|
+
// Context is created eagerly, so this should not normally be NULL (see on_gc_start).
|
|
761
910
|
if (thread_context == NULL) return false;
|
|
762
911
|
|
|
763
912
|
long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
|
|
@@ -798,9 +947,9 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
798
947
|
state->gc_tracking.accumulated_wall_time_ns += gc_wall_time_elapsed_ns;
|
|
799
948
|
state->gc_tracking.wall_time_at_previous_gc_ns = wall_time_at_finish_ns;
|
|
800
949
|
|
|
801
|
-
// Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample
|
|
802
|
-
// We don't
|
|
803
|
-
//
|
|
950
|
+
// Update cpu-time accounting so it doesn't include the cpu-time spent in GC during the next sample.
|
|
951
|
+
// We don't do the same for wall-time, because GC is just like any other reason a thread didn't make
|
|
952
|
+
// progress -- time always goes forward regardless of the thread making progress on what it wanted.
|
|
804
953
|
if (thread_context->cpu_time_at_previous_sample_ns != INVALID_TIME) {
|
|
805
954
|
thread_context->cpu_time_at_previous_sample_ns += gc_cpu_time_elapsed_ns;
|
|
806
955
|
}
|
|
@@ -831,7 +980,7 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
831
980
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
832
981
|
|
|
833
982
|
if (state->gc_tracking.wall_time_at_previous_gc_ns == INVALID_TIME) {
|
|
834
|
-
|
|
983
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected call to sample_after_gc without valid GC information available");
|
|
835
984
|
}
|
|
836
985
|
|
|
837
986
|
int max_labels_needed_for_gc = 7; // Magic number gets validated inside gc_profiling_set_metadata
|
|
@@ -841,11 +990,7 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
841
990
|
ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = label_pos};
|
|
842
991
|
|
|
843
992
|
// The end_timestamp_ns is treated specially by libdatadog and that's why it's not added as a ddog_prof_Label
|
|
844
|
-
int64_t end_timestamp_ns =
|
|
845
|
-
|
|
846
|
-
if (state->timeline_enabled) {
|
|
847
|
-
end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, state->gc_tracking.wall_time_at_previous_gc_ns);
|
|
848
|
-
}
|
|
993
|
+
int64_t end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, state->gc_tracking.wall_time_at_previous_gc_ns);
|
|
849
994
|
|
|
850
995
|
record_placeholder_stack(
|
|
851
996
|
state->recorder_instance,
|
|
@@ -878,10 +1023,8 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
|
878
1023
|
|
|
879
1024
|
static void trigger_sample_for_thread(
|
|
880
1025
|
thread_context_collector_state *state,
|
|
881
|
-
VALUE
|
|
882
|
-
VALUE stack_from_thread, // This can be different when attributing profiler overhead using a different stack
|
|
1026
|
+
VALUE thread_being_sampled,
|
|
883
1027
|
per_thread_context *thread_context,
|
|
884
|
-
sampling_buffer* sampling_buffer,
|
|
885
1028
|
sample_values values,
|
|
886
1029
|
long current_monotonic_wall_time_ns,
|
|
887
1030
|
// These two labels are only used for allocation profiling; @ivoanjo: may want to refactor this at some point?
|
|
@@ -895,7 +1038,6 @@ static void trigger_sample_for_thread(
|
|
|
895
1038
|
int max_label_count =
|
|
896
1039
|
1 + // thread id
|
|
897
1040
|
1 + // thread name
|
|
898
|
-
1 + // profiler overhead
|
|
899
1041
|
2 + // ruby vm type and allocation class
|
|
900
1042
|
1 + // state (only set for cpu/wall-time samples)
|
|
901
1043
|
2; // local root span id and span id
|
|
@@ -907,13 +1049,13 @@ static void trigger_sample_for_thread(
|
|
|
907
1049
|
.str = thread_context->thread_id_char_slice
|
|
908
1050
|
};
|
|
909
1051
|
|
|
910
|
-
VALUE thread_name = thread_name_for(
|
|
1052
|
+
VALUE thread_name = thread_name_for(thread_being_sampled);
|
|
911
1053
|
if (thread_name != Qnil) {
|
|
912
1054
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
913
1055
|
.key = DDOG_CHARSLICE_C("thread name"),
|
|
914
1056
|
.str = char_slice_from_ruby_string(thread_name)
|
|
915
1057
|
};
|
|
916
|
-
} else if (
|
|
1058
|
+
} else if (thread_being_sampled == state->main_thread) { // Threads are often not named, but we can have a nice fallback for this special thread
|
|
917
1059
|
ddog_CharSlice main_thread_name = DDOG_CHARSLICE_C("main");
|
|
918
1060
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
919
1061
|
.key = DDOG_CHARSLICE_C("thread name"),
|
|
@@ -929,11 +1071,11 @@ static void trigger_sample_for_thread(
|
|
|
929
1071
|
}
|
|
930
1072
|
|
|
931
1073
|
trace_identifiers trace_identifiers_result = {.valid = false, .trace_endpoint = Qnil};
|
|
932
|
-
trace_identifiers_for(state,
|
|
1074
|
+
trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result, is_safe_to_allocate_objects);
|
|
933
1075
|
|
|
934
1076
|
if (!trace_identifiers_result.valid && state->otel_context_enabled != OTEL_CONTEXT_ENABLED_FALSE) {
|
|
935
1077
|
// If we couldn't get something with ddtrace, let's see if we can get some trace identifiers from opentelemetry directly
|
|
936
|
-
otel_without_ddtrace_trace_identifiers_for(state,
|
|
1078
|
+
otel_without_ddtrace_trace_identifiers_for(state, thread_being_sampled, &trace_identifiers_result, is_safe_to_allocate_objects);
|
|
937
1079
|
}
|
|
938
1080
|
|
|
939
1081
|
if (trace_identifiers_result.valid) {
|
|
@@ -958,13 +1100,6 @@ static void trigger_sample_for_thread(
|
|
|
958
1100
|
}
|
|
959
1101
|
}
|
|
960
1102
|
|
|
961
|
-
if (thread != stack_from_thread) {
|
|
962
|
-
labels[label_pos++] = (ddog_prof_Label) {
|
|
963
|
-
.key = DDOG_CHARSLICE_C("profiler overhead"),
|
|
964
|
-
.num = 1
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
|
|
968
1103
|
if (ruby_vm_type != NULL) {
|
|
969
1104
|
labels[label_pos++] = (ddog_prof_Label) {
|
|
970
1105
|
.key = DDOG_CHARSLICE_C("ruby vm type"),
|
|
@@ -998,20 +1133,21 @@ static void trigger_sample_for_thread(
|
|
|
998
1133
|
// @ivoanjo: I wonder if C compilers are smart enough to statically prove this check never triggers unless someone
|
|
999
1134
|
// changes the code erroneously and remove it entirely?
|
|
1000
1135
|
if (label_pos > max_label_count) {
|
|
1001
|
-
|
|
1136
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected label_pos (%d) > max_label_count (%d)", label_pos, max_label_count);
|
|
1002
1137
|
}
|
|
1003
1138
|
|
|
1004
1139
|
ddog_prof_Slice_Label slice_labels = {.ptr = labels, .len = label_pos};
|
|
1005
1140
|
|
|
1006
1141
|
// The end_timestamp_ns is treated specially by libdatadog and that's why it's not added as a ddog_prof_Label
|
|
1007
1142
|
int64_t end_timestamp_ns = 0;
|
|
1008
|
-
if (
|
|
1143
|
+
if (current_monotonic_wall_time_ns != INVALID_TIME) {
|
|
1009
1144
|
end_timestamp_ns = monotonic_to_system_epoch_ns(&state->time_converter_state, current_monotonic_wall_time_ns);
|
|
1010
1145
|
}
|
|
1011
1146
|
|
|
1012
1147
|
sample_thread(
|
|
1013
|
-
|
|
1014
|
-
sampling_buffer,
|
|
1148
|
+
thread_being_sampled,
|
|
1149
|
+
&thread_context->sampling_buffer,
|
|
1150
|
+
state->locations,
|
|
1015
1151
|
state->recorder_instance,
|
|
1016
1152
|
values,
|
|
1017
1153
|
(sample_labels) {
|
|
@@ -1039,32 +1175,38 @@ static VALUE _native_thread_list(DDTRACE_UNUSED VALUE _self) {
|
|
|
1039
1175
|
return result;
|
|
1040
1176
|
}
|
|
1041
1177
|
|
|
1042
|
-
static
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
if (st_lookup(state->hash_map_per_thread_context, (st_data_t) thread, &value_context)) {
|
|
1047
|
-
thread_context = (per_thread_context*) value_context;
|
|
1048
|
-
} else {
|
|
1049
|
-
thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
|
|
1050
|
-
initialize_context(thread, thread_context, state);
|
|
1051
|
-
st_insert(state->hash_map_per_thread_context, (st_data_t) thread, (st_data_t) thread_context);
|
|
1178
|
+
static void check_frozen_thread(VALUE thread) {
|
|
1179
|
+
if (RB_OBJ_FROZEN(thread)) {
|
|
1180
|
+
raise_error(rb_eFrozenError, "Cannot setup profiler state for Thread %"PRIsVALUE" because it is frozen. Please avoid freezing Thread instances and/or report the issue to dd-trace-rb", thread);
|
|
1052
1181
|
}
|
|
1053
|
-
|
|
1054
|
-
return thread_context;
|
|
1055
1182
|
}
|
|
1056
1183
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1184
|
+
// See the docs on struct per_thread_context.
|
|
1185
|
+
// This allocates a Ruby object and therefore needs the GVL and is not safe to call from RUBY_INTERNAL_EVENT_* hooks.
|
|
1186
|
+
static per_thread_context *get_or_create_context_for(VALUE thread) {
|
|
1187
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1188
|
+
if (thread_context != NULL) return thread_context;
|
|
1060
1189
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1190
|
+
check_frozen_thread(thread);
|
|
1191
|
+
|
|
1192
|
+
thread_context = calloc(1, sizeof(per_thread_context)); // See "note on calloc vs ruby_xcalloc use" in heap_recorder.c
|
|
1193
|
+
initialize_context(thread, thread_context);
|
|
1194
|
+
|
|
1195
|
+
VALUE wrapper = TypedData_Wrap_Struct(rb_cObject, &per_thread_context_typed_data, thread_context);
|
|
1196
|
+
rb_ivar_set(thread, dd_per_thread_context_id, wrapper);
|
|
1064
1197
|
|
|
1198
|
+
set_per_thread_context(thread, thread_context);
|
|
1065
1199
|
return thread_context;
|
|
1066
1200
|
}
|
|
1067
1201
|
|
|
1202
|
+
static void on_thread_begin_event(VALUE tracepoint_data, DDTRACE_UNUSED void *unused) {
|
|
1203
|
+
if (!ddtrace_rb_ractor_main_p()) return;
|
|
1204
|
+
|
|
1205
|
+
VALUE thread = rb_tracearg_self(rb_tracearg_from_tracepoint(tracepoint_data));
|
|
1206
|
+
ENFORCE_THREAD(thread);
|
|
1207
|
+
get_or_create_context_for(thread);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1068
1210
|
#define LOGGING_GEM_PATH "/lib/logging/diagnostic_context.rb"
|
|
1069
1211
|
|
|
1070
1212
|
// The `logging` gem monkey patches thread creation, which makes the `invoke_location_for` useless, since every thread
|
|
@@ -1086,8 +1228,11 @@ static bool is_logging_gem_monkey_patch(VALUE invoke_file_location) {
|
|
|
1086
1228
|
return strncmp(invoke_file + invoke_file_len - logging_gem_path_len, LOGGING_GEM_PATH, logging_gem_path_len) == 0;
|
|
1087
1229
|
}
|
|
1088
1230
|
|
|
1089
|
-
static void initialize_context(VALUE thread, per_thread_context *thread_context
|
|
1090
|
-
|
|
1231
|
+
static void initialize_context(VALUE thread, per_thread_context *thread_context) {
|
|
1232
|
+
// We always create per_thread_context's with latest_max_frames; that value is kept in sync with the
|
|
1233
|
+
// active profiler's max_frames by the global reset that runs when profiling starts
|
|
1234
|
+
// so we expect to always see here the latest correct value to be used.
|
|
1235
|
+
sampling_buffer_initialize(&thread_context->sampling_buffer, latest_max_frames);
|
|
1091
1236
|
|
|
1092
1237
|
snprintf(thread_context->thread_id, THREAD_ID_LIMIT_CHARS, "%"PRIu64" (%lu)", native_thread_id_for(thread), (unsigned long) thread_id_for(thread));
|
|
1093
1238
|
thread_context->thread_id_char_slice = (ddog_CharSlice) {.ptr = thread_context->thread_id, .len = strlen(thread_context->thread_id)};
|
|
@@ -1106,7 +1251,7 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
|
1106
1251
|
} else {
|
|
1107
1252
|
snprintf(thread_context->thread_invoke_location, THREAD_INVOKE_LOCATION_LIMIT_CHARS, "%s", "(Unnamed thread)");
|
|
1108
1253
|
}
|
|
1109
|
-
} else if (thread !=
|
|
1254
|
+
} else if (thread != rb_thread_main()) {
|
|
1110
1255
|
// If the first function of a thread is native code, there won't be an invoke location, so we use this fallback.
|
|
1111
1256
|
// NOTE: In the future, I wonder if we could take the pointer to the native function, and try to see if there's a native
|
|
1112
1257
|
// symbol attached to it.
|
|
@@ -1120,32 +1265,54 @@ static void initialize_context(VALUE thread, per_thread_context *thread_context,
|
|
|
1120
1265
|
|
|
1121
1266
|
thread_context->thread_cpu_time_id = thread_cpu_time_id_for(thread);
|
|
1122
1267
|
|
|
1123
|
-
|
|
1124
|
-
thread_context->cpu_time_at_previous_sample_ns =
|
|
1125
|
-
thread_context->wall_time_at_previous_sample_ns = INVALID_TIME;
|
|
1268
|
+
thread_context->wall_time_at_previous_sample_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
1269
|
+
thread_context->cpu_time_at_previous_sample_ns = cpu_time_now_ns(thread_context);
|
|
1126
1270
|
|
|
1127
1271
|
// These will only be used during a GC operation
|
|
1128
1272
|
thread_context->gc_tracking.cpu_time_at_start_ns = INVALID_TIME;
|
|
1129
1273
|
thread_context->gc_tracking.wall_time_at_start_ns = INVALID_TIME;
|
|
1130
1274
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
// kind of synchronization (e.g. by threads without the GVL).
|
|
1134
|
-
//
|
|
1135
|
-
// We set this marker here for two purposes:
|
|
1136
|
-
// * To make sure there's no stale data from a previous execution of the profiler.
|
|
1137
|
-
// * To mark threads that are actually being profiled
|
|
1138
|
-
//
|
|
1139
|
-
// (Setting this is potentially a race, but what we want is to avoid _stale_ data, so
|
|
1140
|
-
// if this gets set concurrently with context initialization, then such a value will belong
|
|
1141
|
-
// to the current profiler instance, so that's OK)
|
|
1142
|
-
gvl_profiling_state_thread_object_set(thread, GVL_WAITING_ENABLED_EMPTY);
|
|
1143
|
-
#endif
|
|
1275
|
+
thread_context->gvl_waiting_at = 0;
|
|
1276
|
+
thread_context->gvl_state_change_count = 0;
|
|
1144
1277
|
}
|
|
1145
1278
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1279
|
+
// This MUST be called before profiling starts, so that a new profiler session starts from a fresh state and never
|
|
1280
|
+
// observes or includes any leftover stale state from a previous session.
|
|
1281
|
+
// Such a call MUST happen while the CpuAndWallTimeWorker is stopped (e.g. no tracepoints active, no signals
|
|
1282
|
+
// triggering samples, no gvl hooks, etc).
|
|
1283
|
+
//
|
|
1284
|
+
// It updates the global `latest_max_frames` from the given (latest) ThreadContext and (re)creates every per-thread
|
|
1285
|
+
// context's sampling buffer sized accordingly, so the buffers always match the collector that's about to start
|
|
1286
|
+
// sampling -- even if a previous session used a different max_frames.
|
|
1287
|
+
void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance) {
|
|
1288
|
+
thread_context_collector_state *state;
|
|
1289
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1290
|
+
|
|
1291
|
+
// Update global max frames to be used when allocating sampling buffers
|
|
1292
|
+
if (state->locations.len == 0) {
|
|
1293
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected locations.len == 0. Is this ThreadContext not initialized?");
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
latest_max_frames = state->locations.len;
|
|
1297
|
+
|
|
1298
|
+
VALUE threads = thread_list(state);
|
|
1299
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
1300
|
+
for (long i = 0; i < thread_count; i++) {
|
|
1301
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
1302
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1303
|
+
if (thread_context != NULL) {
|
|
1304
|
+
bool is_profiler_internal_thread = thread_context->is_profiler_internal_thread;
|
|
1305
|
+
|
|
1306
|
+
sampling_buffer_free(&thread_context->sampling_buffer);
|
|
1307
|
+
memset(thread_context, 0, sizeof(per_thread_context));
|
|
1308
|
+
initialize_context(thread, thread_context);
|
|
1309
|
+
|
|
1310
|
+
thread_context->is_profiler_internal_thread = is_profiler_internal_thread;
|
|
1311
|
+
} else {
|
|
1312
|
+
// If thread didn't have a context, let's trigger its creation
|
|
1313
|
+
get_or_create_context_for(thread);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1149
1316
|
}
|
|
1150
1317
|
|
|
1151
1318
|
static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
@@ -1155,15 +1322,12 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
|
1155
1322
|
VALUE result = rb_str_new2(" (native state)");
|
|
1156
1323
|
|
|
1157
1324
|
// Update this when modifying state struct
|
|
1158
|
-
rb_str_concat(result, rb_sprintf(" max_frames=%d", state->
|
|
1159
|
-
rb_str_concat(result, rb_sprintf(" hash_map_per_thread_context=%"PRIsVALUE, per_thread_context_st_table_as_ruby_hash(state)));
|
|
1325
|
+
rb_str_concat(result, rb_sprintf(" max_frames=%d", state->locations.len));
|
|
1160
1326
|
rb_str_concat(result, rb_sprintf(" recorder_instance=%"PRIsVALUE, state->recorder_instance));
|
|
1161
1327
|
VALUE tracer_context_key = state->tracer_context_key == MISSING_TRACER_CONTEXT_KEY ? Qnil : ID2SYM(state->tracer_context_key);
|
|
1162
1328
|
rb_str_concat(result, rb_sprintf(" tracer_context_key=%+"PRIsVALUE, tracer_context_key));
|
|
1163
|
-
rb_str_concat(result, rb_sprintf("
|
|
1164
|
-
rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_as_ruby_hash(state)));
|
|
1329
|
+
rb_str_concat(result, rb_sprintf(" stats=%"PRIsVALUE, stats_to_ruby_hash(state, rb_hash_new())));
|
|
1165
1330
|
rb_str_concat(result, rb_sprintf(" endpoint_collection_enabled=%"PRIsVALUE, state->endpoint_collection_enabled ? Qtrue : Qfalse));
|
|
1166
|
-
rb_str_concat(result, rb_sprintf(" timeline_enabled=%"PRIsVALUE, state->timeline_enabled ? Qtrue : Qfalse));
|
|
1167
1331
|
rb_str_concat(result, rb_sprintf(" native_filenames_enabled=%"PRIsVALUE, state->native_filenames_enabled ? Qtrue : Qfalse));
|
|
1168
1332
|
// Note: `st_table_size()` is available from Ruby 3.2+ but not before
|
|
1169
1333
|
rb_str_concat(result, rb_sprintf(" native_filenames_cache_size=%zu", state->native_filenames_cache->num_entries));
|
|
@@ -1176,53 +1340,51 @@ static VALUE _native_inspect(DDTRACE_UNUSED VALUE _self, VALUE collector_instanc
|
|
|
1176
1340
|
rb_str_concat(result, rb_sprintf(" main_thread=%"PRIsVALUE, state->main_thread));
|
|
1177
1341
|
rb_str_concat(result, rb_sprintf(" gc_tracking=%"PRIsVALUE, gc_tracking_as_ruby_hash(state)));
|
|
1178
1342
|
rb_str_concat(result, rb_sprintf(" otel_current_span_key=%"PRIsVALUE, state->otel_current_span_key));
|
|
1179
|
-
rb_str_concat(result, rb_sprintf("
|
|
1343
|
+
rb_str_concat(result, rb_sprintf(" waiting_for_gvl_threshold_ns=%u", state->waiting_for_gvl_threshold_ns));
|
|
1180
1344
|
|
|
1181
1345
|
return result;
|
|
1182
1346
|
}
|
|
1183
1347
|
|
|
1184
|
-
static VALUE
|
|
1185
|
-
VALUE result = rb_hash_new();
|
|
1186
|
-
st_foreach(state->hash_map_per_thread_context, per_thread_context_as_ruby_hash, result);
|
|
1187
|
-
return result;
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
static int per_thread_context_as_ruby_hash(st_data_t key_thread, st_data_t value_context, st_data_t result_hash) {
|
|
1191
|
-
VALUE thread = (VALUE) key_thread;
|
|
1192
|
-
per_thread_context *thread_context = (per_thread_context*) value_context;
|
|
1193
|
-
VALUE result = (VALUE) result_hash;
|
|
1348
|
+
static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context) {
|
|
1194
1349
|
VALUE context_as_hash = rb_hash_new();
|
|
1195
|
-
rb_hash_aset(result, thread, context_as_hash);
|
|
1196
1350
|
|
|
1197
1351
|
VALUE arguments[] = {
|
|
1198
1352
|
ID2SYM(rb_intern("thread_id")), /* => */ rb_str_new2(thread_context->thread_id),
|
|
1199
1353
|
ID2SYM(rb_intern("thread_invoke_location")), /* => */ rb_str_new2(thread_context->thread_invoke_location),
|
|
1200
1354
|
ID2SYM(rb_intern("thread_cpu_time_id_valid?")), /* => */ thread_context->thread_cpu_time_id.valid ? Qtrue : Qfalse,
|
|
1201
|
-
|
|
1355
|
+
#ifdef __APPLE__
|
|
1356
|
+
ID2SYM(rb_intern("thread_cpu_time_id")), /* => */ ULL2NUM(thread_context->thread_cpu_time_id.clock_id),
|
|
1357
|
+
#else
|
|
1358
|
+
ID2SYM(rb_intern("thread_cpu_time_id")), /* => */ CLOCKID2NUM(thread_context->thread_cpu_time_id.clock_id),
|
|
1359
|
+
#endif
|
|
1202
1360
|
ID2SYM(rb_intern("cpu_time_at_previous_sample_ns")), /* => */ LONG2NUM(thread_context->cpu_time_at_previous_sample_ns),
|
|
1203
1361
|
ID2SYM(rb_intern("wall_time_at_previous_sample_ns")), /* => */ LONG2NUM(thread_context->wall_time_at_previous_sample_ns),
|
|
1204
1362
|
|
|
1205
1363
|
ID2SYM(rb_intern("gc_tracking.cpu_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.cpu_time_at_start_ns),
|
|
1206
1364
|
ID2SYM(rb_intern("gc_tracking.wall_time_at_start_ns")), /* => */ LONG2NUM(thread_context->gc_tracking.wall_time_at_start_ns),
|
|
1207
1365
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1366
|
+
ID2SYM(rb_intern("gvl_waiting_at")), /* => */ LONG2NUM(thread_context->gvl_waiting_at),
|
|
1367
|
+
ID2SYM(rb_intern("gvl_state_change_count")), /* => */ ULL2NUM(thread_context->gvl_state_change_count),
|
|
1368
|
+
ID2SYM(rb_intern("gvl_state_change_count_at_previous_sample")), /* => */ ULL2NUM(thread_context->gvl_state_change_count_at_previous_sample),
|
|
1369
|
+
ID2SYM(rb_intern("was_skipped_at_last_sample")), /* => */ thread_context->was_skipped_at_last_sample ? Qtrue : Qfalse,
|
|
1370
|
+
ID2SYM(rb_intern("is_profiler_internal_thread")), /* => */ thread_context->is_profiler_internal_thread ? Qtrue : Qfalse,
|
|
1211
1371
|
};
|
|
1212
1372
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(context_as_hash, arguments[i], arguments[i+1]);
|
|
1213
1373
|
|
|
1214
|
-
return
|
|
1374
|
+
return context_as_hash;
|
|
1215
1375
|
}
|
|
1216
1376
|
|
|
1217
|
-
static VALUE
|
|
1377
|
+
static VALUE stats_to_ruby_hash(thread_context_collector_state *state, VALUE hash) {
|
|
1218
1378
|
// Update this when modifying state struct (stats inner struct)
|
|
1219
|
-
VALUE stats_as_hash = rb_hash_new();
|
|
1220
1379
|
VALUE arguments[] = {
|
|
1380
|
+
ID2SYM(rb_intern("sample_count")), /* => */ UINT2NUM(state->stats.sample_count),
|
|
1221
1381
|
ID2SYM(rb_intern("gc_samples")), /* => */ UINT2NUM(state->stats.gc_samples),
|
|
1222
1382
|
ID2SYM(rb_intern("gc_samples_missed_due_to_missing_context")), /* => */ UINT2NUM(state->stats.gc_samples_missed_due_to_missing_context),
|
|
1383
|
+
ID2SYM(rb_intern("inactive_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.inactive_thread_samples_skipped),
|
|
1384
|
+
ID2SYM(rb_intern("profiler_thread_samples_skipped")), /* => */ UINT2NUM(state->stats.profiler_thread_samples_skipped),
|
|
1223
1385
|
};
|
|
1224
|
-
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(
|
|
1225
|
-
return
|
|
1386
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
|
|
1387
|
+
return hash;
|
|
1226
1388
|
}
|
|
1227
1389
|
|
|
1228
1390
|
static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state) {
|
|
@@ -1238,70 +1400,80 @@ static VALUE gc_tracking_as_ruby_hash(thread_context_collector_state *state) {
|
|
|
1238
1400
|
return result;
|
|
1239
1401
|
}
|
|
1240
1402
|
|
|
1241
|
-
static void remove_context_for_dead_threads(thread_context_collector_state *state) {
|
|
1242
|
-
st_foreach(state->hash_map_per_thread_context, remove_if_dead_thread, 0 /* unused */);
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
static int remove_if_dead_thread(st_data_t key_thread, st_data_t value_context, DDTRACE_UNUSED st_data_t _argument) {
|
|
1246
|
-
VALUE thread = (VALUE) key_thread;
|
|
1247
|
-
per_thread_context* thread_context = (per_thread_context*) value_context;
|
|
1248
|
-
|
|
1249
|
-
if (is_thread_alive(thread)) return ST_CONTINUE;
|
|
1250
|
-
|
|
1251
|
-
free_context(thread_context);
|
|
1252
|
-
return ST_DELETE;
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
1403
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1256
1404
|
// It SHOULD NOT be used for other purposes.
|
|
1257
1405
|
//
|
|
1258
|
-
// Returns the whole contents of the per_thread_context structs being tracked.
|
|
1406
|
+
// Returns the whole contents of the per_thread_context structs being tracked, by iterating all live threads.
|
|
1259
1407
|
static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1260
1408
|
thread_context_collector_state *state;
|
|
1261
1409
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1262
1410
|
|
|
1263
|
-
|
|
1411
|
+
VALUE result = rb_hash_new();
|
|
1412
|
+
VALUE threads = thread_list(state);
|
|
1413
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
1414
|
+
for (long i = 0; i < thread_count; i++) {
|
|
1415
|
+
VALUE thread = RARRAY_AREF(threads, i);
|
|
1416
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1417
|
+
if (thread_context != NULL) {
|
|
1418
|
+
rb_hash_aset(result, thread, per_thread_context_to_ruby_hash(thread_context));
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return result;
|
|
1264
1422
|
}
|
|
1265
1423
|
|
|
1266
|
-
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns,
|
|
1424
|
+
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, per_thread_context *thread_context) {
|
|
1267
1425
|
// If we didn't have a time for the previous sample, we use the current one
|
|
1268
1426
|
if (*time_at_previous_sample_ns == INVALID_TIME) *time_at_previous_sample_ns = current_time_ns;
|
|
1269
1427
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1428
|
+
// We don't expect to be sampling a thread (and thus updating these counters) while Ruby is doing GC (between
|
|
1429
|
+
// `thread_context_collector_on_gc_start` and `thread_context_collector_on_gc_finish`)
|
|
1430
|
+
if (thread_context->gc_tracking.cpu_time_at_start_ns != INVALID_TIME) {
|
|
1431
|
+
raise_error(
|
|
1432
|
+
rb_eRuntimeError,
|
|
1433
|
+
"BUG: Unexpected sample during GC (thread_id=%s, gc_tracking.cpu_time_at_start_ns=%ld, "
|
|
1434
|
+
"gc_tracking.wall_time_at_start_ns=%ld, monotonic_wall_time_now_ns=%ld)",
|
|
1435
|
+
thread_context->thread_id,
|
|
1436
|
+
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
1437
|
+
thread_context->gc_tracking.wall_time_at_start_ns,
|
|
1438
|
+
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1272
1441
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1442
|
+
long elapsed_time_ns = current_time_ns - *time_at_previous_sample_ns; // Capture all time since previous sample
|
|
1443
|
+
*time_at_previous_sample_ns = current_time_ns;
|
|
1275
1444
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
} else {
|
|
1279
|
-
elapsed_time_ns = gc_start_time_ns - *time_at_previous_sample_ns; // Capture time between previous sample and start of GC only
|
|
1280
|
-
}
|
|
1445
|
+
return elapsed_time_ns;
|
|
1446
|
+
}
|
|
1281
1447
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1448
|
+
static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns) {
|
|
1449
|
+
long elapsed_time_ns = update_time_since_previous_sample(
|
|
1450
|
+
&thread_context->cpu_time_at_previous_sample_ns,
|
|
1451
|
+
current_cpu_time_ns,
|
|
1452
|
+
thread_context
|
|
1453
|
+
);
|
|
1288
1454
|
|
|
1455
|
+
// We don't expect cpu-time to go backwards, so let's flag this as a bug
|
|
1289
1456
|
if (elapsed_time_ns < 0) {
|
|
1290
|
-
|
|
1291
|
-
// Wall-time can actually go backwards (e.g. when the system clock gets set) so we can't assume time going backwards
|
|
1292
|
-
// was a bug.
|
|
1293
|
-
// @ivoanjo: I've also observed time going backwards spuriously on macOS, see discussion on
|
|
1294
|
-
// https://github.com/DataDog/dd-trace-rb/pull/2336.
|
|
1295
|
-
elapsed_time_ns = 0;
|
|
1296
|
-
} else {
|
|
1297
|
-
// We don't expect non-wall time to go backwards, so let's flag this as a bug
|
|
1298
|
-
rb_raise(rb_eRuntimeError, "BUG: Unexpected negative elapsed_time_ns between samples");
|
|
1299
|
-
}
|
|
1457
|
+
raise_error(rb_eRuntimeError, "BUG: Unexpected CPU time going backwards between samples");
|
|
1300
1458
|
}
|
|
1301
1459
|
|
|
1302
1460
|
return elapsed_time_ns;
|
|
1303
1461
|
}
|
|
1304
1462
|
|
|
1463
|
+
static long update_wall_time_since_previous_sample(per_thread_context *thread_context, long current_wall_time_ns) {
|
|
1464
|
+
long elapsed_time_ns = update_time_since_previous_sample(
|
|
1465
|
+
&thread_context->wall_time_at_previous_sample_ns,
|
|
1466
|
+
current_wall_time_ns,
|
|
1467
|
+
thread_context
|
|
1468
|
+
);
|
|
1469
|
+
|
|
1470
|
+
// Wall-time can actually go backwards (e.g. when the system clock gets set) so we can't assume time going backwards
|
|
1471
|
+
// was a bug.
|
|
1472
|
+
// @ivoanjo: I've also observed time going backwards spuriously on macOS, see discussion on
|
|
1473
|
+
// https://github.com/DataDog/dd-trace-rb/pull/2336.
|
|
1474
|
+
return long_max_of(elapsed_time_ns, 0);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1305
1477
|
// Safety: This function is assumed never to raise exceptions by callers
|
|
1306
1478
|
static long cpu_time_now_ns(per_thread_context *thread_context) {
|
|
1307
1479
|
thread_cpu_time cpu_time = thread_cpu_time_for(thread_context->thread_cpu_time_id);
|
|
@@ -1338,13 +1510,23 @@ VALUE enforce_thread_context_collector_instance(VALUE object) {
|
|
|
1338
1510
|
return object;
|
|
1339
1511
|
}
|
|
1340
1512
|
|
|
1513
|
+
// Finalize any pending heap allocation recordings.
|
|
1514
|
+
// On Ruby 4+, heap allocations are recorded in two phases: during on_newobj_event we capture
|
|
1515
|
+
// the object reference, then later we safely call rb_obj_id() to get the object ID.
|
|
1516
|
+
void thread_context_collector_after_allocation(VALUE self_instance) {
|
|
1517
|
+
thread_context_collector_state *state;
|
|
1518
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1519
|
+
|
|
1520
|
+
recorder_after_sample(state->recorder_instance);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1341
1523
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1342
1524
|
// It SHOULD NOT be used for other purposes.
|
|
1343
1525
|
static VALUE _native_stats(DDTRACE_UNUSED VALUE _self, VALUE collector_instance) {
|
|
1344
1526
|
thread_context_collector_state *state;
|
|
1345
1527
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1346
1528
|
|
|
1347
|
-
return
|
|
1529
|
+
return stats_to_ruby_hash(state, rb_hash_new());
|
|
1348
1530
|
}
|
|
1349
1531
|
|
|
1350
1532
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
@@ -1439,17 +1621,18 @@ static bool should_collect_resource(VALUE root_span) {
|
|
|
1439
1621
|
//
|
|
1440
1622
|
// Assumption: This method gets called BEFORE restarting profiling -- e.g. there are no components attempting to
|
|
1441
1623
|
// trigger samples at the same time.
|
|
1624
|
+
//
|
|
1625
|
+
// Note that tests call this method directly in the same process without forking,
|
|
1626
|
+
// and in such a case non-current Threads keep running.
|
|
1442
1627
|
static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
1443
1628
|
thread_context_collector_state *state;
|
|
1444
1629
|
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1445
1630
|
|
|
1446
|
-
// Release all context memory before clearing the existing context
|
|
1447
|
-
st_foreach(state->hash_map_per_thread_context, hash_map_per_thread_context_free_values, 0 /* unused */);
|
|
1448
|
-
|
|
1449
|
-
st_clear(state->hash_map_per_thread_context);
|
|
1450
|
-
|
|
1451
1631
|
state->stats = (struct stats) {}; // Resets all stats back to zero
|
|
1452
1632
|
|
|
1633
|
+
// No need to clean-up the per-thread context because the CpuAndWallTimeWorker always cleans
|
|
1634
|
+
// it up unconditionally on every start/restart and that includes after a fork.
|
|
1635
|
+
|
|
1453
1636
|
rb_funcall(state->recorder_instance, rb_intern("reset_after_fork"), 0);
|
|
1454
1637
|
|
|
1455
1638
|
return Qtrue;
|
|
@@ -1469,25 +1652,25 @@ static VALUE thread_list(thread_context_collector_state *state) {
|
|
|
1469
1652
|
// expected to be called from a signal handler and to be async-signal-safe.
|
|
1470
1653
|
//
|
|
1471
1654
|
// Also, no allocation (Ruby or malloc) can happen.
|
|
1472
|
-
bool thread_context_collector_prepare_sample_inside_signal_handler(
|
|
1473
|
-
thread_context_collector_state *state;
|
|
1474
|
-
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
|
1475
|
-
// This should never fail if the above check passes
|
|
1476
|
-
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1477
|
-
|
|
1655
|
+
bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
1478
1656
|
VALUE current_thread = rb_thread_current();
|
|
1479
|
-
per_thread_context *thread_context =
|
|
1657
|
+
per_thread_context *thread_context = get_per_thread_context(current_thread);
|
|
1480
1658
|
if (thread_context == NULL) return false;
|
|
1481
1659
|
|
|
1482
1660
|
return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
|
|
1483
1661
|
}
|
|
1484
1662
|
|
|
1485
|
-
|
|
1663
|
+
// This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should never allocate in the
|
|
1664
|
+
// Ruby heap.
|
|
1665
|
+
//
|
|
1666
|
+
// Returns true if the after_allocation needs to be called (to do work that can't be done from inside the
|
|
1667
|
+
// tracepoint, such as allocate new objects), and false if it doesn't
|
|
1668
|
+
//
|
|
1669
|
+
// The callers must ensure thread_context is non-NULL.
|
|
1670
|
+
bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object) {
|
|
1486
1671
|
thread_context_collector_state *state;
|
|
1487
1672
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1488
1673
|
|
|
1489
|
-
VALUE current_thread = rb_thread_current();
|
|
1490
|
-
|
|
1491
1674
|
enum ruby_value_type type = rb_type(new_object);
|
|
1492
1675
|
|
|
1493
1676
|
// Tag samples with the VM internal types
|
|
@@ -1552,38 +1735,48 @@ void thread_context_collector_sample_allocation(VALUE self_instance, unsigned in
|
|
|
1552
1735
|
class_name = ruby_vm_type; // For other weird internal things we just use the VM type
|
|
1553
1736
|
}
|
|
1554
1737
|
|
|
1555
|
-
track_object(state->recorder_instance, new_object, sample_weight, class_name);
|
|
1738
|
+
bool needs_after_allocation = track_object(state->recorder_instance, new_object, sample_weight, class_name);
|
|
1556
1739
|
|
|
1557
|
-
|
|
1740
|
+
VALUE current_thread = rb_thread_current();
|
|
1558
1741
|
|
|
1559
1742
|
trigger_sample_for_thread(
|
|
1560
1743
|
state,
|
|
1561
|
-
|
|
1562
|
-
/* stack_from_thread: */ current_thread,
|
|
1744
|
+
current_thread,
|
|
1563
1745
|
thread_context,
|
|
1564
|
-
&thread_context->sampling_buffer,
|
|
1565
1746
|
(sample_values) {.alloc_samples = sample_weight, .alloc_samples_unscaled = 1, .heap_sample = true},
|
|
1566
1747
|
INVALID_TIME, // For now we're not collecting timestamps for allocation events, as per profiling team internal discussions
|
|
1567
1748
|
&ruby_vm_type,
|
|
1568
|
-
|
|
1749
|
+
&class_name,
|
|
1569
1750
|
/* is_gvl_waiting_state: */ false,
|
|
1570
1751
|
/* is_safe_to_allocate_objects: */ false // Not safe to allocate further inside the NEWOBJ tracepoint
|
|
1571
1752
|
);
|
|
1753
|
+
|
|
1754
|
+
return needs_after_allocation;
|
|
1572
1755
|
}
|
|
1573
1756
|
|
|
1574
1757
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1575
1758
|
// It SHOULD NOT be used for other purposes.
|
|
1576
1759
|
static VALUE _native_sample_allocation(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE sample_weight, VALUE new_object) {
|
|
1760
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
1761
|
+
if (thread_context == NULL) {
|
|
1762
|
+
rb_raise(rb_eRuntimeError, "Missing per_thread_context for current thread in _native_sample_allocation");
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1577
1765
|
debug_enter_unsafe_context();
|
|
1578
1766
|
|
|
1579
|
-
thread_context_collector_sample_allocation(collector_instance, NUM2UINT(sample_weight), new_object);
|
|
1767
|
+
bool needs_after_allocation = thread_context_collector_sample_allocation(collector_instance, thread_context, NUM2UINT(sample_weight), new_object);
|
|
1580
1768
|
|
|
1581
1769
|
debug_leave_unsafe_context();
|
|
1582
1770
|
|
|
1583
|
-
|
|
1771
|
+
// We could instead choose to automatically trigger the after allocation here; yet, it seems kinda nice to keep it manual for
|
|
1772
|
+
// the tests so we can pull on each lever separately and observe "the sausage being made" in steps
|
|
1773
|
+
return needs_after_allocation ? Qtrue : Qfalse;
|
|
1584
1774
|
}
|
|
1585
1775
|
|
|
1586
|
-
static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
|
|
1776
|
+
static VALUE new_empty_thread_inner(DDTRACE_UNUSED void *arg) {
|
|
1777
|
+
rb_thread_sleep(INT_MAX);
|
|
1778
|
+
return Qnil;
|
|
1779
|
+
}
|
|
1587
1780
|
|
|
1588
1781
|
// This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
|
|
1589
1782
|
// It SHOULD NOT be used for other purposes.
|
|
@@ -1798,7 +1991,7 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
|
1798
1991
|
VALUE otel_current_span_key = get_otel_current_span_key(state, is_safe_to_allocate_objects);
|
|
1799
1992
|
if (otel_current_span_key == Qnil) return;
|
|
1800
1993
|
|
|
1801
|
-
|
|
1994
|
+
long active_context_index = RARRAY_LEN(context_storage) - 1;
|
|
1802
1995
|
if (active_context_index < 0) return;
|
|
1803
1996
|
|
|
1804
1997
|
otel_span active_span = otel_span_from(rb_ary_entry(context_storage, active_context_index), otel_current_span_key);
|
|
@@ -1807,7 +2000,7 @@ static void otel_without_ddtrace_trace_identifiers_for(
|
|
|
1807
2000
|
otel_span local_root_span = active_span;
|
|
1808
2001
|
|
|
1809
2002
|
// Now find the oldest span starting from the active span that still has the same trace id as the active span
|
|
1810
|
-
for (
|
|
2003
|
+
for (long i = active_context_index - 1; i >= 0; i--) {
|
|
1811
2004
|
otel_span checking_span = otel_span_from(rb_ary_entry(context_storage, i), otel_current_span_key);
|
|
1812
2005
|
if (checking_span.span == Qnil) return;
|
|
1813
2006
|
|
|
@@ -1876,57 +2069,179 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1876
2069
|
((uint64_t)span_bytes[7]);
|
|
1877
2070
|
}
|
|
1878
2071
|
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
intptr_t thread_being_profiled = gvl_profiling_state_get(thread);
|
|
1891
|
-
if (!thread_being_profiled) return;
|
|
2072
|
+
void thread_context_collector_stats(VALUE self_instance, VALUE stats_hash) {
|
|
2073
|
+
thread_context_collector_state *state;
|
|
2074
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2075
|
+
stats_to_ruby_hash(state, stats_hash);
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance) {
|
|
2079
|
+
thread_context_collector_state *state;
|
|
2080
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2081
|
+
state->stats = (struct stats) {};
|
|
2082
|
+
}
|
|
1892
2083
|
|
|
1893
|
-
|
|
1894
|
-
|
|
2084
|
+
static void mark_thread_as_profiler_internal(per_thread_context *ctx) {
|
|
2085
|
+
ctx->is_profiler_internal_thread = true;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
void thread_context_collector_profiler_internal_thread_started(void) {
|
|
2089
|
+
per_thread_context *ctx = get_or_create_context_for(rb_thread_current());
|
|
2090
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2091
|
+
}
|
|
1895
2092
|
|
|
1896
|
-
|
|
2093
|
+
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2094
|
+
per_thread_context *ctx = get_or_create_context_for(thread);
|
|
2095
|
+
mark_thread_as_profiler_internal(ctx);
|
|
2096
|
+
return Qnil;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
// Called via rb_ensure when a profiler-internal thread (worker or idle helper) is about to exit.
|
|
2100
|
+
// Records a final sample so the thread's accumulated cpu/wall time since the last on_serialize
|
|
2101
|
+
// flush is not lost. on_serialize (below) also flushes profiler-internal threads during periodic
|
|
2102
|
+
// serialization, but it can't help at shutdown: by the time the final serialize runs, these
|
|
2103
|
+
// threads are already dead and absent from thread_list.
|
|
2104
|
+
void thread_context_collector_profiler_internal_thread_done(VALUE self_instance) {
|
|
2105
|
+
thread_context_collector_state *state;
|
|
2106
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2107
|
+
|
|
2108
|
+
VALUE current_thread = rb_thread_current();
|
|
2109
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
2110
|
+
if (!thread_context->is_profiler_internal_thread) {
|
|
2111
|
+
rb_raise(rb_eRuntimeError, "current thread %"PRIsVALUE" is not profiler-internal thread", current_thread);
|
|
1897
2112
|
}
|
|
1898
2113
|
|
|
1899
|
-
|
|
2114
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2115
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2116
|
+
|
|
2117
|
+
update_metrics_and_sample(
|
|
2118
|
+
state,
|
|
2119
|
+
current_thread,
|
|
2120
|
+
thread_context,
|
|
2121
|
+
current_cpu_time_ns,
|
|
2122
|
+
current_monotonic_wall_time_ns,
|
|
2123
|
+
true);
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// Flushes threads whose last per-tick sample was skipped (either by the SUSPENDED-skip
|
|
2127
|
+
// optimization, or by is_profiler_internal_thread) so their accumulated time is recorded.
|
|
2128
|
+
// Called by the stack recorder at the start of _native_serialize (regular periodic flush).
|
|
2129
|
+
void thread_context_collector_on_serialize(VALUE self_instance) {
|
|
2130
|
+
thread_context_collector_state *state;
|
|
2131
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2132
|
+
|
|
2133
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2134
|
+
VALUE threads = thread_list(state);
|
|
2135
|
+
const long thread_count = RARRAY_LEN(threads);
|
|
2136
|
+
|
|
2137
|
+
for (long i = 0; i < thread_count; i++) {
|
|
2138
|
+
VALUE thread = RARRAY_AREF(threads, i);
|
|
2139
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2140
|
+
|
|
2141
|
+
if (thread_context != NULL && (thread_context->was_skipped_at_last_sample || thread_context->is_profiler_internal_thread)) {
|
|
2142
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2143
|
+
// We need to force_sample=true otherwise this sample would be skipped too
|
|
2144
|
+
update_metrics_and_sample(
|
|
2145
|
+
state,
|
|
2146
|
+
thread,
|
|
2147
|
+
thread_context,
|
|
2148
|
+
current_cpu_time_ns,
|
|
2149
|
+
current_monotonic_wall_time_ns,
|
|
2150
|
+
true);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
void thread_context_collector_on_gvl_released(per_thread_context *thread_context) {
|
|
2156
|
+
thread_context->gvl_state_change_count |= GVL_SUSPENDED;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
void thread_context_collector_on_gvl_waiting(per_thread_context *thread_context) {
|
|
2160
|
+
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
|
2161
|
+
if (current_monotonic_wall_time_ns <= 0) return;
|
|
2162
|
+
|
|
2163
|
+
thread_context->gvl_waiting_at = current_monotonic_wall_time_ns;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2167
|
+
ENFORCE_THREAD(thread);
|
|
2168
|
+
|
|
2169
|
+
debug_enter_unsafe_context();
|
|
2170
|
+
|
|
2171
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2172
|
+
if (thread_context) thread_context_collector_on_gvl_waiting(thread_context);
|
|
2173
|
+
|
|
2174
|
+
debug_leave_unsafe_context();
|
|
2175
|
+
|
|
2176
|
+
return Qnil;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2180
|
+
ENFORCE_THREAD(thread);
|
|
2181
|
+
|
|
2182
|
+
debug_enter_unsafe_context();
|
|
2183
|
+
|
|
2184
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2185
|
+
if (thread_context) thread_context_collector_on_gvl_released(thread_context);
|
|
2186
|
+
|
|
2187
|
+
debug_leave_unsafe_context();
|
|
2188
|
+
|
|
2189
|
+
return Qnil;
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
#ifndef NO_GVL_INSTRUMENTATION
|
|
2193
|
+
// This function runs on the passed thread and has the GVL because it gets called just after the Ruby thread acquired the GVL
|
|
1900
2194
|
__attribute__((warn_unused_result))
|
|
1901
|
-
on_gvl_running_result
|
|
1902
|
-
|
|
2195
|
+
on_gvl_running_result thread_context_collector_on_gvl_running(VALUE self_instance, VALUE thread, per_thread_context *thread_context) {
|
|
2196
|
+
thread_context_collector_state *state;
|
|
2197
|
+
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1903
2198
|
|
|
1904
|
-
//
|
|
1905
|
-
|
|
2199
|
+
// Bump the event counter and clears the state bit to "running"
|
|
2200
|
+
uint64_t counter_portion = thread_context->gvl_state_change_count >> 1;
|
|
2201
|
+
thread_context->gvl_state_change_count = ((counter_portion + 1) << 1) | GVL_RUNNING;
|
|
1906
2202
|
|
|
2203
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
2204
|
+
// Thread was not waiting on gvl
|
|
2205
|
+
if (gvl_waiting_at == 0) {
|
|
2206
|
+
return (on_gvl_running_result) {.action = ON_GVL_RUNNING_UNKNOWN, .waiting_for_gvl_duration_ns = 0};
|
|
2207
|
+
}
|
|
1907
2208
|
// @ivoanjo: I'm not sure if this can happen -- It means we should've sampled already but haven't gotten the chance yet?
|
|
1908
|
-
if (gvl_waiting_at < 0)
|
|
2209
|
+
if (gvl_waiting_at < 0) {
|
|
2210
|
+
return (on_gvl_running_result) {.action = ON_GVL_RUNNING_SAMPLE, .waiting_for_gvl_duration_ns = 0};
|
|
2211
|
+
}
|
|
1909
2212
|
|
|
1910
2213
|
long waiting_for_gvl_duration_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - gvl_waiting_at;
|
|
1911
2214
|
|
|
1912
|
-
bool should_sample = waiting_for_gvl_duration_ns >= waiting_for_gvl_threshold_ns;
|
|
2215
|
+
bool should_sample = waiting_for_gvl_duration_ns >= state->waiting_for_gvl_threshold_ns;
|
|
1913
2216
|
|
|
1914
2217
|
if (should_sample) {
|
|
1915
2218
|
// We flip the gvl_waiting_at to negative to mark that the thread is now running and no longer waiting
|
|
1916
|
-
|
|
2219
|
+
long gvl_waiting_at_is_now_running = -gvl_waiting_at;
|
|
1917
2220
|
|
|
1918
|
-
|
|
2221
|
+
thread_context->gvl_waiting_at = gvl_waiting_at_is_now_running;
|
|
1919
2222
|
} else {
|
|
1920
|
-
|
|
1921
|
-
|
|
2223
|
+
thread_context->gvl_waiting_at = 0;
|
|
2224
|
+
|
|
2225
|
+
// Even though the GVL wait itself was below threshold, if the thread had skipped samples
|
|
2226
|
+
// (was suspended for a long time without the GVL), we still need to force a sample now.
|
|
2227
|
+
// Otherwise, the accumulated idle wall-time would be reported against whatever stack the
|
|
2228
|
+
// thread runs next, misrepresenting the time spent idle.
|
|
2229
|
+
if (thread_context->was_skipped_at_last_sample) {
|
|
2230
|
+
should_sample = true;
|
|
2231
|
+
}
|
|
1922
2232
|
}
|
|
1923
2233
|
|
|
1924
|
-
|
|
1925
|
-
|
|
2234
|
+
if (should_sample) {
|
|
2235
|
+
// We prepare the sample here because the postponed job might be called some time later,
|
|
2236
|
+
// possibly after some Ruby calls which change the Ruby stack,
|
|
2237
|
+
// and we want to attribute the time acquiring or without the GVL to the correct Ruby stack.
|
|
2238
|
+
prepare_sample_thread(thread, &thread_context->sampling_buffer);
|
|
2239
|
+
}
|
|
1926
2240
|
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
2241
|
+
return (on_gvl_running_result) {
|
|
2242
|
+
.action = should_sample ? ON_GVL_RUNNING_SAMPLE : ON_GVL_RUNNING_DONT_SAMPLE,
|
|
2243
|
+
.waiting_for_gvl_duration_ns = waiting_for_gvl_duration_ns,
|
|
2244
|
+
};
|
|
1930
2245
|
}
|
|
1931
2246
|
|
|
1932
2247
|
// Why does this method need to exist?
|
|
@@ -1946,7 +2261,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1946
2261
|
//
|
|
1947
2262
|
// Stack:
|
|
1948
2263
|
// If the thread starts working without the end of the Waiting for GVL sample, then by the time the thread is sampled
|
|
1949
|
-
// via the regular cpu/wall-time samples mechanism, the stack can be
|
|
2264
|
+
// via the regular cpu/wall-time samples mechanism, the stack can be inaccurate (e.g. does not correctly pinpoint
|
|
1950
2265
|
// where the waiting happened).
|
|
1951
2266
|
//
|
|
1952
2267
|
// Arguably, the last sample after Waiting for GVL ended (when gvl_waiting_at < 0) should always come from this method
|
|
@@ -1955,17 +2270,19 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1955
2270
|
//
|
|
1956
2271
|
// ---
|
|
1957
2272
|
//
|
|
2273
|
+
// Always called with the GVL, either from a postponed_job or from tests.
|
|
2274
|
+
//
|
|
1958
2275
|
// NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
|
|
1959
2276
|
// exists to enable testing.
|
|
1960
2277
|
VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns) {
|
|
1961
2278
|
thread_context_collector_state *state;
|
|
1962
2279
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
1963
2280
|
|
|
1964
|
-
|
|
2281
|
+
per_thread_context *thread_context = get_or_create_context_for(current_thread);
|
|
1965
2282
|
|
|
1966
|
-
|
|
2283
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
1967
2284
|
|
|
1968
|
-
if (gvl_waiting_at >= 0) {
|
|
2285
|
+
if (gvl_waiting_at >= 0 && !thread_context->was_skipped_at_last_sample) {
|
|
1969
2286
|
// @ivoanjo: I'm not sure if this can ever happen. This means that we're not on the same thread
|
|
1970
2287
|
// that ran `thread_context_collector_on_gvl_running` and made the decision to sample OR a regular sample was
|
|
1971
2288
|
// triggered ahead of us.
|
|
@@ -1973,9 +2290,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1973
2290
|
return Qfalse;
|
|
1974
2291
|
}
|
|
1975
2292
|
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may chose to push an
|
|
2293
|
+
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may choose to push an
|
|
1979
2294
|
// extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
|
|
1980
2295
|
// cpu-time of the thread and let `update_metrics_and_sample` decide what to do with it.
|
|
1981
2296
|
long cpu_time_for_thread = cpu_time_now_ns(thread_context);
|
|
@@ -1984,13 +2299,11 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
1984
2299
|
|
|
1985
2300
|
update_metrics_and_sample(
|
|
1986
2301
|
state,
|
|
1987
|
-
|
|
1988
|
-
/* stack_from_thread: */ current_thread,
|
|
2302
|
+
current_thread,
|
|
1989
2303
|
thread_context,
|
|
1990
|
-
&thread_context->sampling_buffer,
|
|
1991
2304
|
cpu_time_for_thread,
|
|
1992
|
-
current_monotonic_wall_time_ns
|
|
1993
|
-
|
|
2305
|
+
current_monotonic_wall_time_ns,
|
|
2306
|
+
false);
|
|
1994
2307
|
|
|
1995
2308
|
return Qtrue;
|
|
1996
2309
|
}
|
|
@@ -2001,14 +2314,12 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2001
2314
|
static bool handle_gvl_waiting(
|
|
2002
2315
|
thread_context_collector_state *state,
|
|
2003
2316
|
VALUE thread_being_sampled,
|
|
2004
|
-
VALUE stack_from_thread,
|
|
2005
2317
|
per_thread_context *thread_context,
|
|
2006
|
-
sampling_buffer* sampling_buffer,
|
|
2007
2318
|
long current_cpu_time_ns
|
|
2008
2319
|
) {
|
|
2009
|
-
|
|
2320
|
+
long gvl_waiting_at = thread_context->gvl_waiting_at;
|
|
2010
2321
|
|
|
2011
|
-
bool is_gvl_waiting_state = gvl_waiting_at != 0
|
|
2322
|
+
bool is_gvl_waiting_state = gvl_waiting_at != 0;
|
|
2012
2323
|
|
|
2013
2324
|
if (!is_gvl_waiting_state) return false;
|
|
2014
2325
|
|
|
@@ -2021,17 +2332,17 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2021
2332
|
// ...──────────────┬───────────────────...
|
|
2022
2333
|
// Other state │ Waiting for GVL
|
|
2023
2334
|
// ...──────────────┴───────────────────...
|
|
2024
|
-
// ▲
|
|
2335
|
+
// ▲ ▲
|
|
2025
2336
|
// └─ Previous sample └─ Regular sample (caller)
|
|
2026
2337
|
//
|
|
2027
2338
|
// In this case, we'll want to push two samples: a) one for the current time (handled by the caller), b) an extra sample
|
|
2028
|
-
// to represent the remaining cpu/wall time before the "Waiting for GVL" started:
|
|
2339
|
+
// to represent the remaining cpu/wall time before the "Waiting for GVL" started (for timeline purposes):
|
|
2029
2340
|
//
|
|
2030
2341
|
// time ─────►
|
|
2031
2342
|
// ...──────────────┬───────────────────...
|
|
2032
2343
|
// Other state │ Waiting for GVL
|
|
2033
2344
|
// ...──────────────┴───────────────────...
|
|
2034
|
-
// ▲
|
|
2345
|
+
// ▲ ▲ ▲
|
|
2035
2346
|
// └─ Prev... └─ Extra sample └─ Regular sample (caller)
|
|
2036
2347
|
//
|
|
2037
2348
|
// 2. The current sample is the n-th one after we entered the "Waiting for GVL" state
|
|
@@ -2041,7 +2352,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2041
2352
|
// ...──────────────┬───────────────────────────────────────────────...
|
|
2042
2353
|
// Other state │ Waiting for GVL
|
|
2043
2354
|
// ...──────────────┴───────────────────────────────────────────────...
|
|
2044
|
-
// ▲
|
|
2355
|
+
// ▲ ▲ ▲
|
|
2045
2356
|
// └─ Previous sample └─ Previous sample └─ Regular sample (caller)
|
|
2046
2357
|
//
|
|
2047
2358
|
// In this case, we just report back to the caller that the thread is in the "Waiting for GVL" state.
|
|
@@ -2056,33 +2367,22 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2056
2367
|
|
|
2057
2368
|
if (gvl_waiting_at < 0) {
|
|
2058
2369
|
// Negative means the waiting for GVL just ended, so we clear the state, so next samples no longer represent waiting
|
|
2059
|
-
|
|
2370
|
+
thread_context->gvl_waiting_at = 0;
|
|
2060
2371
|
}
|
|
2061
2372
|
|
|
2062
2373
|
long gvl_waiting_started_wall_time_ns = labs(gvl_waiting_at);
|
|
2063
2374
|
|
|
2064
2375
|
if (thread_context->wall_time_at_previous_sample_ns < gvl_waiting_started_wall_time_ns) { // situation 1 above
|
|
2065
|
-
long cpu_time_elapsed_ns =
|
|
2066
|
-
&thread_context->cpu_time_at_previous_sample_ns,
|
|
2067
|
-
current_cpu_time_ns,
|
|
2068
|
-
thread_context->gc_tracking.cpu_time_at_start_ns,
|
|
2069
|
-
IS_NOT_WALL_TIME
|
|
2070
|
-
);
|
|
2376
|
+
long cpu_time_elapsed_ns = update_cpu_time_since_previous_sample(thread_context, current_cpu_time_ns);
|
|
2071
2377
|
|
|
2072
|
-
long duration_until_start_of_gvl_waiting_ns =
|
|
2073
|
-
|
|
2074
|
-
gvl_waiting_started_wall_time_ns,
|
|
2075
|
-
INVALID_TIME,
|
|
2076
|
-
IS_WALL_TIME
|
|
2077
|
-
);
|
|
2378
|
+
long duration_until_start_of_gvl_waiting_ns =
|
|
2379
|
+
update_wall_time_since_previous_sample(thread_context, gvl_waiting_started_wall_time_ns);
|
|
2078
2380
|
|
|
2079
2381
|
// Push extra sample
|
|
2080
2382
|
trigger_sample_for_thread(
|
|
2081
2383
|
state,
|
|
2082
2384
|
thread_being_sampled,
|
|
2083
|
-
stack_from_thread,
|
|
2084
2385
|
thread_context,
|
|
2085
|
-
sampling_buffer,
|
|
2086
2386
|
(sample_values) {.cpu_time_ns = cpu_time_elapsed_ns, .cpu_or_wall_samples = 1, .wall_time_ns = duration_until_start_of_gvl_waiting_ns},
|
|
2087
2387
|
gvl_waiting_started_wall_time_ns,
|
|
2088
2388
|
NULL,
|
|
@@ -2095,46 +2395,42 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2095
2395
|
return true;
|
|
2096
2396
|
}
|
|
2097
2397
|
|
|
2098
|
-
static VALUE _native_on_gvl_waiting(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2099
|
-
ENFORCE_THREAD(thread);
|
|
2100
|
-
|
|
2101
|
-
debug_enter_unsafe_context();
|
|
2102
|
-
|
|
2103
|
-
thread_context_collector_on_gvl_waiting(thread_from_thread_object(thread));
|
|
2104
|
-
|
|
2105
|
-
debug_leave_unsafe_context();
|
|
2106
|
-
|
|
2107
|
-
return Qnil;
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
2398
|
static VALUE _native_gvl_waiting_at_for(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2111
2399
|
ENFORCE_THREAD(thread);
|
|
2112
2400
|
|
|
2113
2401
|
debug_enter_unsafe_context();
|
|
2114
2402
|
|
|
2115
|
-
|
|
2403
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2404
|
+
VALUE result = thread_context ? LONG2NUM(thread_context->gvl_waiting_at) : Qnil;
|
|
2116
2405
|
|
|
2117
2406
|
debug_leave_unsafe_context();
|
|
2118
2407
|
|
|
2119
|
-
return
|
|
2408
|
+
return result;
|
|
2120
2409
|
}
|
|
2121
2410
|
|
|
2122
|
-
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
2411
|
+
static VALUE _native_on_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
|
|
2123
2412
|
ENFORCE_THREAD(thread);
|
|
2124
2413
|
|
|
2125
2414
|
debug_enter_unsafe_context();
|
|
2126
2415
|
|
|
2127
|
-
|
|
2416
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2417
|
+
VALUE result;
|
|
2418
|
+
if (thread_context) {
|
|
2419
|
+
result = thread_context_collector_on_gvl_running(collector_instance, thread, thread_context).action == ON_GVL_RUNNING_SAMPLE ? Qtrue : Qfalse;
|
|
2420
|
+
} else {
|
|
2421
|
+
result = Qfalse;
|
|
2422
|
+
}
|
|
2128
2423
|
|
|
2129
2424
|
debug_leave_unsafe_context();
|
|
2130
2425
|
|
|
2131
2426
|
return result;
|
|
2132
2427
|
}
|
|
2133
2428
|
|
|
2134
|
-
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread) {
|
|
2429
|
+
static VALUE _native_sample_after_gvl_running(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE allow_exception) {
|
|
2135
2430
|
ENFORCE_THREAD(thread);
|
|
2431
|
+
ENFORCE_BOOLEAN(allow_exception);
|
|
2136
2432
|
|
|
2137
|
-
debug_enter_unsafe_context();
|
|
2433
|
+
if (allow_exception == Qfalse) debug_enter_unsafe_context();
|
|
2138
2434
|
|
|
2139
2435
|
VALUE result = thread_context_collector_sample_after_gvl_running(
|
|
2140
2436
|
collector_instance,
|
|
@@ -2142,36 +2438,32 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id) {
|
|
|
2142
2438
|
monotonic_wall_time_now_ns(RAISE_ON_FAILURE)
|
|
2143
2439
|
);
|
|
2144
2440
|
|
|
2145
|
-
debug_leave_unsafe_context();
|
|
2441
|
+
if (allow_exception == Qfalse) debug_leave_unsafe_context();
|
|
2146
2442
|
|
|
2147
2443
|
return result;
|
|
2148
2444
|
}
|
|
2149
2445
|
|
|
2150
|
-
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE thread, VALUE delta_ns) {
|
|
2151
|
-
ENFORCE_THREAD(thread);
|
|
2152
|
-
|
|
2153
|
-
thread_context_collector_state *state;
|
|
2154
|
-
TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2155
|
-
|
|
2156
|
-
per_thread_context *thread_context = get_context_for(thread, state);
|
|
2157
|
-
if (thread_context == NULL) rb_raise(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2158
|
-
|
|
2159
|
-
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
|
2160
|
-
|
|
2161
|
-
return Qtrue;
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
2446
|
#else
|
|
2165
2447
|
static bool handle_gvl_waiting(
|
|
2166
2448
|
DDTRACE_UNUSED thread_context_collector_state *state,
|
|
2167
2449
|
DDTRACE_UNUSED VALUE thread_being_sampled,
|
|
2168
|
-
DDTRACE_UNUSED VALUE stack_from_thread,
|
|
2169
2450
|
DDTRACE_UNUSED per_thread_context *thread_context,
|
|
2170
|
-
DDTRACE_UNUSED sampling_buffer* sampling_buffer,
|
|
2171
2451
|
DDTRACE_UNUSED long current_cpu_time_ns
|
|
2172
2452
|
) { return false; }
|
|
2453
|
+
|
|
2173
2454
|
#endif // NO_GVL_INSTRUMENTATION
|
|
2174
2455
|
|
|
2456
|
+
static VALUE _native_apply_delta_to_cpu_time_at_previous_sample_ns(DDTRACE_UNUSED VALUE self, VALUE thread, VALUE delta_ns) {
|
|
2457
|
+
ENFORCE_THREAD(thread);
|
|
2458
|
+
|
|
2459
|
+
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2460
|
+
if (thread_context == NULL) raise_error(rb_eArgError, "Unexpected: This method cannot be used unless the per-thread context for the thread already exists");
|
|
2461
|
+
|
|
2462
|
+
thread_context->cpu_time_at_previous_sample_ns += NUM2LONG(delta_ns);
|
|
2463
|
+
|
|
2464
|
+
return Qtrue;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2175
2467
|
#define MAX_SAFE_LOOKUP_SIZE 16
|
|
2176
2468
|
|
|
2177
2469
|
typedef struct { VALUE lookup_key; VALUE result; } safe_lookup_hash_state;
|
|
@@ -2216,6 +2508,6 @@ static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE c
|
|
|
2216
2508
|
return LONG2NUM(system_epoch_time_ns);
|
|
2217
2509
|
}
|
|
2218
2510
|
|
|
2219
|
-
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self
|
|
2220
|
-
return thread_context_collector_prepare_sample_inside_signal_handler(
|
|
2511
|
+
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self) {
|
|
2512
|
+
return thread_context_collector_prepare_sample_inside_signal_handler() ? Qtrue : Qfalse;
|
|
2221
2513
|
}
|