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) ⇒ 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.

Parameters:

  • row_count (Integer)

    the number of rows returned by the query

  • elapsed_time (Float)

    the time spent executing the query



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/active_record_query_counter.rb', line 57

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

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

  elapsed_time = end_time - start_time
  counter.query_count += 1
  counter.row_count += row_count
  counter.query_time += elapsed_time

  trace = nil
  query_time_threshold = (counter.thresholds.query_time || -1)
  if query_time_threshold >= 0 && elapsed_time >= query_time_threshold
    trace = backtrace
    send_notification("query_time", start_time, end_time, sql: sql, binds: binds, row_count: row_count, trace: trace)
  end

  row_count_threshold = (counter.thresholds.row_count || -1)
  if row_count_threshold >= 0 && row_count >= row_count_threshold
    trace ||= backtrace
    send_notification("row_count", start_time, end_time, sql: sql, binds: binds, row_count: row_count, trace: trace)
  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



88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/active_record_query_counter.rb', line 88

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)


115
116
117
118
# File 'lib/active_record_query_counter.rb', line 115

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



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

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.



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

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



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/active_record_query_counter.rb', line 221

def enable!(connection_class)
  unless connection_class.include?(ConnectionAdapterExtension)
    connection_class.prepend(ConnectionAdapterExtension)
  end
  unless ActiveRecord::ConnectionAdapters::TransactionManager.include?(TransactionManagerExtension)
    ActiveRecord::ConnectionAdapters::TransactionManager.prepend(TransactionManagerExtension)
  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,



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

def first_transaction_start_time
  counter = current_counter
  counter.first_transaction_start_time if counter.is_a?(Counter)
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)


188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/active_record_query_counter.rb', line 188

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
    }
  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,



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

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)


105
106
107
108
# File 'lib/active_record_query_counter.rb', line 105

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)


133
134
135
136
# File 'lib/active_record_query_counter.rb', line 133

def query_time
  counter = current_counter
  counter.query_time 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)


124
125
126
127
# File 'lib/active_record_query_counter.rb', line 124

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.



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

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)


142
143
144
145
# File 'lib/active_record_query_counter.rb', line 142

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)


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

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.



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

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