lib/new_relic/agent/agent.rb in newrelic_rpm-8.10.1 vs lib/new_relic/agent/agent.rb in newrelic_rpm-8.11.0

- old
+ new

@@ -1,6 +1,5 @@ -# encoding: utf-8 # This file is distributed under New Relic's license terms. # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. # frozen_string_literal: true require 'socket' @@ -58,57 +57,66 @@ include NewRelic::Agent::SpecialStartup include NewRelic::Agent::Startup include NewRelic::Agent::Shutdown def initialize + init_basics + init_components + init_event_handlers + setup_attribute_filter + end + + private + + def init_basics @started = false @event_loop = nil @worker_thread = nil + @connect_state = :pending + @connect_attempts = 0 + @waited_on_connect = nil + @connected_pid = nil + @wait_on_connect_mutex = Mutex.new + @after_fork_lock = Mutex.new + @wait_on_connect_condition = ConditionVariable.new + end + def init_components @service = NewRelicService.new - @events = EventListener.new @stats_engine = StatsEngine.new @transaction_sampler = TransactionSampler.new @sql_sampler = SqlSampler.new - @agent_command_router = Commands::AgentCommandRouter.new(@events) - @monitors = Monitors.new(@events) - @error_collector = ErrorCollector.new(@events) @transaction_rules = RulesEngine.new - @harvest_samplers = SamplerCollection.new(@events) @monotonic_gc_profiler = VM::MonotonicGCProfiler.new - @javascript_instrumentor = JavascriptInstrumentor.new(@events) @adaptive_sampler = AdaptiveSampler.new(Agent.config[:sampling_target], Agent.config[:sampling_target_period_in_seconds]) + end + def init_event_handlers + @agent_command_router = Commands::AgentCommandRouter.new(@events) + @monitors = Monitors.new(@events) + @error_collector = ErrorCollector.new(@events) + @harvest_samplers = SamplerCollection.new(@events) + @javascript_instrumentor = JavaScriptInstrumentor.new(@events) @harvester = Harvester.new(@events) - @after_fork_lock = Mutex.new - @transaction_event_recorder = TransactionEventRecorder.new(@events) @custom_event_aggregator = CustomEventAggregator.new(@events) @span_event_aggregator = SpanEventAggregator.new(@events) @log_event_aggregator = LogEventAggregator.new(@events) - - @connect_state = :pending - @connect_attempts = 0 - @waited_on_connect = nil - @connected_pid = nil - - @wait_on_connect_mutex = Mutex.new - @wait_on_connect_condition = ConditionVariable.new - - setup_attribute_filter end def setup_attribute_filter refresh_attribute_filter @events.subscribe(:initial_configuration_complete) do refresh_attribute_filter end end + public + def refresh_attribute_filter @attribute_filter = AttributeFilter.new(Agent.config) end # contains all the class-level methods for NewRelic::Agent::Agent @@ -152,11 +160,11 @@ attr_reader :monitors # Transaction and metric renaming rules as provided by the # collector on connect. The former are applied during txns, # the latter during harvest. attr_accessor :transaction_rules - # Responsbile for restarting the harvest thread + # Responsible for restarting the harvest thread attr_reader :harvester # GC::Profiler.total_time is not monotonic so we wrap it. attr_reader :monotonic_gc_profiler attr_reader :custom_event_aggregator attr_reader :span_event_aggregator @@ -180,11 +188,11 @@ # This method should be called in a forked process after a fork. # It assumes the parent process initialized the agent, but does # not assume the agent started. # - # The call is idempotent, but not re-entrant. + # The call is idempotent, but not reentrant. # # * It clears any metrics carried over from the parent process # * Restarts the sampler thread if necessary # * Initiates a new agent run and worker loop unless that was done # in the parent process and +:force_reconnect+ is not true @@ -196,22 +204,12 @@ # had not connected. # * <tt>:keep_retrying => false</tt> if we try to initiate a new # connection, this tells me to only try it once so this method returns # quickly if there is some kind of latency with the server. def after_fork(options = {}) - needs_restart = false - @after_fork_lock.synchronize do - needs_restart = @harvester.needs_restart? - @harvester.mark_started - end + return unless needs_after_fork_work? - return if !needs_restart || - !Agent.config[:agent_enabled] || - !Agent.config[:monitor_mode] || - disconnected? || - !control.security_settings_valid? - ::NewRelic::Agent.logger.debug("Starting the worker thread in #{Process.pid} (parent #{Process.ppid}) after forking.") channel_id = options[:report_to_channel] install_pipe_service(channel_id) if channel_id @@ -220,10 +218,26 @@ drop_buffered_data setup_and_start_agent(options) end + def needs_after_fork_work? + needs_restart = false + @after_fork_lock.synchronize do + needs_restart = @harvester.needs_restart? + @harvester.mark_started + end + + return false if !needs_restart || + !Agent.config[:agent_enabled] || + !Agent.config[:monitor_mode] || + disconnected? || + !control.security_settings_valid? + + true + end + def install_pipe_service(channel_id) @service = PipeService.new(channel_id) if connected? @connected_pid = Process.pid else @@ -294,16 +308,11 @@ @stats_engine = StatsEngine.new end def flush_pipe_data if connected? && @service.is_a?(PipeService) - transmit_data - transmit_analytic_event_data - transmit_custom_event_data - transmit_error_event_data - transmit_span_event_data - transmit_log_event_data + transmit_data_types end end private @@ -514,16 +523,11 @@ # with New Relic, even if there is already an existing connection. # This is useful primarily when re-establishing a new connection after # forking off from a parent process. # def connect(options = {}) - defaults = { - :keep_retrying => Agent.config[:keep_retrying], - :force_reconnect => Agent.config[:force_reconnect] - } - opts = defaults.merge(options) - + opts = connect_options(options) return unless should_connect?(opts[:force_reconnect]) ::NewRelic::Agent.logger.debug("Connecting Process to New Relic: #$0") connect_to_server @connected_pid = $$ @@ -534,25 +538,36 @@ rescue NewRelic::Agent::LicenseException => e handle_license_error(e) rescue NewRelic::Agent::UnrecoverableAgentException => e handle_unrecoverable_agent_error(e) rescue StandardError, Timeout::Error, NewRelic::Agent::ServerConnectionException => e + retry if retry_from_error?(e, opts) + rescue Exception => e + ::NewRelic::Agent.logger.error("Exception of unexpected type during Agent#connect():", e) + + raise + end + + def connect_options(options) + { + keep_retrying: Agent.config[:keep_retrying], + force_reconnect: Agent.config[:force_reconnect] + }.merge(options) + end + + def retry_from_error?(e, opts) # Allow a killed (aborting) thread to continue exiting during shutdown. # See: https://github.com/newrelic/newrelic-ruby-agent/issues/340 raise if Thread.current.status == 'aborting' log_error(e) - if opts[:keep_retrying] - note_connect_failure - ::NewRelic::Agent.logger.info("Will re-attempt in #{connect_retry_period} seconds") - sleep(connect_retry_period) - retry - end - rescue Exception => e - ::NewRelic::Agent.logger.error("Exception of unexpected type during Agent#connect():", e) + return false unless opts[:keep_retrying] - raise + note_connect_failure + ::NewRelic::Agent.logger.info("Will re-attempt in #{connect_retry_period} seconds") + sleep(connect_retry_period) + true end # Delegates to the control class to determine the root # directory of this project def determine_home_directory @@ -733,17 +748,11 @@ now = Process.clock_gettime(Process::CLOCK_MONOTONIC) ::NewRelic::Agent.logger.debug("Sending data to New Relic Service") @events.notify(:before_harvest) @service.session do # use http keep-alive - harvest_and_send_errors - harvest_and_send_error_event_data - harvest_and_send_transaction_traces - harvest_and_send_slowest_sql - harvest_and_send_timeslice_data - harvest_and_send_span_event_data - harvest_and_send_log_event_data + harvest_and_send_data_types check_for_and_handle_agent_commands harvest_and_send_for_agent_commands end ensure @@ -763,30 +772,48 @@ if connected? begin @service.request_timeout = 10 @events.notify(:before_shutdown) - transmit_data - transmit_analytic_event_data - transmit_custom_event_data - transmit_error_event_data - transmit_span_event_data - transmit_log_event_data + transmit_data_types + shutdown_service - if @connected_pid == $$ && !@service.kind_of?(NewRelic::Agent::NewRelicService) - ::NewRelic::Agent.logger.debug("Sending New Relic service agent run shutdown message") - @service.shutdown - else - ::NewRelic::Agent.logger.debug("This agent connected from parent process #{@connected_pid}--not sending shutdown") - end ::NewRelic::Agent.logger.debug("Graceful disconnect complete") rescue Timeout::Error, StandardError => e ::NewRelic::Agent.logger.debug("Error when disconnecting #{e.class.name}: #{e.message}") end else ::NewRelic::Agent.logger.debug("Bypassing graceful disconnect - agent not connected") end end + end + + def shutdown_service + if @connected_pid == $$ && !@service.kind_of?(NewRelic::Agent::NewRelicService) + ::NewRelic::Agent.logger.debug("Sending New Relic service agent run shutdown message") + @service.shutdown + else + ::NewRelic::Agent.logger.debug("This agent connected from parent process #{@connected_pid}--not sending shutdown") + end + end + + def transmit_data_types + transmit_data + transmit_analytic_event_data + transmit_custom_event_data + transmit_error_event_data + transmit_span_event_data + transmit_log_event_data + end + + def harvest_and_send_data_types + harvest_and_send_errors + harvest_and_send_error_event_data + harvest_and_send_transaction_traces + harvest_and_send_slowest_sql + harvest_and_send_timeslice_data + harvest_and_send_span_event_data + harvest_and_send_log_event_data end extend ClassMethods include InstanceMethods end