Class: NewRelic::Agent::SqlSampler
- Inherits:
-
Object
- Object
- NewRelic::Agent::SqlSampler
- Defined in:
- lib/new_relic/agent/sql_sampler.rb
Overview
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.
Constant Summary collapse
- MAX_SAMPLES =
10
- PRIORITY =
'priority'.freeze
Instance Attribute Summary collapse
- #disabled ⇒ Object readonly
-
#sql_traces ⇒ Object
readonly
this is for unit tests only.
Instance Method Summary collapse
- #distributed_trace_attributes(state) ⇒ Object
- #enabled? ⇒ Boolean
- #harvest! ⇒ Object
-
#has_room? ⇒ Boolean
this should always be called under the @samples_lock.
-
#initialize ⇒ SqlSampler
constructor
A new instance of SqlSampler.
- #merge!(sql_traces) ⇒ Object
-
#notice_sql(sql, metric_name, config, duration, state = nil, explainer = nil, binds = nil, name = nil) ⇒ Object
deprecated
Deprecated.
Use Datastores.notice_sql instead.
- #notice_sql_statement(statement, metric_name, duration) ⇒ Object
-
#on_finishing_transaction(state, name) ⇒ Object
This is called when we are done with the transaction.
- #on_start_transaction(state, uri = nil) ⇒ Object
-
#remove_shortest_trace ⇒ Object
this should always be called under the @samples_lock.
- #reset! ⇒ Object
-
#save_slow_sql(transaction_sql_data) ⇒ Object
this should always be called under the @samples_lock.
-
#should_add_trace?(sql_item) ⇒ Boolean
this should always be called under the @samples_lock.
-
#tl_transaction_data ⇒ Object
only used for testing.
Constructor Details
#initialize ⇒ SqlSampler
Returns a new instance of SqlSampler.
33 34 35 36 37 38 39 40 |
# 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 |
Instance Attribute Details
#disabled ⇒ Object (readonly)
26 27 28 |
# File 'lib/new_relic/agent/sql_sampler.rb', line 26 def disabled @disabled end |
#sql_traces ⇒ Object (readonly)
this is for unit tests only
29 30 31 |
# File 'lib/new_relic/agent/sql_sampler.rb', line 29 def sql_traces @sql_traces end |
Instance Method Details
#distributed_trace_attributes(state) ⇒ Object
158 159 160 161 162 163 164 165 166 167 168 |
# 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 |
#enabled? ⇒ Boolean
42 43 44 45 46 |
# 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 |
#harvest! ⇒ Object
197 198 199 200 201 202 203 204 205 206 207 |
# 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 |
#has_room? ⇒ Boolean
this should always be called under the @samples_lock
115 116 117 |
# File 'lib/new_relic/agent/sql_sampler.rb', line 115 def has_room? @sql_traces.size < MAX_SAMPLES end |
#merge!(sql_traces) ⇒ Object
184 185 186 187 188 189 190 191 192 193 194 195 |
# 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 |
#notice_sql(sql, metric_name, config, duration, state = nil, explainer = nil, binds = nil, name = nil) ⇒ Object
Use Datastores.notice_sql instead.
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.
142 143 144 145 146 147 148 149 150 151 152 153 154 |
# 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 |
#notice_sql_statement(statement, metric_name, duration) ⇒ Object
170 171 172 173 174 175 176 177 178 179 180 181 182 |
# 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 |
#on_finishing_transaction(state, name) ⇒ Object
This is called when we are done with the transaction.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# 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 |
#on_start_transaction(state, uri = nil) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 |
# 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 |
#remove_shortest_trace ⇒ Object
this should always be called under the @samples_lock
120 121 122 123 |
# 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 |
#reset! ⇒ Object
209 210 211 212 213 |
# File 'lib/new_relic/agent/sql_sampler.rb', line 209 def reset! @samples_lock.synchronize do @sql_traces = {} end end |
#save_slow_sql(transaction_sql_data) ⇒ Object
this should always be called under the @samples_lock
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# 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 |
#should_add_trace?(sql_item) ⇒ Boolean
this should always be called under the @samples_lock
108 109 110 111 112 |
# 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 |