datadog 2.4.0 → 2.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1261 -1
- data/README.md +3 -1
- data/bin/ddprofrb +4 -4
- data/ext/LIBDATADOG_DEVELOPMENT.md +3 -0
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
- data/ext/datadog_profiling_native_extension/clock_id.h +11 -3
- data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +66 -0
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +3 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +653 -212
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +5 -5
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +33 -20
- data/ext/datadog_profiling_native_extension/collectors_stack.c +409 -113
- data/ext/datadog_profiling_native_extension/collectors_stack.h +41 -5
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +1208 -623
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +33 -8
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +240 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +85 -16
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +47 -1
- data/ext/datadog_profiling_native_extension/encoded_profile.c +80 -0
- data/ext/datadog_profiling_native_extension/encoded_profile.h +8 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +99 -120
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +6 -43
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -55
- data/ext/datadog_profiling_native_extension/heap_recorder.c +762 -608
- data/ext/datadog_profiling_native_extension/heap_recorder.h +67 -28
- data/ext/datadog_profiling_native_extension/http_transport.c +108 -144
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +18 -15
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -10
- data/ext/datadog_profiling_native_extension/macos_sampler_thread.h +55 -0
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -40
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +796 -417
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +48 -12
- data/ext/datadog_profiling_native_extension/profiling.c +93 -27
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +77 -137
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +21 -20
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +34 -10
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +2 -1
- data/ext/datadog_profiling_native_extension/stack_recorder.c +458 -302
- data/ext/datadog_profiling_native_extension/stack_recorder.h +14 -3
- data/ext/datadog_profiling_native_extension/time_helpers.h +18 -11
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +48 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +36 -0
- data/ext/libdatadog_api/crashtracker.c +33 -30
- data/ext/libdatadog_api/crashtracker.h +5 -0
- data/ext/libdatadog_api/crashtracker_report_exception.c +126 -0
- data/ext/libdatadog_api/datadog_ruby_common.c +85 -16
- data/ext/libdatadog_api/datadog_ruby_common.h +47 -1
- data/ext/libdatadog_api/ddsketch.c +102 -0
- data/ext/libdatadog_api/di.c +218 -0
- data/ext/libdatadog_api/extconf.rb +44 -45
- data/ext/libdatadog_api/feature_flags.c +588 -0
- data/ext/libdatadog_api/feature_flags.h +5 -0
- data/ext/libdatadog_api/helpers.h +27 -0
- data/ext/libdatadog_api/init.c +35 -0
- data/ext/libdatadog_api/library_config.c +173 -0
- data/ext/libdatadog_api/library_config.h +25 -0
- data/ext/libdatadog_api/otel_thread_context.c +268 -0
- data/ext/libdatadog_api/otel_thread_context.h +5 -0
- data/ext/libdatadog_api/process_discovery.c +118 -0
- data/ext/libdatadog_api/process_discovery.h +5 -0
- data/ext/libdatadog_api/trace_exporter.c +1932 -0
- data/ext/libdatadog_api/trace_exporter.h +5 -0
- data/ext/libdatadog_extconf_helpers.rb +116 -18
- data/lib/datadog/ai_guard/autoload.rb +10 -0
- data/lib/datadog/ai_guard/component.rb +53 -0
- data/lib/datadog/ai_guard/configuration/ext.rb +19 -0
- data/lib/datadog/ai_guard/configuration.rb +128 -0
- data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -0
- data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
- data/lib/datadog/ai_guard/contrib/rack/integration.rb +42 -0
- data/lib/datadog/ai_guard/contrib/rack/patcher.rb +26 -0
- data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +97 -0
- data/lib/datadog/ai_guard/contrib/rails/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/rails/patcher.rb +97 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +73 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +31 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
- data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
- data/lib/datadog/ai_guard/evaluation/content_part.rb +48 -0
- data/lib/datadog/ai_guard/evaluation/message.rb +43 -0
- data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +36 -0
- data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +30 -0
- data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +38 -0
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +31 -0
- data/lib/datadog/ai_guard/evaluation.rb +130 -0
- data/lib/datadog/ai_guard/ext.rb +32 -0
- data/lib/datadog/ai_guard/http_client.rb +86 -0
- data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
- data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
- data/lib/datadog/ai_guard/redaction/result.rb +29 -0
- data/lib/datadog/ai_guard/redaction.rb +80 -0
- data/lib/datadog/ai_guard.rb +182 -0
- data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +68 -0
- data/lib/datadog/appsec/actions_handler.rb +49 -0
- data/lib/datadog/appsec/anonymizer.rb +16 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +66 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +36 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
- data/lib/datadog/appsec/api_security/route_extractor.rb +104 -0
- data/lib/datadog/appsec/api_security/sampler.rb +62 -0
- data/lib/datadog/appsec/api_security.rb +23 -0
- data/lib/datadog/appsec/assets/blocked.html +1 -99
- data/lib/datadog/appsec/assets/blocked.json +1 -1
- data/lib/datadog/appsec/assets/blocked.text +3 -1
- data/lib/datadog/appsec/assets/waf_rules/README.md +44 -5
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +954 -229
- data/lib/datadog/appsec/assets/waf_rules/strict.json +109 -106
- data/lib/datadog/appsec/assets.rb +8 -8
- data/lib/datadog/appsec/autoload.rb +5 -5
- data/lib/datadog/appsec/component.rb +56 -75
- data/lib/datadog/appsec/compressed_json.rb +40 -0
- data/lib/datadog/appsec/configuration.rb +436 -1
- data/lib/datadog/appsec/context.rb +135 -0
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +94 -0
- data/lib/datadog/appsec/contrib/active_record/integration.rb +41 -0
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +104 -0
- data/lib/datadog/appsec/contrib/auto_instrument.rb +1 -1
- data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +75 -0
- data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +39 -0
- data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +146 -0
- data/lib/datadog/appsec/contrib/devise/configuration.rb +52 -0
- data/lib/datadog/appsec/contrib/devise/data_extractor.rb +78 -0
- data/lib/datadog/appsec/contrib/devise/ext.rb +22 -0
- data/lib/datadog/appsec/contrib/devise/integration.rb +5 -6
- data/lib/datadog/appsec/contrib/devise/patcher.rb +34 -26
- data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +104 -0
- data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +70 -0
- data/lib/datadog/appsec/contrib/devise/{patcher/rememberable_patch.rb → patches/skip_signin_tracking_patch.rb} +3 -3
- data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +121 -0
- data/lib/datadog/appsec/contrib/excon/integration.rb +41 -0
- data/lib/datadog/appsec/contrib/excon/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +181 -0
- data/lib/datadog/appsec/contrib/faraday/connection_patch.rb +22 -0
- data/lib/datadog/appsec/contrib/faraday/integration.rb +42 -0
- data/lib/datadog/appsec/contrib/faraday/patcher.rb +53 -0
- data/lib/datadog/appsec/contrib/faraday/rack_builder_patch.rb +22 -0
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +166 -0
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +4 -10
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +76 -26
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +21 -36
- data/lib/datadog/appsec/contrib/graphql/integration.rb +10 -9
- data/lib/datadog/appsec/contrib/graphql/patcher.rb +3 -6
- data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
- data/lib/datadog/appsec/contrib/rack/ext.rb +34 -0
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +56 -21
- data/lib/datadog/appsec/contrib/rack/gateway/response.rb +4 -4
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +101 -106
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +79 -0
- data/lib/datadog/appsec/contrib/rack/integration.rb +7 -7
- data/lib/datadog/appsec/contrib/rack/patcher.rb +3 -6
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +17 -15
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +118 -114
- data/lib/datadog/appsec/contrib/rack/response_body.rb +36 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +43 -4
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +58 -35
- data/lib/datadog/appsec/contrib/rails/integration.rb +6 -6
- data/lib/datadog/appsec/contrib/rails/patcher.rb +82 -69
- data/lib/datadog/appsec/contrib/rails/patches/process_action_patch.rb +29 -0
- data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +33 -0
- data/lib/datadog/appsec/contrib/rails/request.rb +3 -3
- data/lib/datadog/appsec/contrib/rails/request_middleware.rb +2 -1
- data/lib/datadog/appsec/contrib/rest_client/integration.rb +45 -0
- data/lib/datadog/appsec/contrib/rest_client/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +193 -0
- data/lib/datadog/appsec/contrib/sinatra/gateway/request.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/route_params.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +83 -71
- data/lib/datadog/appsec/contrib/sinatra/integration.rb +6 -6
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +25 -61
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +31 -0
- data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
- data/lib/datadog/appsec/counter_sampler.rb +25 -0
- data/lib/datadog/appsec/default_header_tags.rb +52 -0
- data/lib/datadog/appsec/event.rb +80 -128
- data/lib/datadog/appsec/ext.rb +17 -1
- data/lib/datadog/appsec/extensions.rb +2 -2
- data/lib/datadog/appsec/instrumentation/gateway/argument.rb +23 -2
- data/lib/datadog/appsec/instrumentation/gateway/middleware.rb +23 -0
- data/lib/datadog/appsec/instrumentation/gateway.rb +6 -24
- data/lib/datadog/appsec/metrics/collector.rb +80 -0
- data/lib/datadog/appsec/metrics/exporter.rb +48 -0
- data/lib/datadog/appsec/metrics/telemetry.rb +40 -0
- data/lib/datadog/appsec/metrics/telemetry_exporter.rb +47 -0
- data/lib/datadog/appsec/metrics.rb +14 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +60 -36
- data/lib/datadog/appsec/monitor.rb +1 -1
- data/lib/datadog/appsec/processor/rule_loader.rb +54 -60
- data/lib/datadog/appsec/rate_limiter.rb +1 -1
- data/lib/datadog/appsec/remote.rb +59 -70
- data/lib/datadog/appsec/response.rb +42 -94
- data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
- data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
- data/lib/datadog/appsec/route_normalizer.rb +80 -0
- data/lib/datadog/appsec/security_engine/engine.rb +197 -0
- data/lib/datadog/appsec/security_engine/result.rb +103 -0
- data/lib/datadog/appsec/security_engine/runner.rb +111 -0
- data/lib/datadog/appsec/security_engine.rb +9 -0
- data/lib/datadog/appsec/security_event.rb +37 -0
- data/lib/datadog/appsec/thread_safe_ref.rb +61 -0
- data/lib/datadog/appsec/trace_keeper.rb +36 -0
- data/lib/datadog/appsec/utils/hash_coercion.rb +23 -0
- data/lib/datadog/appsec/utils/http/body.rb +41 -0
- data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
- data/lib/datadog/appsec/utils/http/media_range.rb +17 -16
- data/lib/datadog/appsec/utils/http/media_type.rb +33 -27
- data/lib/datadog/appsec/utils/http/url_encoded.rb +107 -0
- data/lib/datadog/appsec.rb +35 -28
- data/lib/datadog/auto_instrument.rb +5 -2
- data/lib/datadog/auto_instrument_base.rb +2 -1
- data/lib/datadog/core/buffer/cruby.rb +1 -1
- data/lib/datadog/core/buffer/random.rb +18 -2
- data/lib/datadog/core/buffer/thread_safe.rb +1 -1
- data/lib/datadog/core/configuration/agent_settings.rb +52 -0
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +46 -59
- data/lib/datadog/core/configuration/agentless_settings_resolver.rb +176 -0
- data/lib/datadog/core/configuration/base.rb +19 -7
- data/lib/datadog/core/configuration/components.rb +283 -54
- data/lib/datadog/core/configuration/components_state.rb +28 -0
- data/lib/datadog/core/configuration/config_helper.rb +109 -0
- data/lib/datadog/core/configuration/deprecations.rb +36 -0
- data/lib/datadog/core/configuration/ext.rb +15 -12
- data/lib/datadog/core/configuration/option.rb +150 -84
- data/lib/datadog/core/configuration/option_definition.rb +51 -30
- data/lib/datadog/core/configuration/options.rb +58 -19
- data/lib/datadog/core/configuration/settings.rb +341 -140
- data/lib/datadog/core/configuration/stable_config.rb +32 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +426 -0
- data/lib/datadog/core/configuration.rb +51 -21
- data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
- data/lib/datadog/core/contrib/rails/utils.rb +28 -0
- data/lib/datadog/core/crashtracking/component.rb +89 -39
- data/lib/datadog/core/crashtracking/tag_builder.rb +11 -23
- data/lib/datadog/core/ddsketch.rb +19 -0
- data/lib/datadog/core/deprecations.rb +3 -3
- data/lib/datadog/core/diagnostics/environment_logger.rb +46 -9
- data/lib/datadog/core/diagnostics/health.rb +2 -2
- data/lib/datadog/core/encoding.rb +21 -5
- data/lib/datadog/core/environment/agent_info.rb +142 -0
- data/lib/datadog/core/environment/cgroup.rb +53 -28
- data/lib/datadog/core/environment/container.rb +143 -51
- data/lib/datadog/core/environment/execution.rb +3 -3
- data/lib/datadog/core/environment/ext.rb +32 -21
- data/lib/datadog/core/environment/gc.rb +1 -1
- data/lib/datadog/core/environment/git.rb +5 -4
- data/lib/datadog/core/environment/identity.rb +50 -14
- data/lib/datadog/core/environment/platform.rb +8 -8
- data/lib/datadog/core/environment/process.rb +122 -0
- data/lib/datadog/core/environment/socket.rb +15 -2
- data/lib/datadog/core/environment/variable_helpers.rb +4 -4
- data/lib/datadog/core/environment/vm_cache.rb +1 -1
- data/lib/datadog/core/environment/yjit.rb +8 -2
- data/lib/datadog/core/error.rb +15 -13
- data/lib/datadog/core/evp.rb +11 -0
- data/lib/datadog/core/extensions.rb +1 -1
- data/lib/datadog/core/feature_flags.rb +61 -0
- data/lib/datadog/core/git/ext.rb +4 -4
- data/lib/datadog/core/knuth_sampler.rb +59 -0
- data/lib/datadog/core/logger.rb +5 -5
- data/lib/datadog/core/logging/ext.rb +1 -1
- data/lib/datadog/core/metrics/client.rb +49 -49
- data/lib/datadog/core/metrics/ext.rb +5 -5
- data/lib/datadog/core/metrics/helpers.rb +1 -1
- data/lib/datadog/core/metrics/logging.rb +8 -8
- data/lib/datadog/core/metrics/options.rb +5 -5
- data/lib/datadog/core/pin.rb +9 -9
- data/lib/datadog/core/process_discovery/tracer_memfd.rb +13 -0
- data/lib/datadog/core/process_discovery.rb +62 -0
- data/lib/datadog/core/rate_limiter.rb +16 -4
- data/lib/datadog/core/remote/client/capabilities.rb +82 -11
- data/lib/datadog/core/remote/client.rb +130 -107
- data/lib/datadog/core/remote/component.rb +97 -55
- data/lib/datadog/core/remote/configuration/content.rb +17 -4
- data/lib/datadog/core/remote/configuration/digest.rb +22 -15
- data/lib/datadog/core/remote/configuration/path.rb +9 -9
- data/lib/datadog/core/remote/configuration/repository.rb +19 -6
- data/lib/datadog/core/remote/configuration/target.rb +15 -8
- data/lib/datadog/core/remote/configuration.rb +4 -4
- data/lib/datadog/core/remote/ext.rb +3 -3
- data/lib/datadog/core/remote/negotiation.rb +10 -10
- data/lib/datadog/core/remote/tie/tracing.rb +7 -7
- data/lib/datadog/core/remote/transport/config.rb +11 -37
- data/lib/datadog/core/remote/transport/http/config.rb +39 -107
- data/lib/datadog/core/remote/transport/http/negotiation.rb +19 -92
- data/lib/datadog/core/remote/transport/http.rb +33 -106
- data/lib/datadog/core/remote/transport/negotiation.rb +9 -21
- data/lib/datadog/core/remote/worker.rb +40 -41
- data/lib/datadog/core/runtime/ext.rb +22 -22
- data/lib/datadog/core/runtime/metrics.rb +34 -14
- data/lib/datadog/core/semaphore.rb +32 -0
- data/lib/datadog/core/tag_builder.rb +52 -0
- data/lib/datadog/core/tag_normalizer.rb +84 -0
- data/lib/datadog/core/telemetry/component.rb +143 -60
- data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
- data/lib/datadog/core/telemetry/emitter.rb +26 -14
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +67 -0
- data/lib/datadog/core/telemetry/event/app_closing.rb +18 -0
- data/lib/datadog/core/telemetry/event/app_dependencies_loaded.rb +33 -0
- data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
- data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
- data/lib/datadog/core/telemetry/event/app_heartbeat.rb +18 -0
- data/lib/datadog/core/telemetry/event/app_integrations_change.rb +58 -0
- data/lib/datadog/core/telemetry/event/app_started.rb +274 -0
- data/lib/datadog/core/telemetry/event/base.rb +40 -0
- data/lib/datadog/core/telemetry/event/distributions.rb +18 -0
- data/lib/datadog/core/telemetry/event/generate_metrics.rb +43 -0
- data/lib/datadog/core/telemetry/event/log.rb +76 -0
- data/lib/datadog/core/telemetry/event/message_batch.rb +42 -0
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +66 -0
- data/lib/datadog/core/telemetry/event.rb +21 -392
- data/lib/datadog/core/telemetry/ext.rb +11 -9
- data/lib/datadog/core/telemetry/http/adapters/net.rb +12 -97
- data/lib/datadog/core/telemetry/logger.rb +6 -3
- data/lib/datadog/core/telemetry/logging.rb +35 -17
- data/lib/datadog/core/telemetry/metric.rb +37 -15
- data/lib/datadog/core/telemetry/metrics_collection.rb +2 -2
- data/lib/datadog/core/telemetry/metrics_manager.rb +10 -1
- data/lib/datadog/core/telemetry/request.rb +23 -11
- data/lib/datadog/core/telemetry/transport/http/telemetry.rb +66 -0
- data/lib/datadog/core/telemetry/transport/http.rb +68 -0
- data/lib/datadog/core/telemetry/transport/telemetry.rb +42 -0
- data/lib/datadog/core/telemetry/worker.rb +214 -35
- data/lib/datadog/core/transport/ext.rb +14 -10
- data/lib/datadog/core/transport/http/adapters/net.rb +19 -4
- data/lib/datadog/core/transport/http/adapters/test.rb +3 -2
- data/lib/datadog/core/transport/http/adapters/unix_socket.rb +5 -5
- data/lib/datadog/core/transport/http/api/endpoint.rb +10 -5
- data/lib/datadog/{tracing → core}/transport/http/api/instance.rb +5 -5
- data/lib/datadog/core/transport/http/api/map.rb +1 -1
- data/lib/datadog/{tracing → core}/transport/http/builder.rb +62 -36
- data/lib/datadog/core/transport/http/client.rb +80 -0
- data/lib/datadog/core/transport/http/env.rb +8 -0
- data/lib/datadog/core/transport/http/response.rb +5 -1
- data/lib/datadog/core/transport/http.rb +80 -0
- data/lib/datadog/core/transport/parcel.rb +61 -9
- data/lib/datadog/core/transport/response.rb +19 -1
- data/lib/datadog/core/transport/transport.rb +90 -0
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +181 -29
- data/lib/datadog/core/utils/base64_codec.rb +23 -0
- data/lib/datadog/core/utils/duration.rb +33 -33
- data/lib/datadog/core/utils/enumerable_compat.rb +29 -0
- data/lib/datadog/core/utils/fnv.rb +26 -0
- data/lib/datadog/core/utils/forking.rb +5 -3
- data/lib/datadog/core/utils/hash.rb +0 -23
- data/lib/datadog/core/utils/lru_cache.rb +45 -0
- data/lib/datadog/core/utils/network.rb +34 -13
- data/lib/datadog/core/utils/only_once.rb +1 -1
- data/lib/datadog/core/utils/only_once_successful.rb +25 -8
- data/lib/datadog/core/utils/safe_dup.rb +4 -3
- data/lib/datadog/core/utils/sequence.rb +3 -4
- data/lib/datadog/core/utils/spawn_monkey_patch.rb +68 -0
- data/lib/datadog/core/utils/time.rb +19 -33
- data/lib/datadog/core/utils/truncation.rb +21 -0
- data/lib/datadog/core/utils/url.rb +1 -1
- data/lib/datadog/core/utils.rb +14 -5
- data/lib/datadog/core/vendor/multipart-post/multipart/post/composite_read_io.rb +1 -1
- data/lib/datadog/core/vendor/multipart-post/multipart/post/multipartable.rb +8 -8
- data/lib/datadog/core/vendor/multipart-post/multipart/post/parts.rb +7 -7
- data/lib/datadog/core/worker.rb +1 -1
- data/lib/datadog/core/workers/async.rb +41 -15
- data/lib/datadog/core/workers/interval_loop.rb +63 -4
- data/lib/datadog/core/workers/polling.rb +4 -2
- data/lib/datadog/core/workers/queue.rb +96 -1
- data/lib/datadog/core/workers/runtime_metrics.rb +14 -6
- data/lib/datadog/core.rb +20 -3
- data/lib/datadog/data_streams/configuration.rb +50 -0
- data/lib/datadog/data_streams/ext.rb +11 -0
- data/lib/datadog/data_streams/extensions.rb +16 -0
- data/lib/datadog/data_streams/pathway_context.rb +167 -0
- data/lib/datadog/data_streams/processor.rb +542 -0
- data/lib/datadog/data_streams/transport/http/stats.rb +58 -0
- data/lib/datadog/data_streams/transport/http.rb +36 -0
- data/lib/datadog/data_streams/transport/stats.rb +39 -0
- data/lib/datadog/data_streams.rb +100 -0
- data/lib/datadog/di/base.rb +123 -0
- data/lib/datadog/di/boot.rb +47 -0
- data/lib/datadog/di/capture_expression.rb +21 -0
- data/lib/datadog/di/capture_expression_evaluator.rb +71 -0
- data/lib/datadog/di/capture_limits.rb +41 -0
- data/lib/datadog/di/code_tracker.rb +256 -18
- data/lib/datadog/di/component.rb +220 -0
- data/lib/datadog/di/configuration.rb +241 -2
- data/lib/datadog/di/context.rb +82 -0
- data/lib/datadog/di/contrib/active_record.rb +41 -0
- data/lib/datadog/di/contrib/railtie.rb +15 -0
- data/lib/datadog/di/contrib.rb +28 -0
- data/lib/datadog/di/el/compiler.rb +244 -0
- data/lib/datadog/di/el/evaluator.rb +239 -0
- data/lib/datadog/di/el/expression.rb +53 -0
- data/lib/datadog/di/el.rb +5 -0
- data/lib/datadog/di/error.rb +66 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +978 -0
- data/lib/datadog/di/logger.rb +30 -0
- data/lib/datadog/di/preload.rb +18 -0
- data/lib/datadog/di/probe.rb +188 -14
- data/lib/datadog/di/probe_builder.rb +132 -3
- data/lib/datadog/di/probe_file_loader/railtie.rb +15 -0
- data/lib/datadog/di/probe_file_loader.rb +87 -0
- data/lib/datadog/di/probe_manager.rb +352 -0
- data/lib/datadog/di/probe_notification_builder.rb +452 -0
- data/lib/datadog/di/probe_notifier_worker.rb +381 -0
- data/lib/datadog/di/probe_repository.rb +198 -0
- data/lib/datadog/di/proc_responder.rb +36 -0
- data/lib/datadog/di/redactor.rb +24 -3
- data/lib/datadog/di/remote.rb +310 -0
- data/lib/datadog/di/serializer.rb +487 -96
- data/lib/datadog/di/transport/diagnostics.rb +32 -0
- data/lib/datadog/di/transport/http/diagnostics.rb +35 -0
- data/lib/datadog/di/transport/http/input.rb +45 -0
- data/lib/datadog/di/transport/http.rb +81 -0
- data/lib/datadog/di/transport/input.rb +144 -0
- data/lib/datadog/di/utils.rb +170 -0
- data/lib/datadog/di.rb +224 -2
- data/lib/datadog/error_tracking/collector.rb +88 -0
- data/lib/datadog/error_tracking/component.rb +167 -0
- data/lib/datadog/error_tracking/configuration.rb +64 -0
- data/lib/datadog/error_tracking/ext.rb +18 -0
- data/lib/datadog/error_tracking/extensions.rb +16 -0
- data/lib/datadog/error_tracking/filters.rb +77 -0
- data/lib/datadog/error_tracking.rb +18 -0
- data/lib/datadog/kit/appsec/events/v2.rb +253 -0
- data/lib/datadog/kit/appsec/events.rb +39 -26
- data/lib/datadog/kit/enable_core_dumps.rb +3 -3
- data/lib/datadog/kit/identity.rb +34 -24
- data/lib/datadog/kit/tracing/method_tracer.rb +135 -0
- data/lib/datadog/kit.rb +1 -1
- data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
- data/lib/datadog/open_feature/agentless/parser.rb +43 -0
- data/lib/datadog/open_feature/agentless/response.rb +19 -0
- data/lib/datadog/open_feature/agentless/transport.rb +103 -0
- data/lib/datadog/open_feature/component.rb +165 -0
- data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
- data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
- data/lib/datadog/open_feature/configuration/source.rb +43 -0
- data/lib/datadog/open_feature/configuration.rb +168 -0
- data/lib/datadog/open_feature/evaluation_engine.rb +96 -0
- data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
- data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
- data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
- data/lib/datadog/open_feature/exposures/event.rb +67 -0
- data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
- data/lib/datadog/open_feature/exposures/worker.rb +116 -0
- data/lib/datadog/open_feature/ext.rb +40 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +442 -0
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +544 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +105 -0
- data/lib/datadog/open_feature/hooks/flag_eval_metrics_hook.rb +51 -0
- data/lib/datadog/open_feature/hooks/span_enrichment_hook/codec.rb +58 -0
- data/lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state.rb +96 -0
- data/lib/datadog/open_feature/hooks/span_enrichment_hook/span_enrichment_state_store.rb +49 -0
- data/lib/datadog/open_feature/hooks/span_enrichment_hook.rb +184 -0
- data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
- data/lib/datadog/open_feature/native_evaluator.rb +74 -0
- data/lib/datadog/open_feature/noop_evaluator.rb +31 -0
- data/lib/datadog/open_feature/provider.rb +235 -0
- data/lib/datadog/open_feature/remote.rb +67 -0
- data/lib/datadog/open_feature/resolution_details.rb +37 -0
- data/lib/datadog/open_feature/transport.rb +126 -0
- data/lib/datadog/open_feature.rb +19 -0
- data/lib/datadog/opentelemetry/api/baggage.rb +90 -0
- data/lib/datadog/opentelemetry/api/context.rb +25 -10
- data/lib/datadog/opentelemetry/configuration/settings.rb +226 -0
- data/lib/datadog/opentelemetry/ext.rb +9 -0
- data/lib/datadog/opentelemetry/logs.rb +98 -0
- data/lib/datadog/opentelemetry/metrics.rb +90 -0
- data/lib/datadog/opentelemetry/sdk/configurator.rb +69 -5
- data/lib/datadog/opentelemetry/sdk/id_generator.rb +16 -10
- data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +30 -0
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +28 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +21 -15
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +24 -21
- data/lib/datadog/opentelemetry/sdk/trace/span.rb +43 -39
- data/lib/datadog/opentelemetry/sdk.rb +19 -0
- data/lib/datadog/opentelemetry/signal_configuration.rb +57 -0
- data/lib/datadog/opentelemetry/trace.rb +8 -5
- data/lib/datadog/opentelemetry.rb +11 -6
- data/lib/datadog/profiling/collectors/code_provenance.rb +84 -16
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +60 -3
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -4
- data/lib/datadog/profiling/collectors/info.rb +62 -8
- data/lib/datadog/profiling/collectors/thread_context.rb +25 -18
- data/lib/datadog/profiling/component.rb +123 -104
- data/lib/datadog/profiling/encoded_profile.rb +11 -0
- data/lib/datadog/profiling/exporter.rb +51 -17
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +24 -5
- data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
- data/lib/datadog/profiling/ext.rb +2 -17
- data/lib/datadog/profiling/flush.rb +26 -17
- data/lib/datadog/profiling/http_transport.rb +30 -96
- data/lib/datadog/profiling/load_native_extension.rb +3 -35
- data/lib/datadog/profiling/profiler.rb +31 -12
- data/lib/datadog/profiling/scheduler.rb +19 -10
- data/lib/datadog/profiling/sequence_tracker.rb +44 -0
- data/lib/datadog/profiling/stack_recorder.rb +38 -18
- data/lib/datadog/profiling/tag_builder.rb +41 -41
- data/lib/datadog/profiling/tasks/exec.rb +11 -6
- data/lib/datadog/profiling/tasks/help.rb +1 -0
- data/lib/datadog/profiling/tasks/setup.rb +4 -2
- data/lib/datadog/profiling.rb +42 -37
- data/lib/datadog/ruby_version.rb +25 -0
- data/lib/datadog/single_step_instrument.rb +11 -2
- data/lib/datadog/symbol_database/component.rb +734 -0
- data/lib/datadog/symbol_database/configuration.rb +73 -0
- data/lib/datadog/symbol_database/extensions.rb +19 -0
- data/lib/datadog/symbol_database/extractor.rb +1092 -0
- data/lib/datadog/symbol_database/file_hash.rb +48 -0
- data/lib/datadog/symbol_database/logger.rb +43 -0
- data/lib/datadog/symbol_database/remote.rb +195 -0
- data/lib/datadog/symbol_database/scope.rb +102 -0
- data/lib/datadog/symbol_database/scope_batcher.rb +292 -0
- data/lib/datadog/symbol_database/service_version.rb +66 -0
- data/lib/datadog/symbol_database/symbol.rb +69 -0
- data/lib/datadog/symbol_database/transport/http/endpoint.rb +28 -0
- data/lib/datadog/symbol_database/transport/http.rb +45 -0
- data/lib/datadog/symbol_database/transport.rb +54 -0
- data/lib/datadog/symbol_database/uploader.rb +230 -0
- data/lib/datadog/symbol_database.rb +71 -0
- data/lib/datadog/tracing/analytics.rb +2 -2
- data/lib/datadog/tracing/buffer.rb +15 -15
- data/lib/datadog/tracing/client_ip.rb +4 -4
- data/lib/datadog/tracing/component.rb +63 -23
- data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
- data/lib/datadog/tracing/configuration/dynamic.rb +16 -16
- data/lib/datadog/tracing/configuration/ext.rb +62 -33
- data/lib/datadog/tracing/configuration/http.rb +2 -2
- data/lib/datadog/tracing/configuration/settings.rb +182 -34
- data/lib/datadog/tracing/context.rb +27 -5
- data/lib/datadog/tracing/context_provider.rb +12 -8
- data/lib/datadog/tracing/contrib/action_cable/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/action_cable/event.rb +5 -5
- data/lib/datadog/tracing/contrib/action_cable/events/broadcast.rb +9 -6
- data/lib/datadog/tracing/contrib/action_cable/events/perform_action.rb +8 -5
- data/lib/datadog/tracing/contrib/action_cable/events/transmit.rb +9 -6
- data/lib/datadog/tracing/contrib/action_cable/events.rb +5 -5
- data/lib/datadog/tracing/contrib/action_cable/ext.rb +18 -18
- data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -5
- data/lib/datadog/tracing/contrib/action_cable/integration.rb +10 -7
- data/lib/datadog/tracing/contrib/action_cable/patcher.rb +4 -4
- data/lib/datadog/tracing/contrib/action_mailer/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/action_mailer/event.rb +7 -4
- data/lib/datadog/tracing/contrib/action_mailer/events/deliver.rb +8 -8
- data/lib/datadog/tracing/contrib/action_mailer/events/process.rb +4 -4
- data/lib/datadog/tracing/contrib/action_mailer/events.rb +3 -3
- data/lib/datadog/tracing/contrib/action_mailer/ext.rb +18 -18
- data/lib/datadog/tracing/contrib/action_mailer/integration.rb +11 -7
- data/lib/datadog/tracing/contrib/action_mailer/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +38 -20
- data/lib/datadog/tracing/contrib/action_pack/action_controller/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +48 -13
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +5 -3
- data/lib/datadog/tracing/contrib/action_pack/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/action_pack/ext.rb +10 -8
- data/lib/datadog/tracing/contrib/action_pack/integration.rb +10 -7
- data/lib/datadog/tracing/contrib/action_pack/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/action_pack/utils.rb +2 -3
- data/lib/datadog/tracing/contrib/action_view/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/action_view/event.rb +1 -1
- data/lib/datadog/tracing/contrib/action_view/events/render_partial.rb +10 -7
- data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +12 -9
- data/lib/datadog/tracing/contrib/action_view/events.rb +3 -3
- data/lib/datadog/tracing/contrib/action_view/ext.rb +10 -10
- data/lib/datadog/tracing/contrib/action_view/integration.rb +14 -11
- data/lib/datadog/tracing/contrib/action_view/patcher.rb +5 -5
- data/lib/datadog/tracing/contrib/action_view/utils.rb +2 -2
- data/lib/datadog/tracing/contrib/active_job/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/active_job/data_streams.rb +62 -0
- data/lib/datadog/tracing/contrib/active_job/event.rb +11 -11
- data/lib/datadog/tracing/contrib/active_job/events/discard.rb +11 -8
- data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +11 -8
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +11 -8
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +11 -8
- data/lib/datadog/tracing/contrib/active_job/events/perform.rb +11 -8
- data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +11 -8
- data/lib/datadog/tracing/contrib/active_job/events.rb +6 -6
- data/lib/datadog/tracing/contrib/active_job/ext.rb +23 -23
- data/lib/datadog/tracing/contrib/active_job/integration.rb +10 -7
- data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
- data/lib/datadog/tracing/contrib/active_job/patcher.rb +21 -5
- data/lib/datadog/tracing/contrib/active_model_serializers/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/active_model_serializers/event.rb +4 -4
- data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +8 -8
- data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +9 -10
- data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +3 -3
- data/lib/datadog/tracing/contrib/active_model_serializers/ext.rb +10 -10
- data/lib/datadog/tracing/contrib/active_model_serializers/integration.rb +5 -6
- data/lib/datadog/tracing/contrib/active_model_serializers/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/active_record/configuration/makara_resolver.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +9 -9
- data/lib/datadog/tracing/contrib/active_record/configuration/settings.rb +4 -4
- data/lib/datadog/tracing/contrib/active_record/event.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +10 -10
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +17 -19
- data/lib/datadog/tracing/contrib/active_record/events.rb +3 -3
- data/lib/datadog/tracing/contrib/active_record/ext.rb +15 -15
- data/lib/datadog/tracing/contrib/active_record/integration.rb +16 -12
- data/lib/datadog/tracing/contrib/active_record/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/active_record/utils.rb +19 -19
- data/lib/datadog/tracing/contrib/active_support/cache/event.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +29 -21
- data/lib/datadog/tracing/contrib/active_support/cache/events.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +40 -8
- data/lib/datadog/tracing/contrib/active_support/cache/patcher.rb +8 -4
- data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +16 -6
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +26 -3
- data/lib/datadog/tracing/contrib/active_support/ext.rb +16 -16
- data/lib/datadog/tracing/contrib/active_support/integration.rb +11 -8
- data/lib/datadog/tracing/contrib/active_support/notifications/event.rb +3 -2
- data/lib/datadog/tracing/contrib/active_support/notifications/subscriber.rb +2 -2
- data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +9 -11
- data/lib/datadog/tracing/contrib/active_support/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/analytics.rb +1 -1
- data/lib/datadog/tracing/contrib/auto_instrument.rb +5 -5
- data/lib/datadog/tracing/contrib/aws/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/aws/ext.rb +27 -27
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +21 -13
- data/lib/datadog/tracing/contrib/aws/integration.rb +11 -8
- data/lib/datadog/tracing/contrib/aws/parsed_context.rb +9 -3
- data/lib/datadog/tracing/contrib/aws/patcher.rb +10 -6
- data/lib/datadog/tracing/contrib/aws/service/base.rb +2 -1
- data/lib/datadog/tracing/contrib/aws/service/dynamodb.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/service/eventbridge.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/service/kinesis.rb +4 -4
- data/lib/datadog/tracing/contrib/aws/service/s3.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/service/sns.rb +3 -3
- data/lib/datadog/tracing/contrib/aws/service/sqs.rb +3 -3
- data/lib/datadog/tracing/contrib/aws/service/states.rb +5 -5
- data/lib/datadog/tracing/contrib/aws/services.rb +14 -14
- data/lib/datadog/tracing/contrib/component.rb +3 -3
- data/lib/datadog/tracing/contrib/concurrent_ruby/async_patch.rb +2 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/context_composite_executor_service.rb +2 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/ext.rb +2 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/future_patch.rb +2 -2
- data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +8 -5
- data/lib/datadog/tracing/contrib/concurrent_ruby/patcher.rb +4 -4
- data/lib/datadog/tracing/contrib/concurrent_ruby/promises_future_patch.rb +2 -2
- data/lib/datadog/tracing/contrib/configurable.rb +24 -9
- data/lib/datadog/tracing/contrib/configuration/resolver.rb +14 -4
- data/lib/datadog/tracing/contrib/configuration/resolvers/pattern_resolver.rb +5 -5
- data/lib/datadog/tracing/contrib/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/dalli/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/dalli/ext.rb +16 -15
- data/lib/datadog/tracing/contrib/dalli/instrumentation.rb +6 -10
- data/lib/datadog/tracing/contrib/dalli/integration.rb +10 -7
- data/lib/datadog/tracing/contrib/dalli/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/dalli/quantize.rb +3 -3
- data/lib/datadog/tracing/contrib/delayed_job/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/delayed_job/ext.rb +14 -14
- data/lib/datadog/tracing/contrib/delayed_job/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/delayed_job/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/delayed_job/plugin.rb +7 -5
- data/lib/datadog/tracing/contrib/delayed_job/server_internal_tracer/worker.rb +1 -0
- data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +6 -2
- data/lib/datadog/tracing/contrib/elasticsearch/ext.rb +18 -17
- data/lib/datadog/tracing/contrib/elasticsearch/integration.rb +8 -8
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +63 -64
- data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +9 -9
- data/lib/datadog/tracing/contrib/ethon/configuration/settings.rb +7 -3
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +19 -17
- data/lib/datadog/tracing/contrib/ethon/ext.rb +16 -14
- data/lib/datadog/tracing/contrib/ethon/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/ethon/multi_patch.rb +4 -11
- data/lib/datadog/tracing/contrib/ethon/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +21 -10
- data/lib/datadog/tracing/contrib/excon/ext.rb +14 -12
- data/lib/datadog/tracing/contrib/excon/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/excon/middleware.rb +18 -17
- data/lib/datadog/tracing/contrib/excon/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/ext.rb +23 -20
- data/lib/datadog/tracing/contrib/extensions.rb +57 -14
- data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +20 -13
- data/lib/datadog/tracing/contrib/faraday/ext.rb +14 -12
- data/lib/datadog/tracing/contrib/faraday/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +20 -17
- data/lib/datadog/tracing/contrib/faraday/patcher.rb +6 -6
- data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +11 -7
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +69 -36
- data/lib/datadog/tracing/contrib/grape/ext.rb +15 -15
- data/lib/datadog/tracing/contrib/grape/instrumentation.rb +15 -10
- data/lib/datadog/tracing/contrib/grape/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/grape/patcher.rb +10 -5
- data/lib/datadog/tracing/contrib/graphql/configuration/error_extension_env_parser.rb +21 -0
- data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +20 -2
- data/lib/datadog/tracing/contrib/graphql/ext.rb +11 -5
- data/lib/datadog/tracing/contrib/graphql/integration.rb +9 -9
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +8 -8
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +174 -41
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/grpc/configuration/settings.rb +7 -4
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +26 -24
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +14 -18
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor.rb +6 -6
- data/lib/datadog/tracing/contrib/grpc/distributed/fetcher.rb +2 -2
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +13 -8
- data/lib/datadog/tracing/contrib/grpc/ext.rb +14 -13
- data/lib/datadog/tracing/contrib/grpc/formatting.rb +7 -7
- data/lib/datadog/tracing/contrib/grpc/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/grpc/intercept_with_datadog.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/grpc.rb +7 -6
- data/lib/datadog/tracing/contrib/hanami/action_tracer.rb +4 -3
- data/lib/datadog/tracing/contrib/hanami/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/hanami/ext.rb +8 -8
- data/lib/datadog/tracing/contrib/hanami/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/hanami/patcher.rb +5 -5
- data/lib/datadog/tracing/contrib/hanami/renderer_policy_tracing.rb +5 -4
- data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +14 -14
- data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +1 -7
- data/lib/datadog/tracing/contrib/http/configuration/settings.rb +20 -9
- data/lib/datadog/tracing/contrib/http/distributed/fetcher.rb +6 -6
- data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +13 -8
- data/lib/datadog/tracing/contrib/http/ext.rb +14 -12
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +20 -24
- data/lib/datadog/tracing/contrib/http/integration.rb +9 -8
- data/lib/datadog/tracing/contrib/http/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/http.rb +7 -6
- data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +20 -9
- data/lib/datadog/tracing/contrib/httpclient/ext.rb +14 -12
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +21 -27
- data/lib/datadog/tracing/contrib/httpclient/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/httpclient/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +20 -9
- data/lib/datadog/tracing/contrib/httprb/ext.rb +14 -12
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +21 -29
- data/lib/datadog/tracing/contrib/httprb/integration.rb +9 -6
- data/lib/datadog/tracing/contrib/httprb/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/integration.rb +3 -3
- data/lib/datadog/tracing/contrib/kafka/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/kafka/event.rb +5 -4
- data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +8 -4
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_batch.rb +5 -5
- data/lib/datadog/tracing/contrib/kafka/events/consumer/process_message.rb +5 -5
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/heartbeat.rb +6 -6
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/join_group.rb +6 -6
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/leave_group.rb +6 -6
- data/lib/datadog/tracing/contrib/kafka/events/consumer_group/sync_group.rb +6 -6
- data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +11 -6
- data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +11 -6
- data/lib/datadog/tracing/contrib/kafka/events.rb +10 -10
- data/lib/datadog/tracing/contrib/kafka/ext.rb +40 -40
- data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
- data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
- data/lib/datadog/tracing/contrib/kafka/integration.rb +8 -5
- data/lib/datadog/tracing/contrib/kafka/patcher.rb +17 -3
- data/lib/datadog/tracing/contrib/karafka/configuration/settings.rb +31 -0
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +50 -0
- data/lib/datadog/tracing/contrib/karafka/ext.rb +28 -0
- data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
- data/lib/datadog/tracing/contrib/karafka/integration.rb +45 -0
- data/lib/datadog/tracing/contrib/karafka/monitor.rb +77 -0
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +102 -0
- data/lib/datadog/tracing/contrib/karafka.rb +38 -0
- data/lib/datadog/tracing/contrib/lograge/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/lograge/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/lograge/instrumentation.rb +1 -1
- data/lib/datadog/tracing/contrib/lograge/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +13 -11
- data/lib/datadog/tracing/contrib/mongodb/configuration/settings.rb +11 -3
- data/lib/datadog/tracing/contrib/mongodb/ext.rb +19 -18
- data/lib/datadog/tracing/contrib/mongodb/instrumentation.rb +3 -3
- data/lib/datadog/tracing/contrib/mongodb/integration.rb +9 -6
- data/lib/datadog/tracing/contrib/mongodb/parsers.rb +10 -10
- data/lib/datadog/tracing/contrib/mongodb/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +38 -24
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +9 -3
- data/lib/datadog/tracing/contrib/mysql2/ext.rb +12 -12
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +25 -18
- data/lib/datadog/tracing/contrib/mysql2/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/mysql2/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/opensearch/configuration/settings.rb +19 -2
- data/lib/datadog/tracing/contrib/opensearch/ext.rb +31 -21
- data/lib/datadog/tracing/contrib/opensearch/integration.rb +8 -6
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +75 -77
- data/lib/datadog/tracing/contrib/opensearch/quantize.rb +10 -10
- data/lib/datadog/tracing/contrib/patchable.rb +1 -1
- data/lib/datadog/tracing/contrib/patcher.rb +15 -14
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +9 -3
- data/lib/datadog/tracing/contrib/pg/ext.rb +19 -19
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +21 -24
- data/lib/datadog/tracing/contrib/pg/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/pg/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/presto/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/presto/ext.rb +22 -22
- data/lib/datadog/tracing/contrib/presto/instrumentation.rb +11 -13
- data/lib/datadog/tracing/contrib/presto/integration.rb +8 -5
- data/lib/datadog/tracing/contrib/presto/patcher.rb +4 -4
- data/lib/datadog/tracing/contrib/propagation/sql_comment/comment.rb +2 -2
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +26 -13
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +7 -3
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +29 -5
- data/lib/datadog/tracing/contrib/que/configuration/settings.rb +7 -4
- data/lib/datadog/tracing/contrib/que/ext.rb +19 -18
- data/lib/datadog/tracing/contrib/que/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/que/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/que/tracer.rb +3 -2
- data/lib/datadog/tracing/contrib/racecar/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/racecar/event.rb +6 -10
- data/lib/datadog/tracing/contrib/racecar/events/batch.rb +5 -5
- data/lib/datadog/tracing/contrib/racecar/events/consume.rb +4 -4
- data/lib/datadog/tracing/contrib/racecar/events/message.rb +5 -5
- data/lib/datadog/tracing/contrib/racecar/events.rb +4 -4
- data/lib/datadog/tracing/contrib/racecar/ext.rb +18 -18
- data/lib/datadog/tracing/contrib/racecar/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/racecar/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +14 -4
- data/lib/datadog/tracing/contrib/rack/ext.rb +43 -15
- data/lib/datadog/tracing/contrib/rack/header_collection.rb +13 -3
- data/lib/datadog/tracing/contrib/rack/header_tagging.rb +55 -32
- data/lib/datadog/tracing/contrib/rack/integration.rb +7 -7
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +102 -63
- data/lib/datadog/tracing/contrib/rack/patcher.rb +6 -6
- data/lib/datadog/tracing/contrib/rack/request_queue.rb +8 -7
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +67 -0
- data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +131 -7
- data/lib/datadog/tracing/contrib/rails/auto_instrument_railtie.rb +1 -1
- data/lib/datadog/tracing/contrib/rails/configuration/settings.rb +9 -6
- data/lib/datadog/tracing/contrib/rails/ext.rb +16 -14
- data/lib/datadog/tracing/contrib/rails/framework.rb +12 -12
- data/lib/datadog/tracing/contrib/rails/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/rails/log_injection.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/middlewares.rb +4 -4
- data/lib/datadog/tracing/contrib/rails/patcher.rb +11 -9
- data/lib/datadog/tracing/contrib/rails/railtie.rb +4 -4
- data/lib/datadog/tracing/contrib/rails/runner.rb +64 -40
- data/lib/datadog/tracing/contrib/rake/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/rake/ext.rb +12 -12
- data/lib/datadog/tracing/contrib/rake/instrumentation.rb +13 -11
- data/lib/datadog/tracing/contrib/rake/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/rake/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +5 -5
- data/lib/datadog/tracing/contrib/redis/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/redis/ext.rb +19 -18
- data/lib/datadog/tracing/contrib/redis/instrumentation.rb +6 -6
- data/lib/datadog/tracing/contrib/redis/integration.rb +7 -7
- data/lib/datadog/tracing/contrib/redis/patcher.rb +12 -12
- data/lib/datadog/tracing/contrib/redis/quantize.rb +41 -7
- data/lib/datadog/tracing/contrib/redis/tags.rb +7 -12
- data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +20 -12
- data/lib/datadog/tracing/contrib/registerable.rb +11 -0
- data/lib/datadog/tracing/contrib/registry.rb +1 -1
- data/lib/datadog/tracing/contrib/resque/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/resque/ext.rb +7 -7
- data/lib/datadog/tracing/contrib/resque/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/resque/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/resque/resque_job.rb +7 -6
- data/lib/datadog/tracing/contrib/rest_client/configuration/settings.rb +7 -4
- data/lib/datadog/tracing/contrib/rest_client/ext.rb +13 -11
- data/lib/datadog/tracing/contrib/rest_client/integration.rb +8 -5
- data/lib/datadog/tracing/contrib/rest_client/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +19 -15
- data/lib/datadog/tracing/contrib/roda/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/roda/ext.rb +6 -5
- data/lib/datadog/tracing/contrib/roda/instrumentation.rb +12 -7
- data/lib/datadog/tracing/contrib/roda/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/roda/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/semantic_logger/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/semantic_logger/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/semantic_logger/instrumentation.rb +1 -1
- data/lib/datadog/tracing/contrib/semantic_logger/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/semantic_logger/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/sequel/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/sequel/database.rb +8 -11
- data/lib/datadog/tracing/contrib/sequel/dataset.rb +7 -6
- data/lib/datadog/tracing/contrib/sequel/ext.rb +12 -8
- data/lib/datadog/tracing/contrib/sequel/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
- data/lib/datadog/tracing/contrib/sequel/patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/sequel/utils.rb +103 -16
- data/lib/datadog/tracing/contrib/shoryuken/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/shoryuken/ext.rb +12 -12
- data/lib/datadog/tracing/contrib/shoryuken/integration.rb +6 -6
- data/lib/datadog/tracing/contrib/shoryuken/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/shoryuken/tracer.rb +3 -2
- data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +15 -9
- data/lib/datadog/tracing/contrib/sidekiq/configuration/settings.rb +7 -3
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +14 -9
- data/lib/datadog/tracing/contrib/sidekiq/ext.rb +31 -29
- data/lib/datadog/tracing/contrib/sidekiq/integration.rb +7 -7
- data/lib/datadog/tracing/contrib/sidekiq/patcher.rb +8 -8
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/heartbeat.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/job_fetch.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/redis_info.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/scheduled_poller.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_internal_tracer/stop.rb +1 -0
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +19 -15
- data/lib/datadog/tracing/contrib/sidekiq/utils.rb +10 -10
- data/lib/datadog/tracing/contrib/sidekiq.rb +7 -6
- data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +8 -4
- data/lib/datadog/tracing/contrib/sinatra/env.rb +6 -6
- data/lib/datadog/tracing/contrib/sinatra/ext.rb +17 -16
- data/lib/datadog/tracing/contrib/sinatra/framework.rb +2 -2
- data/lib/datadog/tracing/contrib/sinatra/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/sinatra/patcher.rb +5 -5
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +11 -10
- data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +46 -45
- data/lib/datadog/tracing/contrib/sneakers/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/sneakers/ext.rb +12 -12
- data/lib/datadog/tracing/contrib/sneakers/integration.rb +19 -8
- data/lib/datadog/tracing/contrib/sneakers/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/sneakers/tracer.rb +5 -4
- data/lib/datadog/tracing/contrib/span_attribute_schema.rb +9 -4
- data/lib/datadog/tracing/contrib/status_range_env_parser.rb +2 -2
- data/lib/datadog/tracing/contrib/status_range_matcher.rb +13 -1
- data/lib/datadog/tracing/contrib/stripe/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/stripe/ext.rb +12 -12
- data/lib/datadog/tracing/contrib/stripe/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/stripe/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/stripe/request.rb +5 -5
- data/lib/datadog/tracing/contrib/sucker_punch/configuration/settings.rb +2 -2
- data/lib/datadog/tracing/contrib/sucker_punch/exception_handler.rb +1 -1
- data/lib/datadog/tracing/contrib/sucker_punch/ext.rb +13 -13
- data/lib/datadog/tracing/contrib/sucker_punch/instrumentation.rb +5 -4
- data/lib/datadog/tracing/contrib/sucker_punch/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/sucker_punch/patcher.rb +4 -4
- data/lib/datadog/tracing/contrib/support.rb +28 -0
- data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +9 -3
- data/lib/datadog/tracing/contrib/trilogy/ext.rb +12 -12
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +10 -12
- data/lib/datadog/tracing/contrib/trilogy/integration.rb +5 -5
- data/lib/datadog/tracing/contrib/trilogy/patcher.rb +2 -2
- data/lib/datadog/tracing/contrib/utils/database.rb +5 -5
- data/lib/datadog/tracing/contrib/utils/quantization/{hash.rb → hash_formatter.rb} +17 -15
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +16 -16
- data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +31 -0
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +50 -0
- data/lib/datadog/tracing/contrib/waterdrop/ext.rb +18 -0
- data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
- data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
- data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +49 -0
- data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
- data/lib/datadog/tracing/contrib/waterdrop.rb +42 -0
- data/lib/datadog/tracing/contrib.rb +62 -52
- data/lib/datadog/tracing/correlation.rb +21 -14
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +23 -15
- data/lib/datadog/tracing/diagnostics/ext.rb +19 -19
- data/lib/datadog/tracing/diagnostics/health.rb +1 -1
- data/lib/datadog/tracing/distributed/b3_multi.rb +9 -9
- data/lib/datadog/tracing/distributed/b3_single.rb +6 -6
- data/lib/datadog/tracing/distributed/baggage.rb +250 -0
- data/lib/datadog/tracing/distributed/datadog.rb +41 -38
- data/lib/datadog/tracing/distributed/fetcher.rb +1 -1
- data/lib/datadog/tracing/distributed/helpers.rb +4 -4
- data/lib/datadog/tracing/distributed/none.rb +4 -2
- data/lib/datadog/tracing/distributed/propagation.rb +73 -14
- data/lib/datadog/tracing/distributed/propagation_policy.rb +47 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +31 -259
- data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
- data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
- data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
- data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
- data/lib/datadog/tracing/event.rb +6 -8
- data/lib/datadog/tracing/ext.rb +9 -0
- data/lib/datadog/tracing/flush.rb +1 -1
- data/lib/datadog/tracing/metadata/analytics.rb +3 -3
- data/lib/datadog/tracing/metadata/errors.rb +8 -8
- data/lib/datadog/tracing/metadata/ext.rb +89 -59
- data/lib/datadog/tracing/metadata/metastruct.rb +36 -0
- data/lib/datadog/tracing/metadata/metastruct_tagging.rb +42 -0
- data/lib/datadog/tracing/metadata/tagging.rb +9 -9
- data/lib/datadog/tracing/metadata.rb +5 -3
- data/lib/datadog/tracing/otel_thread_context.rb +99 -0
- data/lib/datadog/tracing/pipeline/span_filter.rb +5 -3
- data/lib/datadog/tracing/pipeline/span_processor.rb +3 -1
- data/lib/datadog/tracing/pipeline.rb +4 -4
- data/lib/datadog/tracing/remote.rb +228 -30
- data/lib/datadog/tracing/sampling/all_sampler.rb +1 -1
- data/lib/datadog/tracing/sampling/ext.rb +9 -9
- data/lib/datadog/tracing/sampling/matcher.rb +4 -4
- data/lib/datadog/tracing/sampling/priority_sampler.rb +20 -5
- data/lib/datadog/tracing/sampling/rate_by_key_sampler.rb +3 -3
- data/lib/datadog/tracing/sampling/rate_by_service_sampler.rb +2 -2
- data/lib/datadog/tracing/sampling/rate_sampler.rb +9 -19
- data/lib/datadog/tracing/sampling/rule.rb +5 -5
- data/lib/datadog/tracing/sampling/rule_sampler.rb +91 -58
- data/lib/datadog/tracing/sampling/sampler.rb +2 -2
- data/lib/datadog/tracing/sampling/span/ext.rb +3 -3
- data/lib/datadog/tracing/sampling/span/matcher.rb +2 -2
- data/lib/datadog/tracing/sampling/span/rule.rb +1 -2
- data/lib/datadog/tracing/sampling/span/rule_parser.rb +11 -11
- data/lib/datadog/tracing/sampling/span/sampler.rb +0 -7
- data/lib/datadog/tracing/span.rb +29 -12
- data/lib/datadog/tracing/span_event.rb +125 -5
- data/lib/datadog/tracing/span_link.rb +12 -12
- data/lib/datadog/tracing/span_operation.rb +120 -40
- data/lib/datadog/tracing/sync_writer.rb +22 -14
- data/lib/datadog/tracing/trace_digest.rb +58 -28
- data/lib/datadog/tracing/trace_operation.rb +263 -120
- data/lib/datadog/tracing/trace_segment.rb +18 -16
- data/lib/datadog/tracing/tracer.rb +208 -73
- data/lib/datadog/tracing/transport/http/client.rb +17 -30
- data/lib/datadog/tracing/transport/http/statistics.rb +2 -2
- data/lib/datadog/tracing/transport/http/traces.rb +11 -90
- data/lib/datadog/tracing/transport/http.rb +26 -73
- data/lib/datadog/tracing/transport/io/client.rb +13 -16
- data/lib/datadog/tracing/transport/io/response.rb +1 -1
- data/lib/datadog/tracing/transport/io/traces.rb +33 -39
- data/lib/datadog/tracing/transport/io.rb +3 -3
- data/lib/datadog/tracing/transport/native/response.rb +71 -0
- data/lib/datadog/tracing/transport/native.rb +379 -0
- data/lib/datadog/tracing/transport/serializable_trace.rb +47 -23
- data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
- data/lib/datadog/tracing/transport/statistics.rb +3 -3
- data/lib/datadog/tracing/transport/trace_formatter.rb +49 -10
- data/lib/datadog/tracing/transport/traces.rb +45 -99
- data/lib/datadog/tracing/utils.rb +3 -3
- data/lib/datadog/tracing/workers.rb +12 -10
- data/lib/datadog/tracing/writer.rb +54 -39
- data/lib/datadog/tracing.rb +19 -6
- data/lib/datadog/version.rb +4 -4
- data/lib/datadog.rb +21 -5
- metadata +372 -70
- data/ext/datadog_profiling_loader/datadog_profiling_loader.c +0 -142
- data/ext/datadog_profiling_loader/extconf.rb +0 -60
- data/ext/datadog_profiling_native_extension/clock_id_noop.c +0 -21
- data/ext/libdatadog_api/macos_development.md +0 -26
- data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -92
- data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -114
- data/lib/datadog/appsec/configuration/settings.rb +0 -207
- data/lib/datadog/appsec/contrib/devise/event.rb +0 -57
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +0 -77
- data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +0 -54
- data/lib/datadog/appsec/contrib/devise/resource.rb +0 -35
- data/lib/datadog/appsec/contrib/devise/tracking.rb +0 -57
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +0 -59
- data/lib/datadog/appsec/contrib/patcher.rb +0 -12
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +0 -81
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +0 -60
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +0 -66
- data/lib/datadog/appsec/contrib/rails/ext.rb +0 -13
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +0 -66
- data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +0 -61
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +0 -58
- data/lib/datadog/appsec/processor/actions.rb +0 -49
- data/lib/datadog/appsec/processor/rule_merger.rb +0 -170
- data/lib/datadog/appsec/processor.rb +0 -170
- data/lib/datadog/appsec/reactive/address_hash.rb +0 -22
- data/lib/datadog/appsec/reactive/engine.rb +0 -47
- data/lib/datadog/appsec/reactive/operation.rb +0 -68
- data/lib/datadog/appsec/reactive/subscriber.rb +0 -19
- data/lib/datadog/appsec/scope.rb +0 -61
- data/lib/datadog/core/crashtracking/agent_base_url.rb +0 -21
- data/lib/datadog/core/remote/transport/http/api/instance.rb +0 -39
- data/lib/datadog/core/remote/transport/http/api/spec.rb +0 -21
- data/lib/datadog/core/remote/transport/http/api.rb +0 -58
- data/lib/datadog/core/remote/transport/http/builder.rb +0 -219
- data/lib/datadog/core/remote/transport/http/client.rb +0 -48
- data/lib/datadog/core/telemetry/http/env.rb +0 -20
- data/lib/datadog/core/telemetry/http/ext.rb +0 -28
- data/lib/datadog/core/telemetry/http/response.rb +0 -70
- data/lib/datadog/core/telemetry/http/transport.rb +0 -90
- data/lib/datadog/core/utils/base64.rb +0 -22
- data/lib/datadog/di/configuration/settings.rb +0 -163
- data/lib/datadog/tracing/contrib/rails/utils.rb +0 -26
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -84
- data/lib/datadog/tracing/transport/http/api/spec.rb +0 -19
- data/lib/datadog/tracing/transport/http/api.rb +0 -43
- data/lib/datadog/tracing/workers/trace_writer.rb +0 -196
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
#include "heap_recorder.h"
|
|
2
|
-
#include <pthread.h>
|
|
3
2
|
#include "ruby/st.h"
|
|
4
3
|
#include "ruby_helpers.h"
|
|
5
|
-
#include <errno.h>
|
|
6
4
|
#include "collectors_stack.h"
|
|
7
5
|
#include "libdatadog_helpers.h"
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
#include "private_vm_api_access.h"
|
|
7
|
+
#include "time_helpers.h"
|
|
8
|
+
|
|
9
|
+
// note on calloc vs ruby_xcalloc use:
|
|
10
|
+
// * Whenever we're allocating memory after being called by the Ruby VM in a "regular" situation (e.g. initializer)
|
|
11
|
+
// we should use `ruby_xcalloc` to give the VM visibility into what we're doing + give it a chance to manage GC
|
|
12
|
+
// * BUT, when we're being called during a sample, being in the middle of an object allocation is a very special
|
|
13
|
+
// situation for the VM to be in, and we've found the hard way (e.g. https://bugs.ruby-lang.org/issues/20629 and
|
|
14
|
+
// https://github.com/DataDog/dd-trace-rb/pull/4240 ) that it can be easy to do things the VM didn't expect.
|
|
15
|
+
// * Thus, out of caution and to avoid future potential issues such as the ones above, whenever we allocate memory
|
|
16
|
+
// during **sampling** we use `calloc` instead of `ruby_xcalloc`. Note that we've never seen issues from using
|
|
17
|
+
// `ruby_xcalloc` at any time, so this is a **precaution** not a "we've seen it break". But it seems a harmless
|
|
18
|
+
// one to use.
|
|
19
|
+
// This applies to both heap_recorder.c and collectors_thread_context.c
|
|
12
20
|
|
|
13
21
|
// Minimum age (in GC generations) of heap objects we want to include in heap
|
|
14
22
|
// recorder iterations. Object with age 0 represent objects that have yet to undergo
|
|
@@ -16,106 +24,71 @@
|
|
|
16
24
|
// relevant for heap profiles as the great majority should be trivially reclaimed
|
|
17
25
|
// during the next GC.
|
|
18
26
|
#define ITERATION_MIN_AGE 1
|
|
27
|
+
// Copied from https://github.com/ruby/ruby/blob/15135030e5808d527325feaaaf04caeb1b44f8b5/gc/default.c#L725C1-L725C27
|
|
28
|
+
// to align with Ruby's GC definition of what constitutes an old object which are only
|
|
29
|
+
// supposed to be reclaimed in major GCs.
|
|
30
|
+
#define OLD_AGE 3
|
|
31
|
+
// Wait at least 2 seconds before asking heap recorder to explicitly update itself. Heap recorder
|
|
32
|
+
// data will only materialize at profile serialization time but updating often helps keep our
|
|
33
|
+
// heap tracking data small since every GC should get rid of a bunch of temporary objects. The
|
|
34
|
+
// more we clean up before profile flush, the less work we'll have to do all-at-once when preparing
|
|
35
|
+
// to flush heap data and holding the GVL which should hopefully help with reducing latency impact.
|
|
36
|
+
#define MIN_TIME_BETWEEN_HEAP_RECORDER_UPDATES_NS SECONDS_AS_NS(2)
|
|
19
37
|
|
|
20
38
|
// A compact representation of a stacktrace frame for a heap allocation.
|
|
21
39
|
typedef struct {
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
ddog_prof_ManagedStringId name;
|
|
41
|
+
ddog_prof_ManagedStringId filename;
|
|
24
42
|
int32_t line;
|
|
25
43
|
} heap_frame;
|
|
26
|
-
|
|
44
|
+
|
|
45
|
+
// We use memcmp/st_hash below to compare/hash an entire array of heap_frames, so want to make sure no padding is added
|
|
46
|
+
// We could define the structure to be packed, but that seems even weirder across compilers, and this seems more portable?
|
|
47
|
+
_Static_assert(
|
|
48
|
+
sizeof(heap_frame) == sizeof(ddog_prof_ManagedStringId) * 2 + sizeof(int32_t),
|
|
49
|
+
"Size of heap_frame does not match the sum of its members. Padding detected."
|
|
50
|
+
);
|
|
27
51
|
|
|
28
52
|
// A compact representation of a stacktrace for a heap allocation.
|
|
29
|
-
//
|
|
30
|
-
// We could use a ddog_prof_Slice_Location instead but it has a lot of
|
|
31
|
-
// unused fields. Because we have to keep these stacks around for at
|
|
32
|
-
// least the lifetime of the objects allocated therein, we would be
|
|
33
|
-
// incurring a non-negligible memory overhead for little purpose.
|
|
53
|
+
// Used to dedup heap allocation stacktraces across multiple objects sharing the same allocation location.
|
|
34
54
|
typedef struct {
|
|
55
|
+
// How many objects are currently tracked in object_records recorder for this heap record.
|
|
56
|
+
uint32_t num_tracked_objects;
|
|
57
|
+
|
|
35
58
|
uint16_t frames_len;
|
|
36
59
|
heap_frame frames[];
|
|
37
|
-
}
|
|
38
|
-
static
|
|
39
|
-
static void
|
|
40
|
-
static st_index_t heap_stack_hash(heap_stack*, st_index_t);
|
|
60
|
+
} heap_record;
|
|
61
|
+
static heap_record* heap_record_new(heap_recorder*, ddog_prof_Slice_Location);
|
|
62
|
+
static void heap_record_free(heap_recorder*, heap_record*, bool should_unintern);
|
|
41
63
|
|
|
42
64
|
#if MAX_FRAMES_LIMIT > UINT16_MAX
|
|
43
65
|
#error Frames len type not compatible with MAX_FRAMES_LIMIT
|
|
44
66
|
#endif
|
|
45
67
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
};
|
|
50
|
-
// This struct allows us to use two different types of stacks when
|
|
51
|
-
// interacting with a heap_record hash.
|
|
52
|
-
//
|
|
53
|
-
// The idea is that we'll always want to use heap_stack-keys when
|
|
54
|
-
// adding new entries to the hash since that's the compact stack
|
|
55
|
-
// representation we rely on internally.
|
|
56
|
-
//
|
|
57
|
-
// However, when querying for an existing heap record, we'd save a
|
|
58
|
-
// lot of allocations if we could query with the
|
|
59
|
-
// ddog_prof_Slice_Location we receive in our external API.
|
|
60
|
-
//
|
|
61
|
-
// To allow this interchange, we need a union and need to ensure
|
|
62
|
-
// that whatever shape of the union, the heap_record_key_cmp_st
|
|
63
|
-
// and heap_record_hash_st functions return the same results for
|
|
64
|
-
// equivalent stacktraces.
|
|
65
|
-
typedef struct {
|
|
66
|
-
enum heap_record_key_type type;
|
|
67
|
-
union {
|
|
68
|
-
// key never owns this if set
|
|
69
|
-
heap_stack *heap_stack;
|
|
70
|
-
// key never owns this if set
|
|
71
|
-
ddog_prof_Slice_Location *location_slice;
|
|
72
|
-
};
|
|
73
|
-
} heap_record_key;
|
|
74
|
-
static heap_record_key* heap_record_key_new(heap_stack*);
|
|
75
|
-
static void heap_record_key_free(heap_record_key*);
|
|
76
|
-
static int heap_record_key_cmp_st(st_data_t, st_data_t);
|
|
77
|
-
static st_index_t heap_record_key_hash_st(st_data_t);
|
|
78
|
-
static const struct st_hash_type st_hash_type_heap_record_key = {
|
|
79
|
-
heap_record_key_cmp_st,
|
|
80
|
-
heap_record_key_hash_st,
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
// Need to implement these functions to support the location-slice based keys
|
|
84
|
-
static st_index_t ddog_location_hash(ddog_prof_Location, st_index_t seed);
|
|
85
|
-
static st_index_t ddog_location_slice_hash(ddog_prof_Slice_Location, st_index_t seed);
|
|
68
|
+
static int heap_record_cmp_st(st_data_t, st_data_t);
|
|
69
|
+
static st_index_t heap_record_hash_st(st_data_t);
|
|
70
|
+
static const struct st_hash_type st_hash_type_heap_record = { .compare = heap_record_cmp_st, .hash = heap_record_hash_st };
|
|
86
71
|
|
|
87
|
-
// A
|
|
88
|
-
//
|
|
72
|
+
// A pending recording is used to defer adding the object to the `weak_objects` map: doing so is a Ruby method
|
|
73
|
+
// call that allocates, and neither of those is safe to do during on_newobj_event
|
|
89
74
|
typedef struct {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
static heap_record* heap_record_new(heap_stack*);
|
|
96
|
-
static void heap_record_free(heap_record*);
|
|
75
|
+
VALUE object_ref;
|
|
76
|
+
long record_id; // Always in Fixnum range
|
|
77
|
+
heap_record *heap_record;
|
|
78
|
+
live_object_data object_data;
|
|
79
|
+
} pending_recording;
|
|
97
80
|
|
|
98
81
|
// An object record is used for storing data about currently tracked live objects
|
|
99
82
|
typedef struct {
|
|
100
|
-
long
|
|
83
|
+
long record_id;
|
|
101
84
|
heap_record *heap_record;
|
|
102
85
|
live_object_data object_data;
|
|
103
86
|
} object_record;
|
|
104
|
-
static object_record* object_record_new(
|
|
105
|
-
static void object_record_free(object_record
|
|
106
|
-
static VALUE object_record_inspect(object_record*);
|
|
107
|
-
static object_record SKIPPED_RECORD = {0};
|
|
87
|
+
static object_record* object_record_new(pending_recording pending);
|
|
88
|
+
static void object_record_free(heap_recorder*, object_record*, bool should_unintern);
|
|
89
|
+
static VALUE object_record_inspect(heap_recorder*, object_record*);
|
|
108
90
|
|
|
109
|
-
|
|
110
|
-
// yet committed.
|
|
111
|
-
typedef struct {
|
|
112
|
-
// Pointer to the (potentially partial) object_record containing metadata about an ongoing recording.
|
|
113
|
-
// When NULL, this symbolizes an unstarted/invalid recording.
|
|
114
|
-
object_record *object_record;
|
|
115
|
-
// A flag to track whether we had to force set the RUBY_FL_SEEN_OBJ_ID flag on this object
|
|
116
|
-
// as part of our workaround around rb_gc_force_recycle issues.
|
|
117
|
-
bool did_recycle_workaround;
|
|
118
|
-
} recording;
|
|
91
|
+
#define MAX_PENDING_RECORDINGS 256
|
|
119
92
|
|
|
120
93
|
struct heap_recorder {
|
|
121
94
|
// Config
|
|
@@ -123,66 +96,234 @@ struct heap_recorder {
|
|
|
123
96
|
bool size_enabled;
|
|
124
97
|
uint sample_rate;
|
|
125
98
|
|
|
126
|
-
// Map[key:
|
|
127
|
-
// NOTE: We always use heap_record_key.type == HEAP_STACK for storage but support lookups
|
|
128
|
-
// via heap_record_key.type == LOCATION_SLICE to allow for allocation-free fast-paths.
|
|
99
|
+
// Map[key: heap_record*, record: nothing] (This is a set, basically)
|
|
129
100
|
// NOTE: This table is currently only protected by the GVL since we never interact with it
|
|
130
101
|
// outside the GVL.
|
|
131
|
-
// NOTE: This table has ownership of
|
|
102
|
+
// NOTE: This table has ownership of its heap_records.
|
|
103
|
+
//
|
|
104
|
+
// This is a cpu/memory trade-off: Maintaining the "heap_records" map means we spend extra CPU when sampling as we need
|
|
105
|
+
// to do de-duplication, but we reduce the memory footprint of the heap profiler.
|
|
106
|
+
// In the future, it may be worth revisiting if we can move this inside libdatadog: if libdatadog was able to track
|
|
107
|
+
// entire stacks for us, then we wouldn't need to do it on the Ruby side.
|
|
132
108
|
st_table *heap_records;
|
|
133
109
|
|
|
134
|
-
// Map[
|
|
110
|
+
// Map[record_id: long, record: object_record*]
|
|
135
111
|
// NOTE: This table is currently only protected by the GVL since we never interact with it
|
|
136
112
|
// outside the GVL.
|
|
137
113
|
// NOTE: This table has ownership of its object_records. The keys are longs and so are
|
|
138
114
|
// passed as values.
|
|
115
|
+
//
|
|
116
|
+
// TODO: @ivoanjo We've evolved to actually never need to look up on object_records (we only insert and iterate),
|
|
117
|
+
// so right now this seems to be just a really really fancy self-resizing list/set.
|
|
118
|
+
// Tests do use it for lookup currently though.
|
|
139
119
|
st_table *object_records;
|
|
140
120
|
|
|
141
|
-
// Map[
|
|
121
|
+
// Map[record_id: long, record: object_record*]
|
|
142
122
|
// NOTE: This is a snapshot of object_records built ahead of a iteration. Outside of an
|
|
143
123
|
// iteration context, this table will be NULL. During an iteration, there will be no
|
|
144
124
|
// mutation of the data so iteration can occur without acquiring a lock.
|
|
145
125
|
// NOTE: Contrary to object_records, this table has no ownership of its data.
|
|
146
126
|
st_table *object_records_snapshot;
|
|
147
|
-
//
|
|
127
|
+
// "Lock" protecting the heap recorder bookkeeping; see "note on locking" below
|
|
128
|
+
uint8_t lock;
|
|
129
|
+
// The GC gen/epoch/count in which we are updating (or last updated if not currently updating).
|
|
148
130
|
//
|
|
149
|
-
// This enables us to calculate the age of
|
|
150
|
-
//
|
|
151
|
-
size_t
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
131
|
+
// This enables us to calculate the age of objects considered in the update by comparing it
|
|
132
|
+
// against an object's alloc_gen.
|
|
133
|
+
size_t update_gen;
|
|
134
|
+
// Whether the current update (or last update if not currently updating) is including old
|
|
135
|
+
// objects or not.
|
|
136
|
+
bool update_include_old;
|
|
137
|
+
// When did we do the last update of heap recorder?
|
|
138
|
+
long last_update_ns;
|
|
139
|
+
|
|
140
|
+
// ObjectSpace::WeakMap[record_id: Integer (Fixnum) => object]
|
|
141
|
+
// Weak references to every object we're tracking.
|
|
142
|
+
// Looking a record_id up returns the object while it's alive, and nil once it's been garbage collected, which
|
|
143
|
+
// is how we determine liveness. See `ruby_weak_map_new` for details on why this gives us a weak reference.
|
|
144
|
+
VALUE weak_objects;
|
|
145
|
+
// Source for the ids used as keys in `object_records` and `weak_objects`. Ids are never reused.
|
|
146
|
+
long next_record_id;
|
|
147
|
+
|
|
148
|
+
// Recordings that are waiting to be committed after on_newobj_event completes.
|
|
149
|
+
// We can't add the object to `weak_objects` during the newobj event, so we store the
|
|
150
|
+
// VALUE reference here and commit it via a postponed job.
|
|
151
|
+
pending_recording pending_recordings[MAX_PENDING_RECORDINGS];
|
|
152
|
+
uint16_t pending_recordings_count;
|
|
153
|
+
|
|
154
|
+
// Reusable arrays, implementing a flyweight pattern for things like iteration
|
|
155
|
+
#define REUSABLE_LOCATIONS_SIZE MAX_FRAMES_LIMIT
|
|
157
156
|
ddog_prof_Location *reusable_locations;
|
|
158
157
|
|
|
158
|
+
#define REUSABLE_FRAME_DETAILS_SIZE (2 * MAX_FRAMES_LIMIT) // because it'll be used for both function names AND file names)
|
|
159
|
+
ddog_prof_ManagedStringId *reusable_ids;
|
|
160
|
+
ddog_CharSlice *reusable_char_slices;
|
|
161
|
+
|
|
159
162
|
// Sampling state
|
|
160
163
|
uint num_recordings_skipped;
|
|
161
164
|
|
|
165
|
+
ddog_prof_ManagedStringStorage string_storage;
|
|
166
|
+
|
|
162
167
|
struct stats_last_update {
|
|
163
168
|
size_t objects_alive;
|
|
164
169
|
size_t objects_dead;
|
|
165
170
|
size_t objects_skipped;
|
|
166
171
|
size_t objects_frozen;
|
|
167
172
|
} stats_last_update;
|
|
168
|
-
};
|
|
169
173
|
|
|
170
|
-
struct
|
|
171
|
-
|
|
172
|
-
|
|
174
|
+
struct stats_lifetime {
|
|
175
|
+
unsigned long updates_successful;
|
|
176
|
+
unsigned long updates_skipped_concurrent;
|
|
177
|
+
unsigned long updates_skipped_gcgen;
|
|
178
|
+
unsigned long updates_skipped_time;
|
|
179
|
+
|
|
180
|
+
double ewma_young_objects_alive;
|
|
181
|
+
double ewma_young_objects_dead;
|
|
182
|
+
double ewma_young_objects_skipped; // Note: Here "young" refers to the young update; objects skipped includes non-young objects
|
|
183
|
+
|
|
184
|
+
double ewma_objects_alive;
|
|
185
|
+
double ewma_objects_dead;
|
|
186
|
+
double ewma_objects_skipped;
|
|
187
|
+
|
|
188
|
+
unsigned long deferred_recordings_skipped_buffer_full;
|
|
189
|
+
unsigned long deferred_recordings_committed;
|
|
190
|
+
} stats_lifetime;
|
|
173
191
|
};
|
|
174
192
|
|
|
193
|
+
// note on locking:
|
|
194
|
+
//
|
|
195
|
+
// The state of the heap profiler (`pending_recordings`, `object_records`, `heap_records`, `weak_objects`) gets mutated from a
|
|
196
|
+
// few different places and, unlike most of the profiler, some of those operations lose the GVL while they work.
|
|
197
|
+
// Thus, unlike most of the profiler, relying only on knowing "we get called with the GVL" is not enough to keep them
|
|
198
|
+
// from stepping on each other, hence this extra "uint8_t lock".
|
|
199
|
+
//
|
|
200
|
+
// Yet, because we only ever touch while holding the GVL, and never lose the GVL between checking it and setting it, a
|
|
201
|
+
// plain field is enough -- no atomics needed. (Hence the "lock" and not a full actual lock)
|
|
202
|
+
//
|
|
203
|
+
// Operations that can be skipped use `heap_recorder_try_lock` and walk away when it's taken (which is almost all of them).
|
|
204
|
+
// The one operation that can't be skipped -- the full update that runs before serialization -- uses
|
|
205
|
+
// `heap_recorder_lock`, which waits.
|
|
206
|
+
//
|
|
207
|
+
// `heap_recorder_lock` is expected to be bounded because once `LOCK_WANTED` gets set, no `try_lock` will succeed, so
|
|
208
|
+
// the `heap_recorder_lock` will only need to wait for the previous user of the lock to finish it's work + Ruby to
|
|
209
|
+
// switch back to is thread.
|
|
210
|
+
#define HEAP_RECORDER_LOCK_HELD 0x1
|
|
211
|
+
#define HEAP_RECORDER_LOCK_WANTED 0x2
|
|
212
|
+
|
|
213
|
+
// Is someone in the middle of a locked operation? (Which, because they may lose the GVL, may be true even though we're
|
|
214
|
+
// the ones currently holding the GVL.)
|
|
215
|
+
static inline bool heap_recorder_is_locked(heap_recorder *heap_recorder) {
|
|
216
|
+
return (heap_recorder->lock & HEAP_RECORDER_LOCK_HELD) != 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Takes the lock, unless it's held or someone is waiting for it. Returns whether it was taken.
|
|
220
|
+
__attribute__((warn_unused_result))
|
|
221
|
+
static inline bool heap_recorder_try_lock(heap_recorder *heap_recorder) {
|
|
222
|
+
if (heap_recorder->lock != 0) return false;
|
|
223
|
+
|
|
224
|
+
heap_recorder->lock = HEAP_RECORDER_LOCK_HELD;
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Takes the lock, waiting for the current holder to finish if needed. See "note on locking" above for why this wait is
|
|
229
|
+
// expected to be bounded.
|
|
230
|
+
//
|
|
231
|
+
// WARN: Do not call this while already holding the lock -- it would wait forever.
|
|
232
|
+
static void heap_recorder_lock(heap_recorder *heap_recorder) {
|
|
233
|
+
while (heap_recorder_is_locked(heap_recorder)) {
|
|
234
|
+
heap_recorder->lock |= HEAP_RECORDER_LOCK_WANTED;
|
|
235
|
+
rb_thread_schedule();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Note that we go from observing the lock as free to taking it without ever losing the GVL, and thus without giving
|
|
239
|
+
// anyone else a chance to take it from under us
|
|
240
|
+
heap_recorder->lock = HEAP_RECORDER_LOCK_HELD;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
static inline void heap_recorder_unlock(heap_recorder *heap_recorder) {
|
|
244
|
+
// Deliberately preserves `HEAP_RECORDER_LOCK_WANTED` (rather than just zero-ing the lock)
|
|
245
|
+
heap_recorder->lock &= ~HEAP_RECORDER_LOCK_HELD;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Same as the above, in the shape `rb_ensure` wants
|
|
249
|
+
static VALUE heap_recorder_unlock_ensure(VALUE heap_recorder_as_value) {
|
|
250
|
+
heap_recorder_unlock((heap_recorder *) heap_recorder_as_value);
|
|
251
|
+
return Qnil;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
typedef struct {
|
|
255
|
+
heap_recorder *heap_recorder;
|
|
256
|
+
bool full_update;
|
|
257
|
+
} heap_recorder_update_locked_args;
|
|
258
|
+
|
|
175
259
|
static heap_record* get_or_create_heap_record(heap_recorder*, ddog_prof_Slice_Location);
|
|
176
260
|
static void cleanup_heap_record_if_unused(heap_recorder*, heap_record*);
|
|
177
261
|
static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, object_record *record);
|
|
178
|
-
static int
|
|
179
|
-
static int
|
|
262
|
+
static int st_heap_record_entry_free_no_unintern(st_data_t, st_data_t, st_data_t);
|
|
263
|
+
static int st_object_record_entry_free_no_unintern(st_data_t, st_data_t, st_data_t);
|
|
180
264
|
static int st_object_record_update(st_data_t, st_data_t, st_data_t);
|
|
181
265
|
static int st_object_records_iterate(st_data_t, st_data_t, st_data_t);
|
|
182
266
|
static int st_object_records_debug(st_data_t key, st_data_t value, st_data_t extra);
|
|
183
|
-
static
|
|
184
|
-
static void commit_recording(heap_recorder
|
|
185
|
-
static
|
|
267
|
+
static void inc_tracked_objects_or_fail(heap_record *heap_record);
|
|
268
|
+
static void commit_recording(heap_recorder *, pending_recording);
|
|
269
|
+
static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update);
|
|
270
|
+
static VALUE heap_recorder_update_locked(VALUE heap_recorder_update_locked_args_as_value);
|
|
271
|
+
static VALUE heap_recorder_commit_recordings_may_lose_gvl_locked(VALUE heap_recorder_as_value);
|
|
272
|
+
static inline double ewma_stat(double previous, double current);
|
|
273
|
+
static void unintern_or_raise(heap_recorder *, ddog_prof_ManagedStringId);
|
|
274
|
+
static void unintern_all_or_raise(heap_recorder *recorder, ddog_prof_Slice_ManagedStringId ids);
|
|
275
|
+
static VALUE get_ruby_string_or_raise(heap_recorder*, ddog_prof_ManagedStringId);
|
|
276
|
+
|
|
277
|
+
// The following global variables are initialized at startup to save expensive lookups later.
|
|
278
|
+
// They are not expected to be mutated outside of init.
|
|
279
|
+
static VALUE class_weak_map = Qnil;
|
|
280
|
+
static ID aref_id = Qnil;
|
|
281
|
+
static ID aset_id = Qnil;
|
|
282
|
+
|
|
283
|
+
void collectors_heap_recorder_init(void) {
|
|
284
|
+
rb_global_variable(&class_weak_map);
|
|
285
|
+
|
|
286
|
+
VALUE module_object_space = rb_const_get(rb_cObject, rb_intern("ObjectSpace"));
|
|
287
|
+
class_weak_map = rb_const_get(module_object_space, rb_intern("WeakMap"));
|
|
288
|
+
aref_id = rb_intern("[]");
|
|
289
|
+
aset_id = rb_intern("[]=");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Native wrapper to create a new `ObjectSpace::WeakMap`.
|
|
293
|
+
//
|
|
294
|
+
// Because an `ObjectSpace::WeakMap` entry is dropped as soon as *either* its key or its value is garbage
|
|
295
|
+
// collected, pairing a key that can never be collected (such as a fixnum) with the object of interest as the
|
|
296
|
+
// value gives us a weak reference: reading the key back returns the object while it's alive, and nothing once
|
|
297
|
+
// it's been collected.
|
|
298
|
+
static VALUE ruby_weak_map_new(void) {
|
|
299
|
+
return rb_class_new_instance(0, NULL, class_weak_map);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Native wrapper to get an object from an `ObjectSpace::WeakMap`.
|
|
303
|
+
// Returns the object on success and nil if the entry is gone, meaning
|
|
304
|
+
// the object has been garbage collected.
|
|
305
|
+
// We never store nil as a value, see ruby_weak_map_set_may_lose_gvl_and_allocate_objects(), so nil unambiguously
|
|
306
|
+
// means "the value was garbage collected".
|
|
307
|
+
//
|
|
308
|
+
// Note: GVL can be released and other threads may get to run before this method returns
|
|
309
|
+
static VALUE ruby_weak_map_get_may_lose_gvl(VALUE weak_map, VALUE key) {
|
|
310
|
+
return rb_funcall(weak_map, aref_id, 1, key);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Native wrapper to add an entry to an `ObjectSpace::WeakMap`.
|
|
314
|
+
// Raises RuntimeError if passed nil as a value.
|
|
315
|
+
//
|
|
316
|
+
// Note: GVL can be released and other threads may get to run before this method returns.
|
|
317
|
+
//
|
|
318
|
+
// On Ruby < 3.3 this allocates Ruby objects (because underneath the weak map registers
|
|
319
|
+
// finalizers for the objects, which requires allocations). Later Rubies improved WeakMap to not need this.
|
|
320
|
+
// This we need to be careful not to recurse on the profiler (e.g. NEWOBJ tracepoint).
|
|
321
|
+
static void ruby_weak_map_set_may_lose_gvl_and_allocate_objects(VALUE weak_map, VALUE key, VALUE value) {
|
|
322
|
+
if (value == Qnil) {
|
|
323
|
+
raise_error(rb_eRuntimeError, "Can't use nil as the value in the WeakMap, otherwise #[] can't differentiate alive vs nil value");
|
|
324
|
+
}
|
|
325
|
+
rb_funcall(weak_map, aset_id, 2, key, value);
|
|
326
|
+
}
|
|
186
327
|
|
|
187
328
|
// ==========================
|
|
188
329
|
// Heap Recorder External API
|
|
@@ -193,16 +334,21 @@ static VALUE end_heap_allocation_recording(VALUE end_heap_allocation_args);
|
|
|
193
334
|
// happens under the GVL.
|
|
194
335
|
//
|
|
195
336
|
// ==========================
|
|
196
|
-
heap_recorder* heap_recorder_new(
|
|
337
|
+
heap_recorder* heap_recorder_new(ddog_prof_ManagedStringStorage string_storage) {
|
|
197
338
|
heap_recorder *recorder = ruby_xcalloc(1, sizeof(heap_recorder));
|
|
198
339
|
|
|
199
|
-
recorder->heap_records = st_init_table(&
|
|
340
|
+
recorder->heap_records = st_init_table(&st_hash_type_heap_record);
|
|
200
341
|
recorder->object_records = st_init_numtable();
|
|
201
342
|
recorder->object_records_snapshot = NULL;
|
|
202
|
-
recorder->reusable_locations = ruby_xcalloc(
|
|
203
|
-
recorder->
|
|
343
|
+
recorder->reusable_locations = ruby_xcalloc(REUSABLE_LOCATIONS_SIZE, sizeof(ddog_prof_Location));
|
|
344
|
+
recorder->reusable_ids = ruby_xcalloc(REUSABLE_FRAME_DETAILS_SIZE, sizeof(ddog_prof_ManagedStringId));
|
|
345
|
+
recorder->reusable_char_slices = ruby_xcalloc(REUSABLE_FRAME_DETAILS_SIZE, sizeof(ddog_CharSlice));
|
|
204
346
|
recorder->size_enabled = true;
|
|
205
347
|
recorder->sample_rate = 1; // By default do no sampling on top of what allocation profiling already does
|
|
348
|
+
recorder->string_storage = string_storage;
|
|
349
|
+
// Note: This allocates, and thus can trigger a GC. That's fine: our caller only publishes the heap recorder on the
|
|
350
|
+
// stack recorder state after we return, so `heap_recorder_mark` will not observe a half-initialized recorder.
|
|
351
|
+
recorder->weak_objects = ruby_weak_map_new();
|
|
206
352
|
|
|
207
353
|
return recorder;
|
|
208
354
|
}
|
|
@@ -218,20 +364,24 @@ void heap_recorder_free(heap_recorder *heap_recorder) {
|
|
|
218
364
|
heap_recorder_finish_iteration(heap_recorder);
|
|
219
365
|
}
|
|
220
366
|
|
|
367
|
+
// NOTE: We don't unintern the strings referenced by the records we're about to free, thus we use
|
|
368
|
+
// `..._no_unintern` and `should_unintern: false`. This is intentional: `heap_recorder_free` is only ever called as part of
|
|
369
|
+
// tearing down the entire stack recorder, and the caller drops the whole managed string storage right after
|
|
370
|
+
// (see `stack_recorder_typed_data_free`), so there's no need to spend effort updating the managed string table.
|
|
371
|
+
// Crucially, this also keeps us from crashing: this code runs from the stack recorder's GC free callback, and
|
|
372
|
+
// because uninterning can fail, we can't raise exceptions in the middle of a dfree.
|
|
373
|
+
|
|
221
374
|
// Clean-up all object records
|
|
222
|
-
st_foreach(heap_recorder->object_records,
|
|
375
|
+
st_foreach(heap_recorder->object_records, st_object_record_entry_free_no_unintern, (st_data_t) heap_recorder);
|
|
223
376
|
st_free_table(heap_recorder->object_records);
|
|
224
377
|
|
|
225
378
|
// Clean-up all heap records (this includes those only referred to by queued_samples)
|
|
226
|
-
st_foreach(heap_recorder->heap_records,
|
|
379
|
+
st_foreach(heap_recorder->heap_records, st_heap_record_entry_free_no_unintern, (st_data_t) heap_recorder);
|
|
227
380
|
st_free_table(heap_recorder->heap_records);
|
|
228
381
|
|
|
229
|
-
if (heap_recorder->active_recording.object_record != NULL && heap_recorder->active_recording.object_record != &SKIPPED_RECORD) {
|
|
230
|
-
// If there's a partial object record, clean it up as well
|
|
231
|
-
object_record_free(heap_recorder->active_recording.object_record);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
382
|
ruby_xfree(heap_recorder->reusable_locations);
|
|
383
|
+
ruby_xfree(heap_recorder->reusable_ids);
|
|
384
|
+
ruby_xfree(heap_recorder->reusable_char_slices);
|
|
235
385
|
|
|
236
386
|
ruby_xfree(heap_recorder);
|
|
237
387
|
}
|
|
@@ -250,7 +400,7 @@ void heap_recorder_set_sample_rate(heap_recorder *heap_recorder, int sample_rate
|
|
|
250
400
|
}
|
|
251
401
|
|
|
252
402
|
if (sample_rate <= 0) {
|
|
253
|
-
|
|
403
|
+
raise_error(rb_eArgError, "Heap sample rate must be a positive integer value but was %d", sample_rate);
|
|
254
404
|
}
|
|
255
405
|
|
|
256
406
|
heap_recorder->sample_rate = sample_rate;
|
|
@@ -272,124 +422,253 @@ void heap_recorder_after_fork(heap_recorder *heap_recorder) {
|
|
|
272
422
|
// simply be noticed on next heap_recorder_prepare_iteration.
|
|
273
423
|
//
|
|
274
424
|
// There is one small caveat though: fork only preserves one thread and in a Ruby app, that
|
|
275
|
-
// will be the thread holding
|
|
276
|
-
// outside of the GVL, any state specific to that interaction may be
|
|
425
|
+
// will be the thread holding the GVL. Since we support iteration on the heap recorder
|
|
426
|
+
// outside of the GVL, any state specific to that interaction may be inconsistent after fork
|
|
277
427
|
// (e.g. an acquired lock for thread safety). Iteration operates on object_records_snapshot
|
|
278
428
|
// though and that one will be updated on next heap_recorder_prepare_iteration so we really
|
|
279
429
|
// only need to finish any iteration that might have been left unfinished.
|
|
280
430
|
if (heap_recorder->object_records_snapshot != NULL) {
|
|
281
431
|
heap_recorder_finish_iteration(heap_recorder);
|
|
282
432
|
}
|
|
433
|
+
|
|
434
|
+
// This could also be left over if fork happens in the middle of a locked operation
|
|
435
|
+
heap_recorder->lock = 0;
|
|
436
|
+
|
|
437
|
+
// Clear lifetime stats since this is essentially a new heap recorder
|
|
438
|
+
heap_recorder->stats_lifetime = (struct stats_lifetime) {0};
|
|
283
439
|
}
|
|
284
440
|
|
|
285
|
-
|
|
441
|
+
// This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
|
|
442
|
+
// Ruby heap nor release the GVL (https://github.com/DataDog/dd-trace-rb/pull/4240).
|
|
443
|
+
//
|
|
444
|
+
// See the header for details on the arguments and on `needs_commit`.
|
|
445
|
+
void heap_recorder_record_allocation(
|
|
446
|
+
heap_recorder *heap_recorder,
|
|
447
|
+
VALUE new_object,
|
|
448
|
+
unsigned int weight,
|
|
449
|
+
ddog_CharSlice alloc_class,
|
|
450
|
+
ddog_prof_Slice_Location locations,
|
|
451
|
+
bool *needs_commit
|
|
452
|
+
) {
|
|
286
453
|
if (heap_recorder == NULL) {
|
|
454
|
+
*needs_commit = false;
|
|
287
455
|
return;
|
|
288
456
|
}
|
|
289
457
|
|
|
290
|
-
if
|
|
291
|
-
|
|
458
|
+
// We always report `needs_commit` when there's anything pending, even if it's from a previous allocation.
|
|
459
|
+
//
|
|
460
|
+
// This can mean we "spam" a bit the postponed jobs mechanism (e.g. perhaps our postponed job hasn't run
|
|
461
|
+
// because there's a native extension doing Ruby object allocations in a row without giving Ruby the chance to
|
|
462
|
+
// run interrupts.
|
|
463
|
+
// That shouldn't be a problem because the allocation profiler has the dynamic sampling rate mechanism +
|
|
464
|
+
// triggering postponed jobs is cheap on current Rubies.
|
|
465
|
+
// (Note that asking for the same job multiple times de-duplicates -- Ruby will only run it once when it gets the chance)
|
|
466
|
+
*needs_commit = heap_recorder->pending_recordings_count > 0;
|
|
467
|
+
|
|
468
|
+
if (++heap_recorder->num_recordings_skipped < heap_recorder->sample_rate) return;
|
|
469
|
+
|
|
470
|
+
if (heap_recorder_is_locked(heap_recorder)) {
|
|
471
|
+
// If we got unlucky and an allocation showed up in the middle of a locked operation (because it triggered
|
|
472
|
+
// an allocation directly OR because that operation lost the GVL), let's skip this sample as well.
|
|
473
|
+
// (Note we don't take the lock ourselves: the current function never loses the GVL, and is thus already atomic.)
|
|
474
|
+
return;
|
|
292
475
|
}
|
|
293
476
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
477
|
+
// Do not sample internal objects in the heap profiler because they are added to a ObjectSpace::WeakMap, which would not be safe.
|
|
478
|
+
// This means we ignore internal VM objects samples for the heap profiler,
|
|
479
|
+
// which is a trade-off discussed in https://github.com/DataDog/dd-trace-rb/pull/6176#discussion_r3819776251
|
|
480
|
+
//
|
|
481
|
+
// Note this is checked after the sample rate above, as the sample rate is in number of allocation profiler samples,
|
|
482
|
+
// not in number of non-internal samples. Like the skips above, num_recordings_skipped is not reset here, so the
|
|
483
|
+
// next allocation sample still gets a chance to be tracked.
|
|
484
|
+
if (ddtrace_is_internal_object_p(new_object)) return;
|
|
485
|
+
|
|
486
|
+
// Skip if we've hit the pending recordings limit
|
|
487
|
+
if (heap_recorder->pending_recordings_count >= MAX_PENDING_RECORDINGS) {
|
|
488
|
+
heap_recorder->stats_lifetime.deferred_recordings_skipped_buffer_full++;
|
|
489
|
+
return; // `needs_commit` is already true: a full buffer definitely has something to commit
|
|
298
490
|
}
|
|
299
491
|
|
|
300
492
|
heap_recorder->num_recordings_skipped = 0;
|
|
301
493
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
494
|
+
// Note: Everything that can raise happens before we add the recording to `pending_recordings` below, so that a
|
|
495
|
+
// failure never leaves a half-built entry behind
|
|
496
|
+
live_object_data object_data = {
|
|
497
|
+
.weight = weight * heap_recorder->sample_rate,
|
|
498
|
+
.class = intern_or_raise(heap_recorder->string_storage, alloc_class),
|
|
499
|
+
.alloc_gen = rb_gc_count(),
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
long record_id = ++heap_recorder->next_record_id;
|
|
503
|
+
if (record_id > RUBY_FIXNUM_MAX) {
|
|
504
|
+
// We rely on record_id always fitting in a FIXNUM to avoid extra overhead and
|
|
505
|
+
// allocation here. If we exhaust the record_id, we stop profiling.
|
|
506
|
+
// We don't expect this to happen in practice, as we don't heap sample every object, and RUBY_FIXNUM_MAX is (2**62 - 1)
|
|
507
|
+
raise_error(rb_eRuntimeError, "Heap profiling: Exhausted usable record_ids");
|
|
305
508
|
}
|
|
306
509
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
// that non-noop implementations of rb_gc_force_recycle have an implementation bug
|
|
318
|
-
// which results in the object that re-used the recycled slot inheriting the same
|
|
319
|
-
// object id without setting the FL_SEEN_OBJ_ID flag. We rely on this knowledge to
|
|
320
|
-
// "observe" implicit frees when an object we are tracking is force-recycled.
|
|
321
|
-
//
|
|
322
|
-
// However, it may happen that we start tracking a new object and that object was
|
|
323
|
-
// allocated on a recycled slot. Due to the bug, this object would be missing the
|
|
324
|
-
// FL_SEEN_OBJ_ID flag even though it was not recycled itself. If we left it be,
|
|
325
|
-
// when we're doing our liveness check, the absence of the flag would trigger our
|
|
326
|
-
// implicit free workaround and the object would be inferred as recycled even though
|
|
327
|
-
// it might still be alive.
|
|
328
|
-
//
|
|
329
|
-
// Thus, if we detect that this new allocation is already missing the flag at the start
|
|
330
|
-
// of the heap allocation recording, we force-set it. This should be safe since we
|
|
331
|
-
// just called rb_obj_id on it above and the expectation is that any flaggable object
|
|
332
|
-
// that goes through it ends up with the flag set (as evidenced by the GC_ASSERT
|
|
333
|
-
// lines in https://github.com/ruby/ruby/blob/4a8d7246d15b2054eacb20f8ab3d29d39a3e7856/gc.c#L4050C14-L4050C14).
|
|
334
|
-
if (RB_FL_ABLE(new_obj) && !RB_FL_TEST(new_obj, RUBY_FL_SEEN_OBJ_ID)) {
|
|
335
|
-
RB_FL_SET(new_obj, RUBY_FL_SEEN_OBJ_ID);
|
|
336
|
-
did_recycle_workaround = true;
|
|
337
|
-
}
|
|
338
|
-
#endif
|
|
339
|
-
|
|
340
|
-
heap_recorder->active_recording = (recording) {
|
|
341
|
-
.object_record = object_record_new(FIX2LONG(ruby_obj_id), NULL, (live_object_data) {
|
|
342
|
-
.weight = weight * heap_recorder->sample_rate,
|
|
343
|
-
.class = alloc_class != NULL ? string_from_char_slice(*alloc_class) : NULL,
|
|
344
|
-
.alloc_gen = rb_gc_count(),
|
|
345
|
-
}),
|
|
346
|
-
.did_recycle_workaround = did_recycle_workaround,
|
|
510
|
+
heap_record *heap_record = get_or_create_heap_record(heap_recorder, locations);
|
|
511
|
+
inc_tracked_objects_or_fail(heap_record);
|
|
512
|
+
|
|
513
|
+
// We can't add the object to `weak_objects` from inside the NEWOBJ tracepoint, so the recording stays pending until
|
|
514
|
+
// `heap_recorder_commit_recordings_may_lose_gvl` gets to it
|
|
515
|
+
heap_recorder->pending_recordings[heap_recorder->pending_recordings_count++] = (pending_recording) {
|
|
516
|
+
.object_ref = new_object,
|
|
517
|
+
.record_id = record_id,
|
|
518
|
+
.heap_record = heap_record,
|
|
519
|
+
.object_data = object_data,
|
|
347
520
|
};
|
|
521
|
+
|
|
522
|
+
*needs_commit = true; // We just added a recording
|
|
348
523
|
}
|
|
349
524
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
struct end_heap_allocation_args end_heap_allocation_args = {
|
|
357
|
-
.heap_recorder = heap_recorder,
|
|
358
|
-
.locations = locations,
|
|
359
|
-
};
|
|
360
|
-
rb_protect(end_heap_allocation_recording, (VALUE) &end_heap_allocation_args, &exception_state);
|
|
361
|
-
return exception_state;
|
|
525
|
+
void heap_recorder_update_young_objects(heap_recorder *heap_recorder) {
|
|
526
|
+
if (heap_recorder == NULL) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
heap_recorder_update(heap_recorder, /* full_update: */ false);
|
|
362
531
|
}
|
|
363
532
|
|
|
364
|
-
|
|
365
|
-
|
|
533
|
+
void heap_recorder_commit_recordings_may_lose_gvl(heap_recorder *heap_recorder) {
|
|
534
|
+
if (heap_recorder == NULL) {
|
|
535
|
+
return; // Nothing to do
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (!heap_recorder_try_lock(heap_recorder)) {
|
|
539
|
+
// Something else is busy; `heap_recorder_record_allocation` will ask for us to be called again later
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
rb_ensure(
|
|
544
|
+
heap_recorder_commit_recordings_may_lose_gvl_locked,
|
|
545
|
+
(VALUE) heap_recorder,
|
|
546
|
+
heap_recorder_unlock_ensure,
|
|
547
|
+
(VALUE) heap_recorder
|
|
548
|
+
);
|
|
549
|
+
}
|
|
366
550
|
|
|
367
|
-
|
|
368
|
-
|
|
551
|
+
static VALUE heap_recorder_commit_recordings_may_lose_gvl_locked(VALUE heap_recorder_as_value) {
|
|
552
|
+
heap_recorder *heap_recorder = (struct heap_recorder *) heap_recorder_as_value;
|
|
369
553
|
|
|
554
|
+
// Note: We consume the buffer back-to-front, and only drop each entry once it's fully committed. That makes the
|
|
555
|
+
// "can lose the GVL" bit below safe to retry: if anything raises, or another thread unwinds us, the entry stays
|
|
556
|
+
// pending and a later commit picks it up -- and setting the same entry on the weak map again is harmless. It also
|
|
557
|
+
// means the entry we're working on stays marked (see `heap_recorder_mark`) across any GC that happens while we don't
|
|
558
|
+
// have the GVL.
|
|
559
|
+
//
|
|
560
|
+
// Note as well that `ruby_weak_map_set_may_lose_gvl_and_allocate_objects` below can lose the GVL (and, on older
|
|
561
|
+
// Rubies, allocate). This function gets called while holding the heap recorder lock so that no other heap recorder
|
|
562
|
+
// operation can concurrently execute with us in that window (including recording more allocations, which on older Rubies
|
|
563
|
+
// would otherwise add entries to the very buffer we're draining).
|
|
564
|
+
while (heap_recorder->pending_recordings_count > 0) {
|
|
565
|
+
pending_recording pending = heap_recorder->pending_recordings[heap_recorder->pending_recordings_count - 1];
|
|
566
|
+
|
|
567
|
+
ruby_weak_map_set_may_lose_gvl_and_allocate_objects(heap_recorder->weak_objects, LONG2FIX(pending.record_id), pending.object_ref);
|
|
568
|
+
|
|
569
|
+
// After here, we can no longer lose the GVL until we loop around again, so these steps are "atomic"
|
|
570
|
+
|
|
571
|
+
commit_recording(heap_recorder, pending);
|
|
572
|
+
heap_recorder->pending_recordings_count--;
|
|
573
|
+
heap_recorder->stats_lifetime.deferred_recordings_committed++;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return Qnil;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Mark the Ruby objects the heap recorder holds on to.
|
|
580
|
+
//
|
|
581
|
+
// Note that the objects we track are deliberately **not** marked here: `weak_objects` holds only weak references
|
|
582
|
+
// to them, which is exactly what lets us detect that they've been garbage collected.
|
|
583
|
+
void heap_recorder_mark(heap_recorder *heap_recorder) {
|
|
370
584
|
if (heap_recorder == NULL) {
|
|
371
|
-
return
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Mark pending recordings while they're waiting to be committed, otherwise it won't be safe to read them later.
|
|
589
|
+
// We would not mind if they are GC'd, but we would need to know that reliably and we can't.
|
|
590
|
+
for (uint i = 0; i < heap_recorder->pending_recordings_count; i++) {
|
|
591
|
+
rb_gc_mark(heap_recorder->pending_recordings[i].object_ref);
|
|
372
592
|
}
|
|
373
593
|
|
|
374
|
-
|
|
594
|
+
rb_gc_mark(heap_recorder->weak_objects);
|
|
595
|
+
}
|
|
375
596
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
597
|
+
// NOTE: This function needs and assumes it gets called with the GVL being held.
|
|
598
|
+
// But importantly **some of the operations inside `st_object_record_update` may cause a thread switch**,
|
|
599
|
+
// so we can't assume a single update happens in a single "atomic" step -- other threads may get some running time
|
|
600
|
+
// in the meanwhile.
|
|
601
|
+
static void heap_recorder_update(heap_recorder *heap_recorder, bool full_update) {
|
|
602
|
+
if (full_update) {
|
|
603
|
+
// A full update runs as part of serialization and can't be skipped, so we wait for our turn if needed
|
|
604
|
+
heap_recorder_lock(heap_recorder);
|
|
605
|
+
} else if (!heap_recorder_try_lock(heap_recorder)) {
|
|
606
|
+
// Non-full updates are optional, so let's walk away
|
|
607
|
+
heap_recorder->stats_lifetime.updates_skipped_concurrent++;
|
|
608
|
+
return;
|
|
379
609
|
}
|
|
380
|
-
// From now on, mark the global active recording as invalid so we can short-circuit at any point
|
|
381
|
-
// and not end up with a still active recording. the local active_recording still holds the
|
|
382
|
-
// data required for committing though.
|
|
383
|
-
heap_recorder->active_recording = (recording) {0};
|
|
384
610
|
|
|
385
|
-
|
|
611
|
+
heap_recorder_update_locked_args args = {.heap_recorder = heap_recorder, .full_update = full_update};
|
|
612
|
+
rb_ensure(heap_recorder_update_locked, (VALUE) &args, heap_recorder_unlock_ensure, (VALUE) heap_recorder);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
static VALUE heap_recorder_update_locked(VALUE heap_recorder_update_locked_args_as_value) {
|
|
616
|
+
heap_recorder_update_locked_args *args = (heap_recorder_update_locked_args *) heap_recorder_update_locked_args_as_value;
|
|
617
|
+
heap_recorder *heap_recorder = args->heap_recorder;
|
|
618
|
+
bool full_update = args->full_update;
|
|
619
|
+
|
|
620
|
+
if (heap_recorder->object_records_snapshot != NULL) {
|
|
621
|
+
// While serialization is happening, it runs without the GVL and uses the object_records_snapshot.
|
|
622
|
+
// Although we iterate on a snapshot of object_records, these records point to other data that has not been
|
|
623
|
+
// snapshotted for efficiency reasons (e.g. heap_records). Since updating may invalidate
|
|
624
|
+
// some of that non-snapshotted data, let's refrain from doing updates during iteration. This also enforces the
|
|
625
|
+
// semantic that iteration will operate as a point-in-time snapshot.
|
|
386
626
|
return Qnil;
|
|
387
627
|
}
|
|
388
628
|
|
|
389
|
-
|
|
629
|
+
size_t current_gc_gen = rb_gc_count();
|
|
630
|
+
long now_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
|
631
|
+
|
|
632
|
+
if (!full_update) {
|
|
633
|
+
if (current_gc_gen == heap_recorder->update_gen) {
|
|
634
|
+
// Are we still in the same GC gen as last update? If so, skip updating since things should not have
|
|
635
|
+
// changed significantly since last time.
|
|
636
|
+
// NOTE: This is mostly a performance decision. I suppose some objects may be cleaned up in intermediate
|
|
637
|
+
// GC steps and sizes may change. But because we have to iterate through all our tracked
|
|
638
|
+
// object records to do an update, let's wait until all steps for a particular GC generation
|
|
639
|
+
// have finished to do so. We may revisit this once we have a better liveness checking mechanism.
|
|
640
|
+
heap_recorder->stats_lifetime.updates_skipped_gcgen++;
|
|
641
|
+
return Qnil;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (now_ns > 0 && (now_ns - heap_recorder->last_update_ns) < MIN_TIME_BETWEEN_HEAP_RECORDER_UPDATES_NS) {
|
|
645
|
+
// We did an update not too long ago. Let's skip this one to avoid over-taxing the system.
|
|
646
|
+
heap_recorder->stats_lifetime.updates_skipped_time++;
|
|
647
|
+
return Qnil;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
390
650
|
|
|
391
|
-
//
|
|
392
|
-
|
|
651
|
+
// Reset last update stats, we'll be building them from scratch during the st_foreach call below
|
|
652
|
+
heap_recorder->stats_last_update = (struct stats_last_update) {0};
|
|
653
|
+
|
|
654
|
+
heap_recorder->update_gen = current_gc_gen;
|
|
655
|
+
heap_recorder->update_include_old = full_update;
|
|
656
|
+
|
|
657
|
+
st_foreach(heap_recorder->object_records, st_object_record_update, (st_data_t) heap_recorder);
|
|
658
|
+
|
|
659
|
+
heap_recorder->last_update_ns = now_ns;
|
|
660
|
+
heap_recorder->stats_lifetime.updates_successful++;
|
|
661
|
+
|
|
662
|
+
// Lifetime stats updating
|
|
663
|
+
if (!full_update) {
|
|
664
|
+
heap_recorder->stats_lifetime.ewma_young_objects_alive = ewma_stat(heap_recorder->stats_lifetime.ewma_young_objects_alive, heap_recorder->stats_last_update.objects_alive);
|
|
665
|
+
heap_recorder->stats_lifetime.ewma_young_objects_dead = ewma_stat(heap_recorder->stats_lifetime.ewma_young_objects_dead, heap_recorder->stats_last_update.objects_dead);
|
|
666
|
+
heap_recorder->stats_lifetime.ewma_young_objects_skipped = ewma_stat(heap_recorder->stats_lifetime.ewma_young_objects_skipped, heap_recorder->stats_last_update.objects_skipped);
|
|
667
|
+
} else {
|
|
668
|
+
heap_recorder->stats_lifetime.ewma_objects_alive = ewma_stat(heap_recorder->stats_lifetime.ewma_objects_alive, heap_recorder->stats_last_update.objects_alive);
|
|
669
|
+
heap_recorder->stats_lifetime.ewma_objects_dead = ewma_stat(heap_recorder->stats_lifetime.ewma_objects_dead, heap_recorder->stats_last_update.objects_dead);
|
|
670
|
+
heap_recorder->stats_lifetime.ewma_objects_skipped = ewma_stat(heap_recorder->stats_lifetime.ewma_objects_skipped, heap_recorder->stats_last_update.objects_skipped);
|
|
671
|
+
}
|
|
393
672
|
|
|
394
673
|
return Qnil;
|
|
395
674
|
}
|
|
@@ -399,21 +678,16 @@ void heap_recorder_prepare_iteration(heap_recorder *heap_recorder) {
|
|
|
399
678
|
return;
|
|
400
679
|
}
|
|
401
680
|
|
|
402
|
-
heap_recorder->iteration_gen = rb_gc_count();
|
|
403
|
-
|
|
404
681
|
if (heap_recorder->object_records_snapshot != NULL) {
|
|
405
682
|
// we could trivially handle this but we raise to highlight and catch unexpected usages.
|
|
406
|
-
|
|
683
|
+
raise_error(rb_eRuntimeError, "New heap recorder iteration prepared without the previous one having been finished.");
|
|
407
684
|
}
|
|
408
685
|
|
|
409
|
-
|
|
410
|
-
heap_recorder->stats_last_update = (struct stats_last_update) {};
|
|
411
|
-
|
|
412
|
-
st_foreach(heap_recorder->object_records, st_object_record_update, (st_data_t) heap_recorder);
|
|
686
|
+
heap_recorder_update(heap_recorder, /* full_update: */ true);
|
|
413
687
|
|
|
414
688
|
heap_recorder->object_records_snapshot = st_copy(heap_recorder->object_records);
|
|
415
689
|
if (heap_recorder->object_records_snapshot == NULL) {
|
|
416
|
-
|
|
690
|
+
raise_error(rb_eRuntimeError, "Failed to create heap snapshot.");
|
|
417
691
|
}
|
|
418
692
|
}
|
|
419
693
|
|
|
@@ -424,7 +698,7 @@ void heap_recorder_finish_iteration(heap_recorder *heap_recorder) {
|
|
|
424
698
|
|
|
425
699
|
if (heap_recorder->object_records_snapshot == NULL) {
|
|
426
700
|
// we could trivially handle this but we raise to highlight and catch unexpected usages.
|
|
427
|
-
|
|
701
|
+
raise_error(rb_eRuntimeError, "Heap recorder iteration finished without having been prepared.");
|
|
428
702
|
}
|
|
429
703
|
|
|
430
704
|
st_free_table(heap_recorder->object_records_snapshot);
|
|
@@ -466,96 +740,103 @@ bool heap_recorder_for_each_live_object(
|
|
|
466
740
|
|
|
467
741
|
VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder) {
|
|
468
742
|
VALUE arguments[] = {
|
|
469
|
-
ID2SYM(rb_intern("num_object_records")), /* => */
|
|
470
|
-
ID2SYM(rb_intern("num_heap_records")), /* => */
|
|
743
|
+
ID2SYM(rb_intern("num_object_records")), /* => */ ULONG2NUM(heap_recorder->object_records->num_entries),
|
|
744
|
+
ID2SYM(rb_intern("num_heap_records")), /* => */ ULONG2NUM(heap_recorder->heap_records->num_entries),
|
|
745
|
+
ID2SYM(rb_intern("pending_recordings_count")), /* => */ ULONG2NUM(heap_recorder->pending_recordings_count),
|
|
471
746
|
|
|
472
747
|
// Stats as of last update
|
|
473
|
-
ID2SYM(rb_intern("last_update_objects_alive")), /* => */
|
|
474
|
-
ID2SYM(rb_intern("last_update_objects_dead")), /* => */
|
|
475
|
-
ID2SYM(rb_intern("last_update_objects_skipped")), /* => */
|
|
476
|
-
ID2SYM(rb_intern("last_update_objects_frozen")), /* => */
|
|
748
|
+
ID2SYM(rb_intern("last_update_objects_alive")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_alive),
|
|
749
|
+
ID2SYM(rb_intern("last_update_objects_dead")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_dead),
|
|
750
|
+
ID2SYM(rb_intern("last_update_objects_skipped")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_skipped),
|
|
751
|
+
ID2SYM(rb_intern("last_update_objects_frozen")), /* => */ ULONG2NUM(heap_recorder->stats_last_update.objects_frozen),
|
|
752
|
+
|
|
753
|
+
// Lifetime stats
|
|
754
|
+
ID2SYM(rb_intern("lifetime_updates_successful")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_successful),
|
|
755
|
+
ID2SYM(rb_intern("lifetime_updates_skipped_concurrent")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_skipped_concurrent),
|
|
756
|
+
ID2SYM(rb_intern("lifetime_updates_skipped_gcgen")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_skipped_gcgen),
|
|
757
|
+
ID2SYM(rb_intern("lifetime_updates_skipped_time")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.updates_skipped_time),
|
|
758
|
+
ID2SYM(rb_intern("lifetime_ewma_young_objects_alive")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_alive),
|
|
759
|
+
ID2SYM(rb_intern("lifetime_ewma_young_objects_dead")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_dead),
|
|
760
|
+
// Note: Here "young" refers to the young update; objects skipped includes non-young objects
|
|
761
|
+
ID2SYM(rb_intern("lifetime_ewma_young_objects_skipped")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_young_objects_skipped),
|
|
762
|
+
ID2SYM(rb_intern("lifetime_ewma_objects_alive")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_alive),
|
|
763
|
+
ID2SYM(rb_intern("lifetime_ewma_objects_dead")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_dead),
|
|
764
|
+
ID2SYM(rb_intern("lifetime_ewma_objects_skipped")), /* => */ DBL2NUM(heap_recorder->stats_lifetime.ewma_objects_skipped),
|
|
765
|
+
|
|
766
|
+
ID2SYM(rb_intern("lifetime_deferred_recordings_skipped_buffer_full")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.deferred_recordings_skipped_buffer_full),
|
|
767
|
+
ID2SYM(rb_intern("lifetime_deferred_recordings_committed")), /* => */ ULONG2NUM(heap_recorder->stats_lifetime.deferred_recordings_committed),
|
|
477
768
|
};
|
|
478
769
|
VALUE hash = rb_hash_new();
|
|
479
770
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
|
|
771
|
+
|
|
480
772
|
return hash;
|
|
481
773
|
}
|
|
482
774
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
.heap_stack = stack,
|
|
488
|
-
};
|
|
489
|
-
heap_record_key location_based_key = (heap_record_key) {
|
|
490
|
-
.type = LOCATION_SLICE,
|
|
491
|
-
.location_slice = &locations,
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
st_index_t stack_hash = heap_record_key_hash_st((st_data_t) &stack_based_key);
|
|
495
|
-
st_index_t location_hash = heap_record_key_hash_st((st_data_t) &location_based_key);
|
|
496
|
-
|
|
497
|
-
heap_stack_free(stack);
|
|
498
|
-
|
|
499
|
-
if (stack_hash != location_hash) {
|
|
500
|
-
rb_raise(rb_eRuntimeError, "Heap record key hashes built from the same locations differ. stack_based_hash=%"PRI_VALUE_PREFIX"u location_based_hash=%"PRI_VALUE_PREFIX"u", stack_hash, location_hash);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
775
|
+
typedef struct {
|
|
776
|
+
heap_recorder *recorder;
|
|
777
|
+
VALUE debug_ary;
|
|
778
|
+
} debug_context;
|
|
503
779
|
|
|
504
780
|
VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder) {
|
|
505
781
|
if (heap_recorder == NULL) {
|
|
506
|
-
|
|
782
|
+
raise_error(rb_eArgError, "heap_recorder is NULL");
|
|
507
783
|
}
|
|
508
784
|
|
|
509
|
-
VALUE
|
|
510
|
-
|
|
511
|
-
|
|
785
|
+
VALUE debug_ary = rb_ary_new();
|
|
786
|
+
debug_context context = (debug_context) {.recorder = heap_recorder, .debug_ary = debug_ary};
|
|
787
|
+
st_foreach(heap_recorder->object_records, st_object_records_debug, (st_data_t) &context);
|
|
788
|
+
|
|
789
|
+
return rb_ary_new_from_args(2,
|
|
790
|
+
rb_ary_new_from_args(2, ID2SYM(rb_intern("records")), debug_ary),
|
|
791
|
+
rb_ary_new_from_args(2, ID2SYM(rb_intern("state")), heap_recorder_state_snapshot(heap_recorder))
|
|
792
|
+
);
|
|
512
793
|
}
|
|
513
794
|
|
|
514
795
|
// ==========================
|
|
515
796
|
// Heap Recorder Internal API
|
|
516
797
|
// ==========================
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
heap_record_free((heap_record *)
|
|
798
|
+
// NOTE: Only expected to be used from heap_recorder_free, which will separately destroy the string table
|
|
799
|
+
static int st_heap_record_entry_free_no_unintern(st_data_t key, DDTRACE_UNUSED st_data_t value, st_data_t extra_arg) {
|
|
800
|
+
heap_recorder *recorder = (heap_recorder *) extra_arg;
|
|
801
|
+
heap_record_free(recorder, (heap_record *) key, false);
|
|
521
802
|
return ST_DELETE;
|
|
522
803
|
}
|
|
523
804
|
|
|
524
|
-
|
|
525
|
-
|
|
805
|
+
// NOTE: Only expected to be used from heap_recorder_free, which will separately destroy the string table
|
|
806
|
+
static int st_object_record_entry_free_no_unintern(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra_arg) {
|
|
807
|
+
heap_recorder *recorder = (heap_recorder *) extra_arg;
|
|
808
|
+
object_record_free(recorder, (object_record *) value, false);
|
|
526
809
|
return ST_DELETE;
|
|
527
810
|
}
|
|
528
811
|
|
|
529
|
-
//
|
|
530
|
-
// This centralizes the checking logic to ensure it's equally applied between
|
|
531
|
-
// preparation and iteration codepaths.
|
|
532
|
-
static inline bool should_exclude_from_iteration(object_record *obj_record) {
|
|
533
|
-
return obj_record->object_data.gen_age < ITERATION_MIN_AGE;
|
|
534
|
-
}
|
|
535
|
-
|
|
812
|
+
// NOTE: Some operations inside this function can cause the GVL to be released! Plan accordingly.
|
|
536
813
|
static int st_object_record_update(st_data_t key, st_data_t value, st_data_t extra_arg) {
|
|
537
|
-
long
|
|
814
|
+
long record_id = (long) key;
|
|
538
815
|
object_record *record = (object_record*) value;
|
|
539
816
|
heap_recorder *recorder = (heap_recorder*) extra_arg;
|
|
540
817
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
size_t iteration_gen = recorder->iteration_gen;
|
|
818
|
+
size_t update_gen = recorder->update_gen;
|
|
544
819
|
size_t alloc_gen = record->object_data.alloc_gen;
|
|
545
820
|
// Guard against potential overflows given unsigned types here.
|
|
546
|
-
record->object_data.gen_age = alloc_gen <
|
|
821
|
+
record->object_data.gen_age = alloc_gen < update_gen ? update_gen - alloc_gen : 0;
|
|
822
|
+
|
|
823
|
+
if (record->object_data.gen_age == 0) {
|
|
824
|
+
// Objects that belong to the current GC gen have not had a chance to be cleaned up yet
|
|
825
|
+
// and won't show up in the iteration anyway so no point in checking their liveness/sizes.
|
|
826
|
+
recorder->stats_last_update.objects_skipped++;
|
|
827
|
+
return ST_CONTINUE;
|
|
828
|
+
}
|
|
547
829
|
|
|
548
|
-
if (
|
|
549
|
-
//
|
|
550
|
-
// no point checking for liveness or updating its size, so exit early.
|
|
551
|
-
// NOTE: This means that there should be an equivalent check during actual
|
|
552
|
-
// iteration otherwise we'd iterate/expose stale object data.
|
|
830
|
+
if (!recorder->update_include_old && record->object_data.gen_age >= OLD_AGE) {
|
|
831
|
+
// The current update is not including old objects but this record is for an old object, skip its update.
|
|
553
832
|
recorder->stats_last_update.objects_skipped++;
|
|
554
833
|
return ST_CONTINUE;
|
|
555
834
|
}
|
|
556
835
|
|
|
557
|
-
|
|
558
|
-
|
|
836
|
+
// Note: This function call can cause the GVL to be released
|
|
837
|
+
VALUE ref = ruby_weak_map_get_may_lose_gvl(recorder->weak_objects, LONG2FIX(record_id));
|
|
838
|
+
if (ref == Qnil) {
|
|
839
|
+
// The weak reference is gone, meaning the object was garbage collected. Need to delete this object record!
|
|
559
840
|
on_committed_object_record_cleanup(recorder, record);
|
|
560
841
|
recorder->stats_last_update.objects_dead++;
|
|
561
842
|
return ST_DELETE;
|
|
@@ -563,49 +844,23 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
|
563
844
|
|
|
564
845
|
// If we got this far, then we found a valid live object for the tracked id.
|
|
565
846
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
// collected the object_record metadata may not be the same as the current ref and
|
|
572
|
-
// thus any further reporting would be innacurately attributed to stale metadata.
|
|
573
|
-
//
|
|
574
|
-
// In practice, there is a way for us to notice that this happened because of a bug
|
|
575
|
-
// in the implementation of rb_gc_force_recycle. Our heap profiler relies on object
|
|
576
|
-
// ids and id2ref to detect whether objects are still alive. Turns out that when an
|
|
577
|
-
// object with an id is re-used via rb_gc_force_recycle, it will "inherit" the ID
|
|
578
|
-
// of the old object but it will NOT have the FL_SEEN_OBJ_ID as per the experiment
|
|
579
|
-
// in https://github.com/DataDog/dd-trace-rb/pull/3360#discussion_r1442823517
|
|
580
|
-
//
|
|
581
|
-
// Thus, if we detect that the ref we just resolved above is missing this flag, we can
|
|
582
|
-
// safely say re-use happened and thus treat it as an implicit free of the object
|
|
583
|
-
// we were tracking (the original one which got recycled).
|
|
584
|
-
if (RB_FL_ABLE(ref) && !RB_FL_TEST(ref, RUBY_FL_SEEN_OBJ_ID)) {
|
|
585
|
-
|
|
586
|
-
// NOTE: We don't really need to set this flag for heap recorder to work correctly
|
|
587
|
-
// but doing so partially mitigates a bug in runtimes with working rb_gc_force_recycle
|
|
588
|
-
// which leads to broken invariants and leaking of entries in obj_to_id and id_to_obj
|
|
589
|
-
// tables in objspace. We already do the same thing when we sample a recycled object,
|
|
590
|
-
// here we apply it as well to objects that replace recycled objects that were being
|
|
591
|
-
// tracked. More details in https://github.com/DataDog/dd-trace-rb/pull/3366
|
|
592
|
-
RB_FL_SET(ref, RUBY_FL_SEEN_OBJ_ID);
|
|
593
|
-
|
|
594
|
-
on_committed_object_record_cleanup(recorder, record);
|
|
595
|
-
recorder->stats_last_update.objects_dead++;
|
|
596
|
-
return ST_DELETE;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
#endif
|
|
600
|
-
|
|
601
|
-
if (recorder->size_enabled && !record->object_data.is_frozen) {
|
|
847
|
+
if (
|
|
848
|
+
recorder->size_enabled &&
|
|
849
|
+
recorder->update_include_old && // We only update sizes when doing a full update
|
|
850
|
+
!record->object_data.is_frozen
|
|
851
|
+
) {
|
|
602
852
|
// if we were asked to update sizes and this object was not already seen as being frozen,
|
|
603
853
|
// update size again.
|
|
604
|
-
record->object_data.size = ruby_obj_memsize_of(ref);
|
|
854
|
+
record->object_data.size = ruby_obj_memsize_of(ref); // Note: This function call can cause the GVL to be released... maybe?
|
|
855
|
+
// (With T_DATA for instance, since it can be a custom method supplied by extensions)
|
|
605
856
|
// Check if it's now frozen so we skip a size update next time
|
|
606
857
|
record->object_data.is_frozen = RB_OBJ_FROZEN(ref);
|
|
607
858
|
}
|
|
608
859
|
|
|
860
|
+
// Ensure that ref is kept on the stack so the Ruby garbage collector does not try to clean up the object before this
|
|
861
|
+
// point.
|
|
862
|
+
RB_GC_GUARD(ref);
|
|
863
|
+
|
|
609
864
|
recorder->stats_last_update.objects_alive++;
|
|
610
865
|
if (record->object_data.is_frozen) {
|
|
611
866
|
recorder->stats_last_update.objects_frozen++;
|
|
@@ -617,15 +872,13 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
|
617
872
|
// WARN: This can get called outside the GVL. NO HEAP ALLOCATIONS OR EXCEPTIONS ARE ALLOWED.
|
|
618
873
|
static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra) {
|
|
619
874
|
object_record *record = (object_record*) value;
|
|
620
|
-
const
|
|
875
|
+
const heap_record *stack = record->heap_record;
|
|
621
876
|
iteration_context *context = (iteration_context*) extra;
|
|
622
877
|
|
|
623
878
|
const heap_recorder *recorder = context->heap_recorder;
|
|
624
879
|
|
|
625
|
-
if (
|
|
880
|
+
if (record->object_data.gen_age < ITERATION_MIN_AGE) {
|
|
626
881
|
// Skip objects that should not be included in iteration
|
|
627
|
-
// NOTE: This matches the short-circuiting condition in st_object_record_update
|
|
628
|
-
// and prevents iteration over stale objects.
|
|
629
882
|
return ST_CONTINUE;
|
|
630
883
|
}
|
|
631
884
|
|
|
@@ -633,10 +886,12 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
|
|
|
633
886
|
for (uint16_t i = 0; i < stack->frames_len; i++) {
|
|
634
887
|
const heap_frame *frame = &stack->frames[i];
|
|
635
888
|
locations[i] = (ddog_prof_Location) {
|
|
636
|
-
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C("")},
|
|
889
|
+
.mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}},
|
|
637
890
|
.function = {
|
|
638
|
-
.name =
|
|
639
|
-
.
|
|
891
|
+
.name = DDOG_CHARSLICE_C(""),
|
|
892
|
+
.name_id = frame->name,
|
|
893
|
+
.filename = DDOG_CHARSLICE_C(""),
|
|
894
|
+
.filename_id = frame->filename,
|
|
640
895
|
},
|
|
641
896
|
.line = frame->line,
|
|
642
897
|
};
|
|
@@ -646,6 +901,7 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
|
|
|
646
901
|
iteration_data.object_data = record->object_data;
|
|
647
902
|
iteration_data.locations = (ddog_prof_Slice_Location) {.ptr = locations, .len = stack->frames_len};
|
|
648
903
|
|
|
904
|
+
// This is expected to be StackRecorder's add_heap_sample_to_active_profile_without_gvl
|
|
649
905
|
if (!context->for_each_callback(iteration_data, context->for_each_callback_extra_arg)) {
|
|
650
906
|
return ST_STOP;
|
|
651
907
|
}
|
|
@@ -654,113 +910,53 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
|
|
|
654
910
|
}
|
|
655
911
|
|
|
656
912
|
static int st_object_records_debug(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra) {
|
|
657
|
-
|
|
913
|
+
debug_context *context = (debug_context*) extra;
|
|
658
914
|
|
|
659
915
|
object_record *record = (object_record*) value;
|
|
660
916
|
|
|
661
|
-
|
|
917
|
+
rb_ary_push(context->debug_ary, object_record_inspect(context->recorder, record));
|
|
662
918
|
|
|
663
919
|
return ST_CONTINUE;
|
|
664
920
|
}
|
|
665
921
|
|
|
666
|
-
|
|
667
|
-
typedef struct {
|
|
668
|
-
// [in] The recording containing the new object record we want to add.
|
|
669
|
-
// NOTE: Transfer of ownership of the contained object record is assumed, do not re-use it after call to ::update_object_record_entry
|
|
670
|
-
recording recording;
|
|
671
|
-
|
|
672
|
-
// [in] The heap recorder where the update is happening.
|
|
673
|
-
heap_recorder *heap_recorder;
|
|
674
|
-
} object_record_update_data;
|
|
675
|
-
|
|
676
|
-
static int update_object_record_entry(DDTRACE_UNUSED st_data_t *key, st_data_t *value, st_data_t data, int existing) {
|
|
677
|
-
object_record_update_data *update_data = (object_record_update_data*) data;
|
|
678
|
-
recording recording = update_data->recording;
|
|
679
|
-
object_record *new_object_record = recording.object_record;
|
|
680
|
-
if (existing) {
|
|
681
|
-
object_record *existing_record = (object_record*) (*value);
|
|
682
|
-
if (recording.did_recycle_workaround) {
|
|
683
|
-
// In this case, it's possible for an object id to be re-used and we were lucky enough to have
|
|
684
|
-
// sampled both the original object and the replacement so cleanup the old one and replace it with
|
|
685
|
-
// the new object_record (i.e. treat this as a combined free+allocation).
|
|
686
|
-
on_committed_object_record_cleanup(update_data->heap_recorder, existing_record);
|
|
687
|
-
} else {
|
|
688
|
-
// This is not supposed to happen, raising...
|
|
689
|
-
VALUE existing_inspect = object_record_inspect(existing_record);
|
|
690
|
-
VALUE new_inspect = object_record_inspect(new_object_record);
|
|
691
|
-
rb_raise(rb_eRuntimeError, "Object ids are supposed to be unique. We got 2 allocation recordings with "
|
|
692
|
-
"the same id. previous=%"PRIsVALUE" new=%"PRIsVALUE, existing_inspect, new_inspect);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
// Always carry on with the update, we want the new record to be there at the end
|
|
696
|
-
(*value) = (st_data_t) new_object_record;
|
|
697
|
-
return ST_CONTINUE;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
static void commit_recording(heap_recorder *heap_recorder, heap_record *heap_record, recording recording) {
|
|
701
|
-
// Link the object record with the corresponding heap record. This was the last remaining thing we
|
|
702
|
-
// needed to fully build the object_record.
|
|
703
|
-
recording.object_record->heap_record = heap_record;
|
|
922
|
+
static void inc_tracked_objects_or_fail(heap_record *heap_record) {
|
|
704
923
|
if (heap_record->num_tracked_objects == UINT32_MAX) {
|
|
705
|
-
|
|
924
|
+
raise_error(rb_eRuntimeError, "Reached maximum number of tracked objects for heap record");
|
|
706
925
|
}
|
|
707
926
|
heap_record->num_tracked_objects++;
|
|
927
|
+
}
|
|
708
928
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
929
|
+
static void commit_recording(heap_recorder *heap_recorder, pending_recording pending) {
|
|
930
|
+
object_record *new_record = object_record_new(pending);
|
|
931
|
+
|
|
932
|
+
int existing = st_insert(heap_recorder->object_records, new_record->record_id, (st_data_t) new_record);
|
|
933
|
+
if (existing) {
|
|
934
|
+
raise_error(rb_eRuntimeError, "Unexpected record with the same record_id in heap_recorder->object_records");
|
|
935
|
+
}
|
|
715
936
|
}
|
|
716
937
|
|
|
717
|
-
// Struct holding data required for an update operation on heap_records
|
|
718
|
-
typedef struct {
|
|
719
|
-
// [in] The locations we did this update with
|
|
720
|
-
ddog_prof_Slice_Location locations;
|
|
721
|
-
// [out] Pointer that will be updated to the updated heap record to prevent having to do
|
|
722
|
-
// another lookup to access the updated heap record.
|
|
723
|
-
heap_record **record;
|
|
724
|
-
} heap_record_update_data;
|
|
725
|
-
|
|
726
|
-
// This function assumes ownership of stack_data is passed on to it so it'll either transfer ownership or clean-up.
|
|
727
938
|
static int update_heap_record_entry_with_new_allocation(st_data_t *key, st_data_t *value, st_data_t data, int existing) {
|
|
728
|
-
|
|
939
|
+
heap_record **new_or_existing_record = (heap_record **) data;
|
|
940
|
+
(*new_or_existing_record) = (heap_record *) (*key);
|
|
729
941
|
|
|
730
942
|
if (!existing) {
|
|
731
|
-
|
|
732
|
-
// we need to initialize a heap_record_key with a new stack and use that for the key storage. We can't use the
|
|
733
|
-
// locations-based key we used for the update call because we don't own its lifecycle. So we create a new
|
|
734
|
-
// heap stack and will pass ownership of it to the heap_record.
|
|
735
|
-
heap_stack *stack = heap_stack_new(update_data->locations);
|
|
736
|
-
(*key) = (st_data_t) heap_record_key_new(stack);
|
|
737
|
-
(*value) = (st_data_t) heap_record_new(stack);
|
|
943
|
+
(*value) = (st_data_t) true; // We're only using this hash as a set
|
|
738
944
|
}
|
|
739
945
|
|
|
740
|
-
heap_record *record = (heap_record*) (*value);
|
|
741
|
-
(*update_data->record) = record;
|
|
742
|
-
|
|
743
946
|
return ST_CONTINUE;
|
|
744
947
|
}
|
|
745
948
|
|
|
746
949
|
static heap_record* get_or_create_heap_record(heap_recorder *heap_recorder, ddog_prof_Slice_Location locations) {
|
|
747
|
-
//
|
|
748
|
-
|
|
749
|
-
// NOTE: If we end up creating a new record, we'll create a heap-allocated key we own and use that for storage
|
|
750
|
-
// instead of this one.
|
|
751
|
-
heap_record_key lookup_key = (heap_record_key) {
|
|
752
|
-
.type = LOCATION_SLICE,
|
|
753
|
-
.location_slice = &locations,
|
|
754
|
-
};
|
|
950
|
+
// See note on "heap_records" definition for why we keep this map.
|
|
951
|
+
heap_record *stack = heap_record_new(heap_recorder, locations);
|
|
755
952
|
|
|
756
|
-
heap_record *
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
}
|
|
761
|
-
st_update(heap_recorder->heap_records, (st_data_t) &lookup_key, update_heap_record_entry_with_new_allocation, (st_data_t) &update_data);
|
|
953
|
+
heap_record *new_or_existing_record = NULL; // Will be set inside update_heap_record_entry_with_new_allocation
|
|
954
|
+
int existing = st_update(heap_recorder->heap_records, (st_data_t) stack, update_heap_record_entry_with_new_allocation, (st_data_t) &new_or_existing_record);
|
|
955
|
+
if (existing) {
|
|
956
|
+
heap_record_free(heap_recorder, stack, true);
|
|
957
|
+
}
|
|
762
958
|
|
|
763
|
-
return
|
|
959
|
+
return new_or_existing_record;
|
|
764
960
|
}
|
|
765
961
|
|
|
766
962
|
static void cleanup_heap_record_if_unused(heap_recorder *heap_recorder, heap_record *heap_record) {
|
|
@@ -769,18 +965,10 @@ static void cleanup_heap_record_if_unused(heap_recorder *heap_recorder, heap_rec
|
|
|
769
965
|
return;
|
|
770
966
|
}
|
|
771
967
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
.heap_stack = heap_record->stack,
|
|
968
|
+
if (!st_delete(heap_recorder->heap_records, (st_data_t*) &heap_record, NULL)) {
|
|
969
|
+
raise_error(rb_eRuntimeError, "Attempted to cleanup an untracked heap_record");
|
|
775
970
|
};
|
|
776
|
-
|
|
777
|
-
// st_delete will change this pointer to point to the removed key if one is found.
|
|
778
|
-
heap_record_key *deleted_key = &heap_key;
|
|
779
|
-
if (!st_delete(heap_recorder->heap_records, (st_data_t*) &deleted_key, NULL)) {
|
|
780
|
-
rb_raise(rb_eRuntimeError, "Attempted to cleanup an untracked heap_record");
|
|
781
|
-
};
|
|
782
|
-
heap_record_key_free(deleted_key);
|
|
783
|
-
heap_record_free(heap_record);
|
|
971
|
+
heap_record_free(heap_recorder, heap_record, true);
|
|
784
972
|
}
|
|
785
973
|
|
|
786
974
|
static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, object_record *record) {
|
|
@@ -788,72 +976,61 @@ static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, obj
|
|
|
788
976
|
// (See PROF-10656 Datadog-internal for details). Just in case, I've sprinkled a bunch of NULL tests in this function for now.
|
|
789
977
|
// Once we figure out the issue we can get rid of them again.
|
|
790
978
|
|
|
791
|
-
if (heap_recorder == NULL)
|
|
792
|
-
if (heap_recorder->heap_records == NULL)
|
|
793
|
-
if (record == NULL)
|
|
979
|
+
if (heap_recorder == NULL) raise_error(rb_eRuntimeError, "heap_recorder was NULL in on_committed_object_record_cleanup");
|
|
980
|
+
if (heap_recorder->heap_records == NULL) raise_error(rb_eRuntimeError, "heap_recorder->heap_records was NULL in on_committed_object_record_cleanup");
|
|
981
|
+
if (record == NULL) raise_error(rb_eRuntimeError, "record was NULL in on_committed_object_record_cleanup");
|
|
794
982
|
|
|
795
983
|
// Starting with the associated heap record. There will now be one less tracked object pointing to it
|
|
796
984
|
heap_record *heap_record = record->heap_record;
|
|
797
985
|
|
|
798
|
-
if (heap_record == NULL)
|
|
799
|
-
if (heap_record->stack == NULL) rb_raise(rb_eRuntimeError, "heap_record->stack was NULL in on_committed_object_record_cleanup");
|
|
986
|
+
if (heap_record == NULL) raise_error(rb_eRuntimeError, "heap_record was NULL in on_committed_object_record_cleanup");
|
|
800
987
|
|
|
801
988
|
heap_record->num_tracked_objects--;
|
|
802
989
|
|
|
803
990
|
// One less object using this heap record, it may have become unused...
|
|
804
991
|
cleanup_heap_record_if_unused(heap_recorder, heap_record);
|
|
805
992
|
|
|
806
|
-
object_record_free(record);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// ===============
|
|
810
|
-
// Heap Record API
|
|
811
|
-
// ===============
|
|
812
|
-
heap_record* heap_record_new(heap_stack *stack) {
|
|
813
|
-
heap_record *record = ruby_xcalloc(1, sizeof(heap_record));
|
|
814
|
-
record->num_tracked_objects = 0;
|
|
815
|
-
record->stack = stack;
|
|
816
|
-
return record;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
void heap_record_free(heap_record *record) {
|
|
820
|
-
heap_stack_free(record->stack);
|
|
821
|
-
ruby_xfree(record);
|
|
993
|
+
object_record_free(heap_recorder, record, true);
|
|
822
994
|
}
|
|
823
995
|
|
|
824
|
-
|
|
825
996
|
// =================
|
|
826
997
|
// Object Record API
|
|
827
998
|
// =================
|
|
828
|
-
object_record* object_record_new(
|
|
829
|
-
object_record *record =
|
|
830
|
-
record->
|
|
831
|
-
record
|
|
832
|
-
record->
|
|
999
|
+
static object_record* object_record_new(pending_recording pending) {
|
|
1000
|
+
object_record *record = calloc(1, sizeof(object_record)); // See "note on calloc vs ruby_xcalloc use" above
|
|
1001
|
+
record->record_id = pending.record_id;
|
|
1002
|
+
// Link the object record with the corresponding heap record
|
|
1003
|
+
record->heap_record = pending.heap_record;
|
|
1004
|
+
record->object_data = pending.object_data;
|
|
833
1005
|
return record;
|
|
834
1006
|
}
|
|
835
1007
|
|
|
836
|
-
void object_record_free(object_record *record) {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1008
|
+
static void object_record_free(heap_recorder *recorder, object_record *record, bool should_unintern) {
|
|
1009
|
+
// When tearing down the whole recorder state, we skip uninterning as it's not needed (the managed
|
|
1010
|
+
// string table is going to be destroyed anyway) and if there's any failures we can't raise
|
|
1011
|
+
// in the middle of a dfree callback.
|
|
1012
|
+
if (should_unintern) unintern_or_raise(recorder, record->object_data.class);
|
|
1013
|
+
|
|
1014
|
+
free(record); // See "note on calloc vs ruby_xcalloc use" above
|
|
841
1015
|
}
|
|
842
1016
|
|
|
843
|
-
VALUE object_record_inspect(object_record *record) {
|
|
844
|
-
heap_frame top_frame = record->heap_record->
|
|
1017
|
+
static VALUE object_record_inspect(heap_recorder *recorder, object_record *record) {
|
|
1018
|
+
heap_frame top_frame = record->heap_record->frames[0];
|
|
1019
|
+
VALUE filename = get_ruby_string_or_raise(recorder, top_frame.filename);
|
|
845
1020
|
live_object_data object_data = record->object_data;
|
|
846
|
-
|
|
847
|
-
|
|
1021
|
+
|
|
1022
|
+
VALUE inspect = rb_sprintf("record_id=%ld weight=%d size=%zu location=%"PRIsVALUE":%d alloc_gen=%zu gen_age=%zu frozen=%d ",
|
|
1023
|
+
record->record_id, object_data.weight, object_data.size, filename,
|
|
848
1024
|
(int) top_frame.line, object_data.alloc_gen, object_data.gen_age, object_data.is_frozen);
|
|
849
1025
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1026
|
+
if (record->object_data.class.value > 0) {
|
|
1027
|
+
VALUE class = get_ruby_string_or_raise(recorder, record->object_data.class);
|
|
1028
|
+
|
|
1029
|
+
rb_str_catf(inspect, "class=%"PRIsVALUE" ", class);
|
|
853
1030
|
}
|
|
854
|
-
VALUE ref;
|
|
855
1031
|
|
|
856
|
-
|
|
1032
|
+
VALUE ref = ruby_weak_map_get_may_lose_gvl(recorder->weak_objects, LONG2FIX(record->record_id));
|
|
1033
|
+
if (ref == Qnil) {
|
|
857
1034
|
rb_str_catf(inspect, "object=<invalid>");
|
|
858
1035
|
} else {
|
|
859
1036
|
rb_str_catf(inspect, "value=%p ", (void *) ref);
|
|
@@ -869,221 +1046,198 @@ VALUE object_record_inspect(object_record *record) {
|
|
|
869
1046
|
}
|
|
870
1047
|
|
|
871
1048
|
// ==============
|
|
872
|
-
// Heap
|
|
873
|
-
// ==============
|
|
874
|
-
int heap_frame_cmp(heap_frame *f1, heap_frame *f2) {
|
|
875
|
-
int line_diff = (int) (f1->line - f2->line);
|
|
876
|
-
if (line_diff != 0) {
|
|
877
|
-
return line_diff;
|
|
878
|
-
}
|
|
879
|
-
int cmp = strcmp(f1->name, f2->name);
|
|
880
|
-
if (cmp != 0) {
|
|
881
|
-
return cmp;
|
|
882
|
-
}
|
|
883
|
-
return strcmp(f1->filename, f2->filename);
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// TODO: Research potential performance improvements around hashing stuff here
|
|
887
|
-
// once we have a benchmarking suite.
|
|
888
|
-
// Example: Each call to st_hash is calling murmur_finish and we may want
|
|
889
|
-
// to only finish once per structure, not per field?
|
|
890
|
-
// Example: There may be a more efficient hashing for line that is not the
|
|
891
|
-
// generic st_hash algorithm?
|
|
892
|
-
|
|
893
|
-
// WARN: Must be kept in-sync with ::char_slice_hash
|
|
894
|
-
st_index_t string_hash(char *str, st_index_t seed) {
|
|
895
|
-
return st_hash(str, strlen(str), seed);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// WARN: Must be kept in-sync with ::string_hash
|
|
899
|
-
st_index_t char_slice_hash(ddog_CharSlice char_slice, st_index_t seed) {
|
|
900
|
-
return st_hash(char_slice.ptr, char_slice.len, seed);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// WARN: Must be kept in-sync with ::ddog_location_hash
|
|
904
|
-
st_index_t heap_frame_hash(heap_frame *frame, st_index_t seed) {
|
|
905
|
-
st_index_t hash = string_hash(frame->name, seed);
|
|
906
|
-
hash = string_hash(frame->filename, hash);
|
|
907
|
-
hash = st_hash(&frame->line, sizeof(frame->line), hash);
|
|
908
|
-
return hash;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// WARN: Must be kept in-sync with ::heap_frame_hash
|
|
912
|
-
st_index_t ddog_location_hash(ddog_prof_Location location, st_index_t seed) {
|
|
913
|
-
st_index_t hash = char_slice_hash(location.function.name, seed);
|
|
914
|
-
hash = char_slice_hash(location.function.filename, hash);
|
|
915
|
-
// Convert ddog_prof line type to the same type we use for our heap_frames to
|
|
916
|
-
// ensure we have compatible hashes
|
|
917
|
-
int32_t line_as_int32 = (int32_t) location.line;
|
|
918
|
-
hash = st_hash(&line_as_int32, sizeof(line_as_int32), hash);
|
|
919
|
-
return hash;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
// ==============
|
|
923
|
-
// Heap Stack API
|
|
1049
|
+
// Heap Record API
|
|
924
1050
|
// ==============
|
|
925
|
-
|
|
1051
|
+
static heap_record* heap_record_new(heap_recorder *recorder, ddog_prof_Slice_Location locations) {
|
|
926
1052
|
uint16_t frames_len = locations.len;
|
|
927
1053
|
if (frames_len > MAX_FRAMES_LIMIT) {
|
|
928
|
-
// This
|
|
929
|
-
|
|
930
|
-
// be safe since only allocate with the GVL anyway.
|
|
931
|
-
rb_raise(rb_eRuntimeError, "Found stack with more than %d frames (%d)", MAX_FRAMES_LIMIT, frames_len);
|
|
1054
|
+
// This is not expected as MAX_FRAMES_LIMIT is shared with the stacktrace construction mechanism
|
|
1055
|
+
raise_error(rb_eRuntimeError, "Found stack with more than %d frames (%d)", MAX_FRAMES_LIMIT, frames_len);
|
|
932
1056
|
}
|
|
933
|
-
|
|
1057
|
+
heap_record *stack = calloc(1, sizeof(heap_record) + frames_len * sizeof(heap_frame)); // See "note on calloc vs ruby_xcalloc use" above
|
|
1058
|
+
stack->num_tracked_objects = 0;
|
|
934
1059
|
stack->frames_len = frames_len;
|
|
1060
|
+
|
|
1061
|
+
// Intern all these strings...
|
|
1062
|
+
ddog_CharSlice *strings = recorder->reusable_char_slices;
|
|
1063
|
+
// Put all the char slices in the same array; we'll pull them out in the same order from the ids array
|
|
935
1064
|
for (uint16_t i = 0; i < stack->frames_len; i++) {
|
|
936
1065
|
const ddog_prof_Location *location = &locations.ptr[i];
|
|
1066
|
+
strings[i] = location->function.filename;
|
|
1067
|
+
strings[i + stack->frames_len] = location->function.name;
|
|
1068
|
+
}
|
|
1069
|
+
intern_all_or_raise(recorder->string_storage, (ddog_prof_Slice_CharSlice) { .ptr = strings, .len = stack->frames_len * 2 }, recorder->reusable_ids, stack->frames_len * 2);
|
|
1070
|
+
|
|
1071
|
+
// ...and record them for later use
|
|
1072
|
+
for (uint16_t i = 0; i < stack->frames_len; i++) {
|
|
937
1073
|
stack->frames[i] = (heap_frame) {
|
|
938
|
-
.
|
|
939
|
-
.
|
|
1074
|
+
.filename = recorder->reusable_ids[i],
|
|
1075
|
+
.name = recorder->reusable_ids[i + stack->frames_len],
|
|
940
1076
|
// ddog_prof_Location is a int64_t. We don't expect to have to profile files with more than
|
|
941
1077
|
// 2M lines so this cast should be fairly safe?
|
|
942
|
-
.line = (int32_t)
|
|
1078
|
+
.line = (int32_t) locations.ptr[i].line,
|
|
943
1079
|
};
|
|
944
1080
|
}
|
|
1081
|
+
|
|
945
1082
|
return stack;
|
|
946
1083
|
}
|
|
947
1084
|
|
|
948
|
-
void
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1085
|
+
static void heap_record_free(heap_recorder *recorder, heap_record *stack, bool should_unintern) {
|
|
1086
|
+
// When tearing down the whole recorder state, we skip uninterning as it's not needed (the managed
|
|
1087
|
+
// string table is going to be destroyed anyway) and if there's any failures we can't raise
|
|
1088
|
+
// in the middle of a dfree callback.
|
|
1089
|
+
if (should_unintern) {
|
|
1090
|
+
ddog_prof_ManagedStringId *ids = recorder->reusable_ids;
|
|
1091
|
+
|
|
1092
|
+
// Put all the ids in the same array; doesn't really matter the order
|
|
1093
|
+
for (u_int16_t i = 0; i < stack->frames_len; i++) {
|
|
1094
|
+
ids[i] = stack->frames[i].filename;
|
|
1095
|
+
ids[i + stack->frames_len] = stack->frames[i].name;
|
|
1096
|
+
}
|
|
1097
|
+
unintern_all_or_raise(recorder, (ddog_prof_Slice_ManagedStringId) { .ptr = ids, .len = stack->frames_len * 2 });
|
|
953
1098
|
}
|
|
954
|
-
ruby_xfree(stack);
|
|
955
|
-
}
|
|
956
1099
|
|
|
957
|
-
//
|
|
958
|
-
st_index_t heap_stack_hash(heap_stack *stack, st_index_t seed) {
|
|
959
|
-
st_index_t hash = seed;
|
|
960
|
-
for (uint64_t i = 0; i < stack->frames_len; i++) {
|
|
961
|
-
hash = heap_frame_hash(&stack->frames[i], hash);
|
|
962
|
-
}
|
|
963
|
-
return hash;
|
|
1100
|
+
free(stack); // See "note on calloc vs ruby_xcalloc use" above
|
|
964
1101
|
}
|
|
965
1102
|
|
|
966
|
-
//
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1103
|
+
// The entire stack is represented by ids (name, filename) and lines (integers) so we can treat is as just
|
|
1104
|
+
// a big string of bytes and compare it all in one go.
|
|
1105
|
+
static int heap_record_cmp_st(st_data_t key1, st_data_t key2) {
|
|
1106
|
+
heap_record *stack1 = (heap_record*) key1;
|
|
1107
|
+
heap_record *stack2 = (heap_record*) key2;
|
|
1108
|
+
|
|
1109
|
+
if (stack1->frames_len != stack2->frames_len) {
|
|
1110
|
+
return ((int) stack1->frames_len) - ((int) stack2->frames_len);
|
|
1111
|
+
} else {
|
|
1112
|
+
return memcmp(stack1->frames, stack2->frames, stack1->frames_len * sizeof(heap_frame));
|
|
971
1113
|
}
|
|
972
|
-
return hash;
|
|
973
1114
|
}
|
|
974
1115
|
|
|
975
|
-
//
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
return
|
|
1116
|
+
// Initial seed for hash function, same as Ruby uses
|
|
1117
|
+
#define FNV1_32A_INIT 0x811c9dc5
|
|
1118
|
+
|
|
1119
|
+
// The entire stack is represented by ids (name, filename) and lines (integers) so we can treat is as just
|
|
1120
|
+
// a big string of bytes and hash it all in one go.
|
|
1121
|
+
static st_index_t heap_record_hash_st(st_data_t key) {
|
|
1122
|
+
heap_record *stack = (heap_record*) key;
|
|
1123
|
+
return st_hash(stack->frames, stack->frames_len * sizeof(heap_frame), FNV1_32A_INIT);
|
|
983
1124
|
}
|
|
984
1125
|
|
|
985
|
-
void
|
|
986
|
-
|
|
1126
|
+
static void unintern_or_raise(heap_recorder *recorder, ddog_prof_ManagedStringId id) {
|
|
1127
|
+
if (id.value == 0) return; // Empty string, nothing to do
|
|
1128
|
+
|
|
1129
|
+
ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern(recorder->string_storage, id);
|
|
1130
|
+
if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
|
|
1131
|
+
raise_error(rb_eRuntimeError, "Failed to unintern id: %"PRIsVALUE, get_error_details_and_drop(&result.some));
|
|
1132
|
+
}
|
|
987
1133
|
}
|
|
988
1134
|
|
|
989
|
-
static
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
return key->location_slice->len;
|
|
1135
|
+
static void unintern_all_or_raise(heap_recorder *recorder, ddog_prof_Slice_ManagedStringId ids) {
|
|
1136
|
+
ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern_all(recorder->string_storage, ids);
|
|
1137
|
+
if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
|
|
1138
|
+
raise_error(rb_eRuntimeError, "Failed to unintern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some));
|
|
994
1139
|
}
|
|
995
1140
|
}
|
|
996
1141
|
|
|
997
|
-
static
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
return key->location_slice->ptr[entry_i].line;
|
|
1142
|
+
static VALUE get_ruby_string_or_raise(heap_recorder *recorder, ddog_prof_ManagedStringId id) {
|
|
1143
|
+
ddog_StringWrapperResult get_string_result = ddog_prof_ManagedStringStorage_get_string(recorder->string_storage, id);
|
|
1144
|
+
if (get_string_result.tag == DDOG_STRING_WRAPPER_RESULT_ERR) {
|
|
1145
|
+
raise_error(rb_eRuntimeError, "Failed to get string: %"PRIsVALUE, get_error_details_and_drop(&get_string_result.err));
|
|
1002
1146
|
}
|
|
1147
|
+
VALUE ruby_string = ruby_string_from_vec_u8(get_string_result.ok.message);
|
|
1148
|
+
ddog_StringWrapper_drop((ddog_StringWrapper *) &get_string_result.ok);
|
|
1149
|
+
|
|
1150
|
+
return ruby_string;
|
|
1003
1151
|
}
|
|
1004
1152
|
|
|
1005
|
-
static inline
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
(
|
|
1013
|
-
return name.len;
|
|
1153
|
+
static inline double ewma_stat(double previous, double current) {
|
|
1154
|
+
double alpha = 0.3;
|
|
1155
|
+
return (1 - alpha) * previous + alpha * current;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
VALUE heap_recorder_testonly_is_object_recorded(heap_recorder *heap_recorder, long record_id) {
|
|
1159
|
+
if (heap_recorder == NULL) {
|
|
1160
|
+
raise_error(rb_eArgError, "heap_recorder is NULL");
|
|
1014
1161
|
}
|
|
1162
|
+
|
|
1163
|
+
// Check if object records contains an object with this record_id
|
|
1164
|
+
return st_is_member(heap_recorder->object_records, record_id) ? Qtrue : Qfalse;
|
|
1015
1165
|
}
|
|
1016
1166
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1167
|
+
typedef struct {
|
|
1168
|
+
heap_recorder *recorder;
|
|
1169
|
+
VALUE target;
|
|
1170
|
+
VALUE result;
|
|
1171
|
+
} record_id_for_context;
|
|
1172
|
+
|
|
1173
|
+
static int st_object_record_id_for(st_data_t key, DDTRACE_UNUSED st_data_t value, st_data_t extra) {
|
|
1174
|
+
record_id_for_context *context = (record_id_for_context*) extra;
|
|
1175
|
+
long record_id = (long) key;
|
|
1176
|
+
|
|
1177
|
+
VALUE ref = ruby_weak_map_get_may_lose_gvl(context->recorder->weak_objects, LONG2FIX(record_id));
|
|
1178
|
+
if (ref != Qnil && ref == context->target) {
|
|
1179
|
+
context->result = LONG2FIX(record_id);
|
|
1180
|
+
return ST_STOP;
|
|
1026
1181
|
}
|
|
1182
|
+
|
|
1183
|
+
return ST_CONTINUE;
|
|
1027
1184
|
}
|
|
1028
1185
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1186
|
+
VALUE heap_recorder_testonly_record_id_for(heap_recorder *heap_recorder, VALUE obj) {
|
|
1187
|
+
// Heap profiling is disabled, so nothing is being tracked
|
|
1188
|
+
if (heap_recorder == NULL) return Qnil;
|
|
1032
1189
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
size_t key_record2_len = heap_record_key_len(key_record2);
|
|
1190
|
+
record_id_for_context context = (record_id_for_context) {.recorder = heap_recorder, .target = obj, .result = Qnil};
|
|
1191
|
+
st_foreach(heap_recorder->object_records, st_object_record_id_for, (st_data_t) &context);
|
|
1036
1192
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1193
|
+
return context.result;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
void heap_recorder_testonly_set_object_alloc_gen(heap_recorder *heap_recorder, long record_id, size_t alloc_gen) {
|
|
1197
|
+
if (heap_recorder == NULL) {
|
|
1198
|
+
raise_error(rb_eArgError, "heap_recorder is NULL");
|
|
1039
1199
|
}
|
|
1040
1200
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
size_t line2 = heap_record_key_entry_line(key_record2, i);
|
|
1046
|
-
if (line1 != line2) {
|
|
1047
|
-
return ((int) line1) - ((int)line2);
|
|
1048
|
-
}
|
|
1201
|
+
object_record *record = NULL;
|
|
1202
|
+
if (!st_lookup(heap_recorder->object_records, (st_data_t) record_id, (st_data_t *) &record)) {
|
|
1203
|
+
raise_error(rb_eArgError, "record_id %ld is not being tracked", record_id);
|
|
1204
|
+
}
|
|
1049
1205
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
size_t name1_len = heap_record_key_entry_name(key_record1, i, &name1);
|
|
1053
|
-
size_t name2_len = heap_record_key_entry_name(key_record2, i, &name2);
|
|
1054
|
-
if (name1_len != name2_len) {
|
|
1055
|
-
return ((int) name1_len) - ((int) name2_len);
|
|
1056
|
-
}
|
|
1057
|
-
int name_cmp_result = strncmp(name1, name2, name1_len);
|
|
1058
|
-
if (name_cmp_result != 0) {
|
|
1059
|
-
return name_cmp_result;
|
|
1060
|
-
}
|
|
1206
|
+
record->object_data.alloc_gen = alloc_gen;
|
|
1207
|
+
}
|
|
1061
1208
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
int64_t filename2_len = heap_record_key_entry_filename(key_record2, i, &filename2);
|
|
1066
|
-
if (filename1_len != filename2_len) {
|
|
1067
|
-
return ((int) filename1_len) - ((int) filename2_len);
|
|
1068
|
-
}
|
|
1069
|
-
int filename_cmp_result = strncmp(filename1, filename2, filename1_len);
|
|
1070
|
-
if (filename_cmp_result != 0) {
|
|
1071
|
-
return filename_cmp_result;
|
|
1072
|
-
}
|
|
1209
|
+
void heap_recorder_testonly_exhaust_record_ids(heap_recorder *heap_recorder) {
|
|
1210
|
+
if (heap_recorder == NULL) {
|
|
1211
|
+
raise_error(rb_eArgError, "heap_recorder is NULL");
|
|
1073
1212
|
}
|
|
1074
1213
|
|
|
1075
|
-
|
|
1076
|
-
return 0;
|
|
1214
|
+
heap_recorder->next_record_id = RUBY_FIXNUM_MAX;
|
|
1077
1215
|
}
|
|
1078
1216
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1217
|
+
void heap_recorder_testonly_reset_last_update(heap_recorder *heap_recorder) {
|
|
1218
|
+
if (heap_recorder == NULL) {
|
|
1219
|
+
raise_error(rb_eArgError, "heap_recorder is NULL");
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
heap_recorder->last_update_ns = 0;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
void heap_recorder_testonly_benchmark_intern(heap_recorder *heap_recorder, ddog_CharSlice string, int times, bool use_all) {
|
|
1226
|
+
if (heap_recorder == NULL) raise_error(rb_eArgError, "heap profiling must be enabled");
|
|
1227
|
+
if (times > REUSABLE_FRAME_DETAILS_SIZE) raise_error(rb_eArgError, "times cannot be > than REUSABLE_FRAME_DETAILS_SIZE");
|
|
1228
|
+
|
|
1229
|
+
if (use_all) {
|
|
1230
|
+
ddog_CharSlice *strings = heap_recorder->reusable_char_slices;
|
|
1231
|
+
|
|
1232
|
+
for (int i = 0; i < times; i++) strings[i] = string;
|
|
1081
1233
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1234
|
+
intern_all_or_raise(
|
|
1235
|
+
heap_recorder->string_storage,
|
|
1236
|
+
(ddog_prof_Slice_CharSlice) { .ptr = strings, .len = times },
|
|
1237
|
+
heap_recorder->reusable_ids,
|
|
1238
|
+
times
|
|
1239
|
+
);
|
|
1086
1240
|
} else {
|
|
1087
|
-
|
|
1241
|
+
for (int i = 0; i < times; i++) intern_or_raise(heap_recorder->string_storage, string);
|
|
1088
1242
|
}
|
|
1089
1243
|
}
|