lib/new_relic/agent/agent.rb in newrelic_rpm-3.7.0.174.beta vs lib/new_relic/agent/agent.rb in newrelic_rpm-3.7.0.177

- old
+ new

@@ -17,10 +17,11 @@ require 'new_relic/agent/commands/agent_command_router' require 'new_relic/agent/event_listener' require 'new_relic/agent/cross_app_monitor' require 'new_relic/agent/request_sampler' require 'new_relic/agent/sampler_collection' +require 'new_relic/agent/javascript_instrumentor' require 'new_relic/environment_report' module NewRelic module Agent @@ -36,22 +37,21 @@ # This should be handled with a configuration callback if Agent.config[:monitor_mode] @service = NewRelic::Agent::NewRelicService.new end - @launch_time = Time.now - @events = NewRelic::Agent::EventListener.new @stats_engine = NewRelic::Agent::StatsEngine.new @transaction_sampler = NewRelic::Agent::TransactionSampler.new @sql_sampler = NewRelic::Agent::SqlSampler.new @agent_command_router = NewRelic::Agent::Commands::AgentCommandRouter.new(@events) @cross_app_monitor = NewRelic::Agent::CrossAppMonitor.new(@events) @error_collector = NewRelic::Agent::ErrorCollector.new @transaction_rules = NewRelic::Agent::RulesEngine.new @request_sampler = NewRelic::Agent::RequestSampler.new(@events) @harvest_samplers = NewRelic::Agent::SamplerCollection.new(@events) + @javascript_instrumentor = NewRelic::Agent::JavascriptInstrumentor.new(@events) @connect_state = :pending @connect_attempts = 0 @environment_report = nil @@ -84,14 +84,12 @@ # error collector is a simple collection of recorded errors attr_reader :error_collector attr_reader :harvest_samplers # whether we should record raw, obfuscated, or no sql attr_reader :record_sql - # a configuration for the Real User Monitoring system - - # handles things like static setup of the header for inclusion - # into pages - attr_reader :beacon_configuration + # builder for JS agent scripts to inject + attr_reader :javascript_instrumentor # cross application tracing ids and encoding attr_reader :cross_process_id attr_reader :cross_app_encoding_bytes attr_reader :cross_app_monitor # service for communicating with collector @@ -183,11 +181,11 @@ ::NewRelic::Agent.logger.debug "Starting the worker thread in #{$$} after forking." reset_objects_with_locks # Clear out stats that are left over from parent process - reset_stats + drop_buffered_data generate_environment_report unless @service.is_a?(NewRelic::Agent::PipeService) start_worker_thread(options) end @@ -497,21 +495,24 @@ log_startup check_config_and_start_agent log_version_and_pid end - # Clear out the metric data, errors, and transaction traces, - # making sure the agent is in a fresh state - def reset_stats - @stats_engine.reset_stats + # Clear out the metric data, errors, and transaction traces, etc. + def drop_buffered_data + @stats_engine.reset! @error_collector.reset! @transaction_sampler.reset! @request_sampler.reset! @sql_sampler.reset! - @launch_time = Time.now end + # Deprecated, and not part of the public API, but here for backwards + # compatibility because some 3rd-party gems call it. + # @deprecated + def reset_stats; drop_buffered_data; end + # Clear out state for any objects that we know lock from our parents # This is necessary for cases where we're in a forked child and Ruby # might be holding locks for background thread that aren't there anymore. def reset_objects_with_locks @stats_engine = NewRelic::Agent::StatsEngine.new @@ -551,25 +552,27 @@ return if harvest_lock.nil? harvest_lock.unlock if harvest_lock.locked? end - # Creates the worker loop and loads it with the instructions - # it should run every @report_period seconds + def create_worker_loop + WorkerLoop.new + end + def create_and_run_worker_loop - @worker_loop = WorkerLoop.new + @worker_loop = create_worker_loop @worker_loop.run(Agent.config[:data_report_period]) do transmit_data end end # Handles the case where the server tells us to restart - # this clears the data, clears connection attempts, and # waits a while to reconnect. def handle_force_restart(error) ::NewRelic::Agent.logger.debug error.message - reset_stats + drop_buffered_data @service.reset_metric_id_cache if @service @connect_state = :pending sleep 30 end @@ -792,12 +795,10 @@ @transaction_rules = RulesEngine.from_specs(config_data['transaction_name_rules']) @stats_engine.metric_rules = RulesEngine.from_specs(config_data['metric_name_rules']) # If you're adding something else here to respond to the server-side config, # use Agent.instance.events.subscribe(:finished_configuring) callback instead! - - @beacon_configuration = BeaconConfiguration.new end # Logs when we connect to the server, for debugging purposes # - makes sure we know if an agent has not connected def log_connection!(config_data) @@ -891,135 +892,107 @@ # directory of this project def determine_home_directory control.root end - # calls the busy harvester and collects timeslice data to - # send later - def harvest_timeslice_data - NewRelic::Agent::BusyCalculator.harvest_busy - @stats_engine.harvest + # Harvests data from the given container, sends it to the named endpoint + # on the service, and automatically merges back in upon a recoverable + # failure. + # + # The given container should respond to: + # + # #harvest! + # returns an enumerable collection of data items to be sent to the + # collector. + # + # #reset! + # drop any stored data and reset to a clean state. + # + # #merge!(items) + # merge the given items back into the internal buffer of the + # container, so that they may be harvested again later. + # + def harvest_and_send_from_container(container, endpoint) + items = harvest_from_container(container, endpoint) + send_data_to_endpoint(endpoint, items, container) unless items.empty? end - def harvest_and_send_timeslice_data - timeslices = harvest_timeslice_data + def harvest_from_container(container, endpoint) + items = [] begin - @service.metric_data(timeslices) + items = container.harvest! + rescue => e + NewRelic::Agent.logger.error("Failed to harvest #{endpoint} data, resetting. Error: ", e) + container.reset! + end + items + end + + def send_data_to_endpoint(endpoint, items, container) + NewRelic::Agent.logger.debug("Sending #{items.size} items to #{endpoint}") + begin + @service.send(endpoint, items) + rescue ForceRestartException, ForceDisconnectException + raise rescue UnrecoverableServerException => e - ::NewRelic::Agent.logger.debug e.message + NewRelic::Agent.logger.warn("#{endpoint} data was rejected by remote service, discarding. Error: ", e) rescue => e - NewRelic::Agent.logger.info("Failed to send timeslice data, trying again later. Error:", e) - @stats_engine.merge!(timeslices) + NewRelic::Agent.logger.info("Unable to send #{endpoint} data, will try again later. Error: ", e) + container.merge!(items) end end + def harvest_and_send_timeslice_data + NewRelic::Agent::BusyCalculator.harvest_busy + harvest_and_send_from_container(@stats_engine, :metric_data) + end + def harvest_and_send_slowest_sql - # FIXME add the code to try to resend if our connection is down - sql_traces = @sql_sampler.harvest - unless sql_traces.empty? - ::NewRelic::Agent.logger.debug "Sending (#{sql_traces.size}) sql traces" - begin - @service.sql_trace_data(sql_traces) - rescue UnrecoverableServerException => e - ::NewRelic::Agent.logger.debug e.message - rescue => e - ::NewRelic::Agent.logger.debug "Remerging SQL traces after #{e.class.name}: #{e.message}" - @sql_sampler.merge!(sql_traces) - end - end + harvest_and_send_from_container(@sql_sampler, :sql_trace_data) end # This handles getting the transaction traces and then sending # them across the wire. This includes gathering SQL # explanations, stripping out stack traces, and normalizing # SQL. note that we explain only the sql statements whose # segments' execution times exceed our threshold (to avoid # unnecessary overhead of running explains on fast queries.) def harvest_and_send_transaction_traces - traces = @transaction_sampler.harvest - unless traces.empty? - begin - send_transaction_traces(traces) - rescue UnrecoverableServerException => e - # This indicates that there was a problem with the POST body, so - # we discard the traces rather than trying again later. - ::NewRelic::Agent.logger.debug("Server rejected transaction traces, discarding. Error: ", e) - rescue => e - ::NewRelic::Agent.logger.error("Failed to send transaction traces, will re-attempt next harvest. Error: ", e) - @transaction_sampler.merge!(traces) - end - end + harvest_and_send_from_container(@transaction_sampler, :transaction_sample_data) end - def send_transaction_traces(traces) - start_time = Time.now - ::NewRelic::Agent.logger.debug "Sending (#{traces.length}) transaction traces" - - options = {} - unless NewRelic::Agent::Database.record_sql_method == :off - options[:record_sql] = NewRelic::Agent::Database.record_sql_method - end - - if Agent.config[:'transaction_tracer.explain_enabled'] - options[:explain_sql] = Agent.config[:'transaction_tracer.explain_threshold'] - end - - traces.each { |trace| trace.prepare_to_send!(options) } - - @service.transaction_sample_data(traces) - ::NewRelic::Agent.logger.debug "Sent slowest sample (#{@service.agent_id}) in #{Time.now - start_time} seconds" + def harvest_and_send_for_agent_commands + harvest_and_send_from_container(@agent_command_router, :profile_data) end - def harvest_and_send_for_agent_commands(disconnecting=false) - data = @agent_command_router.harvest_data_to_send(disconnecting) - data.each do |service_method, payload| - @service.send(service_method, payload) - end - end - - # Handles getting the errors from the error collector and - # sending them to the server, and any error cases like trying - # to send very large errors def harvest_and_send_errors - errors = @error_collector.harvest_errors - if errors && !errors.empty? - ::NewRelic::Agent.logger.debug "Sending #{errors.length} errors" - begin - @service.error_data(errors) - rescue UnrecoverableServerException => e - ::NewRelic::Agent.logger.debug e.message - rescue => e - ::NewRelic::Agent.logger.debug "Failed to send error traces, will try again later. Error:", e - @error_collector.merge!(errors) - end - end + harvest_and_send_from_container(@error_collector, :error_data) end - # Fetch samples from the RequestSampler and send them. def harvest_and_send_analytic_event_data - samples = @request_sampler.harvest + harvest_and_send_from_container(@request_sampler, :analytic_event_data) + end + + def check_for_and_handle_agent_commands begin - @service.analytic_event_data(samples) unless samples.empty? - rescue - @request_sampler.merge!(samples) + @agent_command_router.check_for_and_handle_agent_commands + rescue ForceRestartException, ForceDisconnectException raise + rescue => e + NewRelic::Agent.logger.warn("Error during check_for_and_handle_agent_commands, will retry later: ", e) end end - def check_for_and_handle_agent_commands - @agent_command_router.check_for_and_handle_agent_commands - end - - def transmit_data(disconnecting=false) + def transmit_data harvest_lock.synchronize do - transmit_data_already_locked(disconnecting) + transmit_data_already_locked end end # This method is expected to only be called with the harvest_lock # already held - def transmit_data_already_locked(disconnecting) + def transmit_data_already_locked now = Time.now ::NewRelic::Agent.logger.debug "Sending data to New Relic Service" @events.notify(:before_harvest) @service.session do # use http keep-alive @@ -1028,23 +1001,12 @@ harvest_and_send_slowest_sql harvest_and_send_timeslice_data harvest_and_send_analytic_event_data check_for_and_handle_agent_commands - harvest_and_send_for_agent_commands(disconnecting) + harvest_and_send_for_agent_commands end - rescue EOFError => e - ::NewRelic::Agent.logger.warn("EOFError after #{Time.now - now}s when transmitting data to New Relic Service.") - ::NewRelic::Agent.logger.debug(e) - rescue => e - retry_count ||= 0 - retry_count += 1 - if retry_count <= 1 - ::NewRelic::Agent.logger.debug "retrying transmit_data after #{e}" - retry - end - raise e ensure NewRelic::Agent::Database.close_connections duration = (Time.now - now).to_f @stats_engine.record_metrics('Supportability/Harvest', duration) end @@ -1060,12 +1022,14 @@ # disconnect, so that the parent process can continue to send data def graceful_disconnect if connected? begin @service.request_timeout = 10 - transmit_data(true) + @events.notify(:before_shutdown) + transmit_data + if @connected_pid == $$ && !@service.kind_of?(NewRelic::Agent::NewRelicService) ::NewRelic::Agent.logger.debug "Sending New Relic service agent run shutdown message" @service.shutdown(Time.now.to_f) else ::NewRelic::Agent.logger.debug "This agent connected from parent process #{@connected_pid}--not sending shutdown" @@ -1080,9 +1044,8 @@ end end extend ClassMethods include InstanceMethods - include BrowserMonitoring end end end