Module: ActiveRecordQueryCounter

Defined in:
lib/active_record_query_counter.rb,
lib/active_record_query_counter/counter.rb,
lib/active_record_query_counter/version.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/connection_adapter_extension.rb,
lib/active_record_query_counter/transaction_manager_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, TransactionManagerExtension Classes: Counter, RackMiddleware, SidekiqMiddleware, Thresholds, TransactionInfo

Constant Summary collapse

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

Class Method Summary collapse

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



84
85
86
87
88
89
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
# File 'lib/active_record_query_counter.rb', line 84

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 >= 0 && query_time >= query_time_threshold
    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 >= 0 && row_count >= row_count_threshold
    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



122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/active_record_query_counter.rb', line 122

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 >= 0 && end_time - start_time >= transaction_time_threshold
    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)


160
161
162
163
# File 'lib/active_record_query_counter.rb', line 160

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



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/active_record_query_counter.rb', line 30

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 >= 0 && transaction_count >= transaction_threshold
        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

.default_thresholdsActiveRecordQueryCounter::Thresholds

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



262
263
264
# File 'lib/active_record_query_counter.rb', line 262

def default_thresholds
  @default_thresholds ||= Thresholds.new
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



56
57
58
59
60
61
62
63
64
# File 'lib/active_record_query_counter.rb', line 56

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



276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/active_record_query_counter.rb', line 276

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

  @cache_subscription ||= ActiveSupport::Notifications.subscribe("sql.active_record") do |_name, _start_time, _end_time, _id, payload|
    if payload[:cached]
      counter = current_counter
      counter.cached_query_count += 1 if counter
    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,



205
206
207
208
# File 'lib/active_record_query_counter.rb', line 205

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)


139
140
141
142
143
144
# File 'lib/active_record_query_counter.rb', line 139

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)


242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/active_record_query_counter.rb', line 242

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,



214
215
216
217
# File 'lib/active_record_query_counter.rb', line 214

def last_transaction_end_time
  counter = current_counter
  counter.transactions.last&.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)


150
151
152
153
# File 'lib/active_record_query_counter.rb', line 150

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)


178
179
180
181
# File 'lib/active_record_query_counter.rb', line 178

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)


232
233
234
235
# File 'lib/active_record_query_counter.rb', line 232

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)


169
170
171
172
# File 'lib/active_record_query_counter.rb', line 169

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.



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

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)


187
188
189
190
# File 'lib/active_record_query_counter.rb', line 187

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)


196
197
198
199
# File 'lib/active_record_query_counter.rb', line 196

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.



223
224
225
226
# File 'lib/active_record_query_counter.rb', line 223

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