lib/new_relic/agent/agent.rb in newrelic_rpm-3.1.2 vs lib/new_relic/agent/agent.rb in newrelic_rpm-3.2.0.beta1
- old
+ new
@@ -33,18 +33,20 @@
@launch_time = Time.now
@metric_ids = {}
@stats_engine = NewRelic::Agent::StatsEngine.new
@transaction_sampler = NewRelic::Agent::TransactionSampler.new
+ @sql_sampler = NewRelic::Agent::SqlSampler.new
@stats_engine.transaction_sampler = @transaction_sampler
+ @stats_engine.sql_sampler = @sql_sampler
@error_collector = NewRelic::Agent::ErrorCollector.new
@connect_attempts = 0
@request_timeout = NewRelic::Control.instance.fetch('timeout', 2 * 60)
@last_harvest_time = Time.now
- @obfuscator = method(:default_sql_obfuscator)
+ @obfuscator = lambda {|sql| NewRelic::Agent::Database.default_sql_obfuscator(sql) }
end
# contains all the class-level methods for NewRelic::Agent::Agent
module ClassMethods
# Should only be called by NewRelic::Control - returns a
@@ -62,10 +64,11 @@
attr_reader :obfuscator
# the statistics engine that holds all the timeslice data
attr_reader :stats_engine
# the transaction sampler that handles recording transactions
attr_reader :transaction_sampler
+ attr_reader :sql_sampler
# error collector is a simple collection of recorded errors
attr_reader :error_collector
# whether we should record raw, obfuscated, or no sql
attr_reader :record_sql
# a cached set of metric_ids to save the collector some time -
@@ -269,33 +272,10 @@
# to what it was before we pushed the current flag.
def pop_trace_execution_flag
Thread.current[:newrelic_untraced].pop if Thread.current[:newrelic_untraced]
end
- # Sets the sql obfuscator used to clean up sql when sending it
- # to the server. Possible types are:
- #
- # :before => sets the block to run before the existing
- # obfuscators
- #
- # :after => sets the block to run after the existing
- # obfuscator(s)
- #
- # :replace => removes the current obfuscator and replaces it
- # with the provided block
- def set_sql_obfuscator(type, &block)
- if type == :before
- @obfuscator = NewRelic::ChainedCall.new(block, @obfuscator)
- elsif type == :after
- @obfuscator = NewRelic::ChainedCall.new(@obfuscator, block)
- elsif type == :replace
- @obfuscator = block
- else
- fail "unknown sql_obfuscator type #{type}"
- end
- end
-
# Shorthand to the NewRelic::Agent.logger method
def log
NewRelic::Agent.logger
end
@@ -329,72 +309,10 @@
# Logs the configured application names
def log_app_names
log.info "Application: #{control.app_names.join(", ")}"
end
- # apdex_f is always 4 times the apdex_t
- def apdex_f
- (4 * NewRelic::Control.instance.apdex_t).to_f
- end
-
- # If the transaction threshold is set to the string
- # 'apdex_f', we use 4 times the apdex_t value to record
- # transactions. This gears well with using apdex since you
- # will attempt to send any transactions that register as 'failing'
- def apdex_f_threshold?
- sampler_config.fetch('transaction_threshold', '') =~ /apdex_f/i
- end
-
- # Sets the sql recording configuration by trying to detect
- # any attempt to disable the sql collection - 'off',
- # 'false', 'none', and friends. Otherwise, we accept 'raw',
- # and unrecognized values default to 'obfuscated'
- def set_sql_recording!
- record_sql_config = sampler_config.fetch('record_sql', :obfuscated)
- case record_sql_config.to_s
- when 'off'
- @record_sql = :off
- when 'none'
- @record_sql = :off
- when 'false'
- @record_sql = :off
- when 'raw'
- @record_sql = :raw
- else
- @record_sql = :obfuscated
- end
-
- log_sql_transmission_warning?
- end
-
- # Warn the user when we are sending raw sql across the wire
- # - they should probably be using ssl when this is true
- def log_sql_transmission_warning?
- log_if((@record_sql == :raw), :warn, "Agent is configured to send raw SQL to the service")
- end
-
- # gets the sampler configuration from the control object's settings
- def sampler_config
- control.fetch('transaction_tracer', {})
- end
-
- # this entire method should be done on the transaction
- # sampler object, rather than here. We should pass in the
- # sampler config.
- def config_transaction_tracer
- @should_send_samples = @config_should_send_samples = sampler_config.fetch('enabled', true)
- @should_send_random_samples = sampler_config.fetch('random_sample', false)
- @explain_threshold = sampler_config.fetch('explain_threshold', 0.5).to_f
- @explain_enabled = sampler_config.fetch('explain_enabled', true)
- set_sql_recording!
-
- # default to 2.0, string 'apdex_f' will turn into your
- # apdex * 4
- @slowest_transaction_threshold = sampler_config.fetch('transaction_threshold', 2.0).to_f
- @slowest_transaction_threshold = apdex_f if apdex_f_threshold?
- end
-
# Connecting in the foreground blocks further startup of the
# agent until we have a connection - useful in cases where
# you're trying to log a very-short-running process and want
# to get statistics from before a server connection
# (typically 20 seconds) exists
@@ -546,10 +464,20 @@
else
@transaction_sampler.disable
end
end
+ def check_sql_sampler_status
+ # disable sql sampling if disabled by the server
+ # and we're not in dev mode
+ if control.developer_mode? || @should_send_samples
+ @sql_sampler.enable
+ else
+ @sql_sampler.disable
+ end
+ end
+
# logs info about the worker loop so users can see when the
# agent actually begins running in the background
def log_worker_loop_start
log.info "Reporting performance data every #{@report_period} seconds."
log.debug "Running worker loop"
@@ -631,10 +559,11 @@
# just exit the thread. If it returns nil
# that means it didn't try to connect because we're in the master.
connect(connection_options)
if @connected
check_transaction_sampler_status
+ check_sql_sampler_status
log_worker_loop_start
create_and_run_worker_loop
# never reaches here unless there is a problem or
# the agent is exiting
else
@@ -794,10 +723,12 @@
# Configures the error collector if the server says that we
# are allowed to send errors. Pretty simple, and logs at
# debug whether errors will or will not be sent.
def configure_error_collector!(server_enabled)
+ # Reinitialize the error collector
+ @error_collector = NewRelic::Agent::ErrorCollector.new
# Ask for permission to collect error data
enabled = if error_collector.config_enabled && server_enabled
error_collector.enabled = true
else
error_collector.enabled = false
@@ -818,10 +749,28 @@
@transaction_sampler.random_sampling = true
@transaction_sampler.sampling_rate = sample_rate
log.info "Transaction sampling enabled, rate = #{@transaction_sampler.sampling_rate}"
end
+ # this entire method should be done on the transaction
+ # sampler object, rather than here. We should pass in the
+ # sampler config.
+ def config_transaction_tracer
+ # Reconfigure the transaction tracer
+ @transaction_sampler.configure!
+ @should_send_samples = @config_should_send_samples = sampler_config.fetch('enabled', true)
+ @should_send_random_samples = sampler_config.fetch('random_sample', false)
+ @explain_threshold = sampler_config.fetch('explain_threshold', 0.5).to_f
+ @explain_enabled = sampler_config.fetch('explain_enabled', true)
+ set_sql_recording!
+
+ # default to 2.0, string 'apdex_f' will turn into your
+ # apdex * 4
+ @slowest_transaction_threshold = sampler_config.fetch('transaction_threshold', 2.0).to_f
+ @slowest_transaction_threshold = apdex_f if apdex_f_threshold?
+ end
+
# Enables or disables the transaction tracer and sets its
# options based on the options provided to the
# method.
def configure_transaction_tracer!(server_enabled, sample_rate)
# Ask the server for permission to send transaction samples.
@@ -835,10 +784,56 @@
else
log.debug "Transaction traces will not be sent to the New Relic service."
end
end
+ # apdex_f is always 4 times the apdex_t
+ def apdex_f
+ (4 * NewRelic::Control.instance.apdex_t).to_f
+ end
+
+ # If the transaction threshold is set to the string
+ # 'apdex_f', we use 4 times the apdex_t value to record
+ # transactions. This gears well with using apdex since you
+ # will attempt to send any transactions that register as 'failing'
+ def apdex_f_threshold?
+ sampler_config.fetch('transaction_threshold', '') =~ /apdex_f/i
+ end
+
+ # Sets the sql recording configuration by trying to detect
+ # any attempt to disable the sql collection - 'off',
+ # 'false', 'none', and friends. Otherwise, we accept 'raw',
+ # and unrecognized values default to 'obfuscated'
+ def set_sql_recording!
+ record_sql_config = sampler_config.fetch('record_sql', :obfuscated)
+ case record_sql_config.to_s
+ when 'off'
+ @record_sql = :off
+ when 'none'
+ @record_sql = :off
+ when 'false'
+ @record_sql = :off
+ when 'raw'
+ @record_sql = :raw
+ else
+ @record_sql = :obfuscated
+ end
+
+ log_sql_transmission_warning?
+ end
+
+ # Warn the user when we are sending raw sql across the wire
+ # - they should probably be using ssl when this is true
+ def log_sql_transmission_warning?
+ log.warn("Agent is configured to send raw SQL to the service") if @record_sql == :raw
+ end
+
+ # gets the sampler configuration from the control object's settings
+ def sampler_config
+ control.fetch('transaction_tracer', {})
+ end
+
# Asks the collector to tell us which sub-collector we
# should be reporting to, and then does the name resolution
# on that host so we don't block on DNS during the normal
# course of agent processing
def set_collector_host!
@@ -866,11 +861,14 @@
def finish_setup(config_data)
@agent_id = config_data['agent_run_id']
@report_period = config_data['data_report_period']
@url_rules = config_data['url_rules']
@beacon_configuration = BeaconConfiguration.new(config_data)
+ @server_side_config_enabled = config_data['listen_to_server_config']
+ control.merge_server_side_config(config_data) if @server_side_config_enabled
+ config_transaction_tracer
log_connection!(config_data)
configure_transaction_tracer!(config_data['collect_traces'], config_data['sample_rate'])
configure_error_collector!(config_data['collect_errors'])
end
@@ -1041,10 +1039,24 @@
def harvest_transaction_traces
@traces = @transaction_sampler.harvest(@traces, @slowest_transaction_threshold)
@traces
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?
+ log.debug "Sending (#{sql_traces.count}) sql traces"
+ begin
+ response = invoke_remote :sql_trace_data, sql_traces
+# log.debug "Sql trace response: #{response}"
+ rescue
+ @sql_sampler.merge sql_traces
+ end
+ end
+ 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
@@ -1255,10 +1267,11 @@
if NewRelic::DataSerialization.should_send_data?
log.debug "Sending data to New Relic Service"
NewRelic::Agent.load_data
harvest_and_send_errors
harvest_and_send_slowest_sample
+ harvest_and_send_slowest_sql
harvest_and_send_timeslice_data
else
log.debug "Serializing agent data to disk"
NewRelic::Agent.save_data
end
@@ -1286,20 +1299,9 @@
rescue Timeout::Error, StandardError
end
else
log.debug "Bypassing graceful disconnect - agent not connected"
end
- end
- def default_sql_obfuscator(sql)
- sql = sql.dup
- # This is hardly readable. Use the unit tests.
- # remove single quoted strings:
- sql.gsub!(/'(.*?[^\\'])??'(?!')/, '?')
- # remove double quoted strings:
- sql.gsub!(/"(.*?[^\\"])??"(?!")/, '?')
- # replace all number literals
- sql.gsub!(/\d+/, "?")
- sql
end
end
extend ClassMethods
include InstanceMethods