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