Module: ActiveRecordQueryCounter

Defined in:
lib/active_record_query_counter.rb,
lib/active_record_query_counter/counter.rb,
lib/active_record_query_counter/thresholds.rb,
lib/active_record_query_counter/rack_middleware.rb,
lib/active_record_query_counter/transaction_info.rb,
lib/active_record_query_counter/sidekiq_middleware.rb,
lib/active_record_query_counter/transaction_extension.rb,
lib/active_record_query_counter/connection_adapter_extension.rb

Overview

Everything you need to count ActiveRecord queries and row counts within a block.

Examples:


ActiveRecordQueryCounter.count_queries do
  yield
  puts ActiveRecordQueryCounter.query_count
  puts ActiveRecordQueryCounter.row_count
end

Defined Under Namespace

Modules: ConnectionAdapterExtension, TransactionExtension Classes: Counter, RackMiddleware, SidekiqMiddleware, Thresholds, TransactionInfo

Constant Summary collapse

VERSION =
File.read(File.join(__dir__, "..", "VERSION")).strip

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.default_thresholdsActiveRecordQueryCounter::Thresholds (readonly)

The global notification thresholds for sending notifications. The values set in these thresholds are used as the default values.



268
269
270
# File 'lib/active_record_query_counter.rb', line 268

def default_thresholds
  @default_thresholds
end

Class Method Details

.add_query(sql, name, binds, row_count, start_time, end_time, gc_time, cpu_time) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Increment the query counters.

The reported query time is the wall clock time spent executing the query with the GC time and Ruby thread CPU time subtracted out so that it reflects the time actually spent waiting on the database as closely as possible (see database_query_time). This query time, rather than the raw wall clock time, is what is accumulated, compared against the threshold, and used as the duration of the emitted notification.

Parameters:

  • sql (String)

    the SQL statement that was executed

  • name (String, nil)

    the name of the query

  • binds (Array)

    the bind parameters

  • row_count (Integer)

    the number of rows returned by the query

  • start_time (Float)

    the monotonic time when the query started

  • end_time (Float)

    the monotonic time when the query ended

  • gc_time (Float)

    the GC time in seconds that elapsed while the query ran

  • cpu_time (Float)

    the thread CPU time in seconds spent while the query ran



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/active_record_query_counter.rb', line 90

def add_query(sql, name, binds, row_count, start_time, end_time, gc_time, cpu_time)
  return if IGNORED_STATEMENTS.include?(name)

  counter = current_counter
  return unless counter.is_a?(Counter)

  elapsed_time = end_time - start_time
  query_time = database_query_time(elapsed_time, gc_time, cpu_time)
  counter.query_count += 1
  counter.row_count += row_count
  counter.query_time += query_time

  # The notification duration is the database query time, so the event ends that long after
  # it started rather than at the raw wall clock end time.
  notification_end_time = start_time + query_time

  trace = nil
  query_time_threshold = counter.thresholds.query_time || -1
  if query_time_threshold.between?(0, query_time)
    trace = backtrace
    payload = notification_payload(sql: sql, binds: binds, row_count: row_count, trace: trace, elapsed_time: elapsed_time, gc_time: gc_time, cpu_time: cpu_time)
    send_notification("query_time", start_time, notification_end_time, **payload)
  end

  row_count_threshold = counter.thresholds.row_count || -1
  if row_count_threshold.between?(0, row_count)
    trace ||= backtrace
    payload = notification_payload(sql: sql, binds: binds, row_count: row_count, trace: trace, elapsed_time: elapsed_time, gc_time: gc_time, cpu_time: cpu_time)
    send_notification("row_count", start_time, notification_end_time, **payload)
  end
end

.add_transaction(start_time, end_time) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Increment the transaction counters.

Parameters:

  • start_time (Float)

    the time the transaction started

  • end_time (Float)

    the time the transaction ended



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/active_record_query_counter.rb', line 128

def add_transaction(start_time, end_time)
  counter = current_counter
  return unless counter.is_a?(Counter)

  trace = backtrace
  counter.add_transaction(trace: trace, start_time: start_time, end_time: end_time)

  transaction_time_threshold = counter.thresholds.transaction_time || -1
  if transaction_time_threshold.between?(0, end_time - start_time)
    send_notification("transaction_time", start_time, end_time, trace: backtrace)
  end
end

.cached_query_countInteger?

Return the number of queries that hit the query cache and were not sent to the database that have been counted within the current block. Returns nil if not inside a block where queries are being counted.

Returns:

  • (Integer, nil)


166
167
168
169
# File 'lib/active_record_query_counter.rb', line 166

def cached_query_count
  counter = current_counter
  counter.cached_query_count if counter.is_a?(Counter)
end

.count_queriesObject

Enable query counting within a block.

Returns:

  • (Object)

    the result of the block



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/active_record_query_counter.rb', line 36

def count_queries
  save_counter = current_counter
  begin
    counter = Counter.new
    self.current_counter = counter

    retval = yield

    transaction_count = counter.transaction_count
    if transaction_count > 0
      transaction_threshold = counter.thresholds.transaction_count || -1
      if transaction_threshold.between?(0, transaction_count)
        send_notification("transaction_count", counter.first_transaction_start_time, counter.last_transaction_end_time, transactions: counter.transactions)
      end
    end

    retval
  ensure
    self.current_counter = save_counter
  end
end

.disable(&block) ⇒ Object

Disable query counting in a block. Any queries or transactions inside the block will not be counted.

Returns:

  • (Object)

    the return value of the block



62
63
64
65
66
67
68
69
70
# File 'lib/active_record_query_counter.rb', line 62

