class NewRelic::Agent::SqlSampler
This class contains the logic of recording slow SQL traces, which may represent multiple aggregated SQL queries.
A slow SQL trace consists of a collection of SQL instrumented SQL queries that all normalize to the same text. For example, the following two queries would be aggregated together into a single slow SQL trace:
SELECT * FROM table WHERE id=42 SELECT * FROM table WHERE id=1234
Each slow SQL trace keeps track of the number of times the same normalized query was seen, the min, max, and total time spent executing those queries, and an example backtrace from one of the aggregated queries.
@api public
Constants
- MAX_SAMPLES
- PRIORITY
Attributes
this is for unit tests only
Public Class Methods
Source
# File lib/new_relic/agent/sql_sampler.rb, line 33 def initialize @sql_traces = {} # This lock is used to synchronize access to @sql_traces # and related variables. It can become necessary on JRuby or # any 'honest-to-god'-multithreaded system @samples_lock = Mutex.new end
Public Instance Methods
Source
# File lib/new_relic/agent/sql_sampler.rb, line 158 def distributed_trace_attributes(state) transaction = state.current_transaction params = nil if transaction&.distributed_tracer&.distributed_trace_payload params = {} payload = transaction.distributed_tracer.distributed_trace_payload DistributedTraceAttributes.copy_from_transaction(transaction, payload, params) params[PRIORITY] = transaction.priority end params end
Source
# File lib/new_relic/agent/sql_sampler.rb, line 42 def enabled? Agent.config[:'slow_sql.enabled'] && Agent.config[:'transaction_tracer.enabled'] && NewRelic::Agent::Database.should_record_sql?(:slow_sql) end
Source
# File lib/new_relic/agent/sql_sampler.rb, line 197 def harvest! return NewRelic::EMPTY_ARRAY unless enabled? slowest = [] @samples_lock.synchronize do slowest = @sql_traces.values @sql_traces = {} end slowest.each { |trace| trace.prepare_to_send } slowest end
Source
# File lib/new_relic/agent/sql_sampler.rb, line 115 def has_room? @sql_traces.size < MAX_SAMPLES end
this should always be called under the @samples_lock
Source
# File lib/new_relic/agent/sql_sampler.rb, line 184 def merge!(sql_traces) @samples_lock.synchronize do sql_traces.each do |trace| existing_trace = @sql_traces[trace.sql] if existing_trace existing_trace.aggregate_trace(trace) else @sql_traces[trace.sql] = trace end end end end
Source
# File lib/new_relic/agent/sql_sampler.rb, line 142 def notice_sql(sql, metric_name, config, duration, state = nil, explainer = nil, binds = nil, name = nil) # THREAD_LOCAL_ACCESS sometimes state ||= Tracer.state data = state.sql_sampler_transaction_data return unless data if state.is_sql_recorded? if duration > Agent.config[:'slow_sql.explain_threshold'] backtrace = caller.join("\n") statement = Database::Statement.new(sql, config, explainer, binds, name) data.sql_data << SlowSql.new(statement, metric_name, duration, backtrace) end end end
Records an SQL query, potentially creating a new slow SQL trace, or aggregating the query into an existing slow SQL trace.
This method should be used only by gem authors wishing to extend the Ruby agent to instrument new database interfaces - it should generally not be called directly from application code.
@param sql [String] the SQL query being recorded @param metric_name [String] is the metric name under which this query will be recorded @param config [Object] is the driver configuration for the connection @param duration [Float] number of seconds the query took to execute @param explainer [Proc] for internal use only - 3rd-party clients must
not pass this parameter.
@api public @deprecated Use {Datastores.notice_sql} instead.
Source
# File lib/new_relic/agent/sql_sampler.rb, line 170 def notice_sql_statement(statement, metric_name, duration) state ||= Tracer.state data = state.sql_sampler_transaction_data return unless data if state.is_sql_recorded? if duration > Agent.config[:'slow_sql.explain_threshold'] backtrace = caller.join("\n") params = distributed_trace_attributes(state) data.sql_data << SlowSql.new(statement, metric_name, duration, backtrace, params) end end end
Source
# File lib/new_relic/agent/sql_sampler.rb, line 67 def on_finishing_transaction(state, name) return unless enabled? data = state.sql_sampler_transaction_data return unless data data.set_transaction_name(name) if data.sql_data.size > 0 @samples_lock.synchronize do ::NewRelic::Agent.logger.debug("Examining #{data.sql_data.size} slow transaction sql statement(s)") save_slow_sql(data) end end end
This is called when we are done with the transaction.
Source
# File lib/new_relic/agent/sql_sampler.rb, line 48 def on_start_transaction(state, uri = nil) return unless enabled? state.sql_sampler_transaction_data = TransactionSqlData.new if state.current_transaction guid = state.current_transaction.guid end if Agent.config[:'slow_sql.enabled'] && state.sql_sampler_transaction_data state.sql_sampler_transaction_data.set_transaction_info(uri, guid) end end
Source
# File lib/new_relic/agent/sql_sampler.rb, line 120 def remove_shortest_trace shortest_key, _ = @sql_traces.min_by { |(_, trace)| trace.max_call_time } @sql_traces.delete(shortest_key) end
this should always be called under the @samples_lock
Source
# File lib/new_relic/agent/sql_sampler.rb, line 209 def reset! @samples_lock.synchronize do @sql_traces = {} end end
Source
# File lib/new_relic/agent/sql_sampler.rb, line 83 def save_slow_sql(transaction_sql_data) path = transaction_sql_data.path uri = transaction_sql_data.uri transaction_sql_data.sql_data.each do |sql_item| normalized_sql = sql_item.normalize sql_trace = @sql_traces[normalized_sql] if sql_trace sql_trace.aggregate(sql_item, path, uri) else if has_room? sql_trace = SqlTrace.new(normalized_sql, sql_item, path, uri) elsif should_add_trace?(sql_item) remove_shortest_trace sql_trace = SqlTrace.new(normalized_sql, sql_item, path, uri) end if sql_trace @sql_traces[normalized_sql] = sql_trace end end end end
this should always be called under the @samples_lock
Source
# File lib/new_relic/agent/sql_sampler.rb, line 108 def should_add_trace?(sql_item) @sql_traces.any? do |(_, existing_trace)| existing_trace.max_call_time < sql_item.duration end end
this should always be called under the @samples_lock
Source
# File lib/new_relic/agent/sql_sampler.rb, line 62 def tl_transaction_data # only used for testing Tracer.state.sql_sampler_transaction_data end