lib/datadog/profiling/component.rb in ddtrace-1.14.0 vs lib/datadog/profiling/component.rb in ddtrace-1.15.0

- old
+ new

@@ -5,12 +5,13 @@ # Responsible for wiring up the Profiler for execution module Component # Passing in a `nil` tracer is supported and will disable the following profiling features: # * Code Hotspots panel in the trace viewer, as well as scoping a profile down to a span # * Endpoint aggregation in the profiler UX, including normalization (resource per endpoint call) - def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) # rubocop:disable Metrics/MethodLength + def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) require_relative '../profiling/diagnostics/environment_logger' + Profiling::Diagnostics::EnvironmentLogger.collect_and_log! return unless settings.profiling.enabled # Workaround for weird dependency direction: the Core::Configuration::Components class currently has a @@ -31,88 +32,49 @@ # done, then profiling may not be loaded, and thus to avoid this issue we do a require here (which is a # no-op if profiling is already loaded). require_relative '../profiling' return unless Profiling.supported? - unless defined?(Profiling::Tasks::Setup) - # In #1545 a user reported a NameError due to this constant being uninitialized - # I've documented my suspicion on why that happened in - # https://github.com/DataDog/dd-trace-rb/issues/1545#issuecomment-856049025 - # - # > Thanks for the info! It seems to feed into my theory: there's two moments in the code where we check if - # > profiler is "supported": 1) when loading ddtrace (inside preload) and 2) when starting the profile - # > after Datadog.configure gets run. - # > The problem is that the code assumes that both checks 1) and 2) will always reach the same conclusion: - # > either profiler is supported, or profiler is not supported. - # > In the problematic case, it looks like in your case check 1 decides that profiler is not - # > supported => doesn't load it, and then check 2 decides that it is => assumes it is loaded and tries to - # > start it. - # - # I was never able to validate if this was the issue or why exactly .supported? would change its mind BUT - # just in case it happens again, I've left this check which avoids breaking the user's application AND - # would instead direct them to report it to us instead, so that we can investigate what's wrong. - # - # TODO: As of June 2021, most checks in .supported? are related to the google-protobuf gem; so it's - # very likely that it was the origin of the issue we saw. Thus, if, as planned we end up moving away from - # protobuf OR enough time has passed and no users saw the issue again, we can remove this check altogether. - Datadog.logger.error( - 'Profiling was marked as supported and enabled, but setup task was not loaded properly. ' \ - 'Please report this at https://github.com/DataDog/dd-trace-rb/blob/master/CONTRIBUTING.md#found-a-bug' - ) - - return - end - - # Load extensions needed to support some of the Profiling features + # Activate forking extensions Profiling::Tasks::Setup.new.run # NOTE: Please update the Initialization section of ProfilingDevelopment.md with any changes to this method - no_signals_workaround_enabled = false - timeline_enabled = false + no_signals_workaround_enabled = no_signals_workaround_enabled?(settings) + timeline_enabled = settings.profiling.advanced.experimental_timeline_enabled - if enable_new_profiler?(settings) - no_signals_workaround_enabled = no_signals_workaround_enabled?(settings) - timeline_enabled = settings.profiling.advanced.experimental_timeline_enabled + recorder = Datadog::Profiling::StackRecorder.new( + cpu_time_enabled: RUBY_PLATFORM.include?('linux'), # Only supported on Linux currently + alloc_samples_enabled: false, # Always disabled for now -- work in progress + ) + thread_context_collector = Datadog::Profiling::Collectors::ThreadContext.new( + recorder: recorder, + max_frames: settings.profiling.advanced.max_frames, + tracer: optional_tracer, + endpoint_collection_enabled: settings.profiling.advanced.endpoint.collection.enabled, + timeline_enabled: timeline_enabled, + ) + worker = Datadog::Profiling::Collectors::CpuAndWallTimeWorker.new( + gc_profiling_enabled: enable_gc_profiling?(settings), + allocation_counting_enabled: settings.profiling.advanced.allocation_counting_enabled, + no_signals_workaround_enabled: no_signals_workaround_enabled, + thread_context_collector: thread_context_collector, + allocation_sample_every: 0, + ) - recorder = Datadog::Profiling::StackRecorder.new( - cpu_time_enabled: RUBY_PLATFORM.include?('linux'), # Only supported on Linux currently - alloc_samples_enabled: false, # Always disabled for now -- work in progress - ) - collector = Datadog::Profiling::Collectors::CpuAndWallTimeWorker.new( - recorder: recorder, - max_frames: settings.profiling.advanced.max_frames, - tracer: optional_tracer, - endpoint_collection_enabled: settings.profiling.advanced.endpoint.collection.enabled, - gc_profiling_enabled: enable_gc_profiling?(settings), - allocation_counting_enabled: settings.profiling.advanced.allocation_counting_enabled, - no_signals_workaround_enabled: no_signals_workaround_enabled, - timeline_enabled: timeline_enabled, - ) - else - load_pprof_support - - recorder = build_profiler_old_recorder(settings) - collector = build_profiler_oldstack_collector(settings, recorder, optional_tracer) - end - internal_metadata = { no_signals_workaround_enabled: no_signals_workaround_enabled, timeline_enabled: timeline_enabled, }.freeze exporter = build_profiler_exporter(settings, recorder, internal_metadata: internal_metadata) transport = build_profiler_transport(settings, agent_settings) scheduler = Profiling::Scheduler.new(exporter: exporter, transport: transport) - Profiling::Profiler.new([collector], scheduler) + Profiling::Profiler.new(worker: worker, scheduler: scheduler) end - private_class_method def self.build_profiler_old_recorder(settings) - Profiling::OldRecorder.new([Profiling::Events::StackSample], settings.profiling.advanced.max_events) - end - private_class_method def self.build_profiler_exporter(settings, recorder, internal_metadata:) code_provenance_collector = (Profiling::Collectors::CodeProvenance.new if settings.profiling.advanced.code_provenance_enabled) Profiling::Exporter.new( @@ -120,23 +82,10 @@ code_provenance_collector: code_provenance_collector, internal_metadata: internal_metadata, ) end - private_class_method def self.build_profiler_oldstack_collector(settings, old_recorder, tracer) - trace_identifiers_helper = Profiling::TraceIdentifiers::Helper.new( - tracer: tracer, - endpoint_collection_enabled: settings.profiling.advanced.endpoint.collection.enabled - ) - - Profiling::Collectors::OldStack.new( - old_recorder, - trace_identifiers_helper: trace_identifiers_helper, - max_frames: settings.profiling.advanced.max_frames - ) - end - private_class_method def self.build_profiler_transport(settings, agent_settings) settings.profiling.exporter.transport || Profiling::HttpTransport.new( agent_settings: agent_settings, site: settings.site, @@ -159,21 +108,10 @@ else false end end - private_class_method def self.enable_new_profiler?(settings) - if settings.profiling.advanced.force_enable_legacy_profiler - Datadog.logger.warn( - 'Legacy profiler has been force-enabled via configuration. Do not use unless instructed to by support.' - ) - return false - end - - true - end - private_class_method def self.no_signals_workaround_enabled?(settings) # rubocop:disable Metrics/MethodLength setting_value = settings.profiling.advanced.no_signals_workaround_enabled legacy_ruby_that_should_use_workaround = RUBY_VERSION.start_with?('2.3.', '2.4.', '2.5.') unless [true, false, :auto].include?(setting_value) @@ -296,22 +234,9 @@ "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}" ) true end - end - - # The old profiler's pprof support conflicts with the ruby-cloud-profiler gem. - # - # This is not a problem for almost all customers, since we now default everyone to use the new CPU Profiling 2.0 - # profiler. But the issue was still triggered, because currently we still _load_ both the old and new profiling - # code paths. - # - # To work around this issue, and because we plan on deleting the old profiler soon, rather than poking at the - # pprof support code, we only load the conflicting file when the old profiler is in use. This way customers using - # the new profiler will not be affected by the issue any longer. - private_class_method def self.load_pprof_support - require_relative 'pprof/pprof_pb' end end end end