def disable(&block)
  counter = current_counter
  begin
    self.current_counter = nil
    yield
  ensure
    self.current_counter = counter
  end
end

.enable!(connection_class) ⇒ void

This method returns an undefined value.

Enable the query counting behavior on a connection adapter class.

Parameters:

  • connection_class (Class)

    the connection adapter class to extend



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/active_record_query_counter.rb', line 280

def enable!(connection_class)
  ActiveSupport.on_load(:active_record) do
    ConnectionAdapterExtension.inject(connection_class)
    TransactionExtension.inject(ActiveRecord::ConnectionAdapters::RealTransaction)
  end

  @lock.synchronize do
    @cache_subscription ||= ActiveSupport::Notifications.subscribe("sql.active_record") do |_name, _start_time, _end_time, _id, payload|
      if payload[:cached] && !IGNORED_STATEMENTS.include?(payload[:name])
        counter = current_counter
        counter.cached_query_count += 1 if counter.is_a?(Counter)
      end
    end
  end
end

.first_transaction_start_timeFloat?

Return the time when the first transaction began within the current block. Returns nil if not inside a block where queries are being counted or there are no transactions.

Returns:

  • (Float, nil)

    the monotonic time when the first transaction began,



211
212
213
214
# File 'lib/active_record_query_counter.rb', line 211

def first_transaction_start_time
  counter = current_counter
  counter.first_transaction_start_time if counter.is_a?(Counter)
end

.increment_rollbacksInteger?

Return the number of rollbacks that have been counted within the current block. Returns nil if not inside a block where queries are being counted.

Returns:

  • (Integer, nil)


145
146
147
148
149
150
# File 'lib/active_record_query_counter.rb', line 145

def increment_rollbacks
  counter = current_counter
  return unless counter.is_a?(Counter)

  counter.rollback_count += 1
end

.infoHash?

Return the query info as a hash with keys :query_count, :row_count, :query_time :transaction_count, and :transaction_type or nil if not inside a block where queries are being counted.

Returns:

  • (Hash, nil)


248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/active_record_query_counter.rb', line 248

def info
  counter = current_counter
  if counter.is_a?(Counter)
    {
      query_count: counter.query_count,
      row_count: counter.row_count,
      query_time: counter.query_time,
      cached_query_count: counter.cached_query_count,
      cache_hit_rate: counter.cache_hit_rate,
      transaction_count: counter.transaction_count,
      transaction_time: counter.transaction_time,
      rollback_count: counter.rollback_count
    }
  end
end

.last_transaction_end_timeFloat?

Return the time when the last transaction ended within the current block. Returns nil if not inside a block where queries are being counted or there are no transactions.

Returns:

  • (Float, nil)

    the monotonic time when the last transaction ended,



220
221
222
223
# File 'lib/active_record_query_counter.rb', line 220

def last_transaction_end_time
  counter = current_counter
  counter.last_transaction_end_time if counter.is_a?(Counter)
end

.query_countInteger?

Return the number of queries that have been counted within the current block. Returns nil if not inside a block where queries are being counted.

Returns:

  • (Integer, nil)


156
157
158
159
# File 'lib/active_record_query_counter.rb', line 156

def query_count
  counter = current_counter
  counter.query_count if counter.is_a?(Counter)
end

.query_timeFloat?

Return the total time spent executing queries within the current block. Returns nil if not inside a block where queries are being counted.

Returns:

  • (Float, nil)


184
185
186
187
# File 'lib/active_record_query_counter.rb', line 184

def query_time
  counter = current_counter
  counter.query_time if counter.is_a?(Counter)
end

.rollback_countInteger?

Return the number of transactions that have rolled back within the current block. Returns nil if not inside a block where queries are being counted.

Returns:

  • (Integer, nil)


238
239
240
241
# File 'lib/active_record_query_counter.rb', line 238

def rollback_count
  counter = current_counter
  counter.rollback_count if counter.is_a?(Counter)
end

.row_countInteger?

Return the number of rows that have been counted within the current block. Returns nil if not inside a block where queries are being counted.

Returns:

  • (Integer, nil)


175
176
177
178
# File 'lib/active_record_query_counter.rb', line 175

def row_count
  counter = current_counter
  counter.row_count if counter.is_a?(Counter)
end

.thresholdsObject

Get the current local notification thresholds. These thresholds are only used within the current count_queries block.



272
273
274
# File 'lib/active_record_query_counter.rb', line 272

def thresholds
  current_counter&.thresholds || default_thresholds.dup
end

.transaction_countInteger?

Return the number of transactions that have been counted within the current block. Returns nil if not inside a block where queries are being counted.

Returns:

  • (Integer, nil)


193
194
195
196
# File 'lib/active_record_query_counter.rb', line 193

def transaction_count
  counter = current_counter
  counter.transaction_count if counter.is_a?(Counter)
end

.transaction_timeFloat?

Return the total time spent in transactions that have been counted within the current block. Returns nil if not inside a block where queries are being counted.

Returns:

  • (Float, nil)


202
203
204
205
# File 'lib/active_record_query_counter.rb', line 202

def transaction_time
  counter = current_counter
  counter.transaction_time if counter.is_a?(Counter)
end

.transactionsArray<ActiveRecordQueryCounter::TransactionInfo>?

Return an array of transaction information for any transactions that have been counted within the current block. Returns nil if not inside a block where queries are being counted.



229
230
231
232
# File 'lib/active_record_query_counter.rb', line 229

def transactions
  counter = current_counter
  counter.transactions if counter.is_a?(Counter)
end