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