active_record_query_counter 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: daaa5b12132d82613fb803966073b0b55131f0e47b83c1d05e0e6dca63c3ba98
4
- data.tar.gz: ede14e3f86b236a2d39009dfe84d930a0541eea70c1273d7346ff4ac40218154
3
+ metadata.gz: 499ad971fe53532f23bfa674961133b6c39f850b39d89b102d959be760ba386c
4
+ data.tar.gz: 0f2eb66f8d2d274ddd2b173fce6f21ba35a7069d398f0f72e03052905f0b9221
5
5
  SHA512:
6
- metadata.gz: 03c8d4e734928d45f7a95188e4a9dc805ef45dabef52d0144d4323f5100163c2603955d1ffa3e78f22178fec458932ea8f56ee67f346bb7a51eae83264de4cee
7
- data.tar.gz: 38e3d4959598bc8a9d8cd80c06b07fa34485e9a812095be7783f7a8161e3bcf0e4f3aaa619a114fe8beac9b4251bfdc78b3e445b67e5e0fe21a505b865bb33b2
6
+ metadata.gz: 8ead36fbb297cfb37af6ccb6c5b50903c123224c5c0ff72f96cb9ccf4caeac02da8e12b34858f91d7ef4ea153383babe0c6eac37e59bace9491707542ccac48b
7
+ data.tar.gz: d3042dca0d9c1b0624338367461c0277360a676061dd62dd4f2ba101cfdf7831c67a1382637703e4d91d7da51bfc90e0829b2792ff2b20fecfbcee892b48fff4
data/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 3.3.0
8
+
9
+ ### Added
10
+
11
+ - `TransactionInfo#query_count` reports the number of queries executed within the transaction.
12
+ - `TransactionInfo#gc_time` and `TransactionInfo#cpu_time` report the GC time and thread CPU time in seconds that elapsed while the transaction was open.
13
+ - `TransactionInfo#idle_time` estimates the time the transaction was open but not spent executing queries, running Ruby code, or paused for garbage collection. A large value indicates the process was waiting on something other than the database (an HTTP request, a sleep, a lock, etc.) while it held the transaction open.
14
+ - The `transaction_time` notification payload now includes `:queries` along with `:gc_time`, `:cpu_time`, and `:idle_time` in milliseconds. The queries are an array of new `ActiveRecordQueryCounter::QueryInfo` objects; each includes the SQL statement, query name, row count, start and end times, and the GC, CPU, and connection setup times measured for the query. Bind parameter values are not included.
15
+ - The full query details for a transaction only live in a transient `ActiveRecordQueryCounter::TransactionDetails` object that populates the `transaction_time` notification payload and is then released. The `TransactionInfo` objects retained for the duration of a `count_queries` block hold only counts and timings, so memory stays flat even when a process creates many implicit transactions (for example, many saves without an explicit transaction).
16
+
7
17
  ## 3.2.0
8
18
 
9
19
  ### Added
data/README.md CHANGED
@@ -14,6 +14,9 @@ It measures database usage within a block of code, including:
14
14
  - The number of transactions used
15
15
  - The total time spent inside transactions
16
16
  - The number of transactions that were rolled back
17
+ - The queries executed inside each transaction, with the time each one took
18
+ - The GC time and CPU time spent inside each transaction
19
+ - The idle time inside each transaction spent waiting on things other than the database
17
20
 
18
21
  This gem is designed to help you:
19
22
 
@@ -142,15 +145,22 @@ Triggered when a query exceeds the row_count threshold with the payload:
142
145
  Triggered when a transaction exceeds the transaction_time threshold with the payload:
143
146
 
144
147
  - `:trace` - The stack trace of where the transaction was completed.
148
+ - `:queries` - An array of `ActiveRecordQueryCounter::QueryInfo` objects for the queries executed within the transaction. Each object includes the SQL statement, query name, row count, and timing details. Bind parameter values are not included.
149
+ - `:gc_time` - The GC time that elapsed while the transaction was open (in milliseconds).
150
+ - `:cpu_time` - The thread CPU time spent while the transaction was open (in milliseconds).
151
+ - `:idle_time` - The estimated time the transaction was open but not spent executing queries, running Ruby code, or paused for garbage collection (in milliseconds). A large value indicates the process was waiting on something other than the database (an HTTP request, a sleep, a lock, etc.) while it held the transaction open.
145
152
 
146
153
  ##### 4. active_record_query_counter.transaction_count notification
147
154
 
148
155
  Triggered when transactions exceed the transaction_count threshold with the payload:
149
156
 
150
- - `:transactions` - An array of `ActiveRecordQueryCounter::TransactionInfo` objects.
157
+ - `:transactions` - An array of `ActiveRecordQueryCounter::TransactionInfo` objects. Each object includes the number of queries executed within the transaction (`query_count`), the GC time (`gc_time`) and thread CPU time (`cpu_time`) in seconds spent while the transaction was open, and the estimated idle time (`idle_time`) in seconds spent waiting on things other than the database.
151
158
 
152
159
  The duration of the notification event is the time between when the first transaction was started and the last transaction was completed.
153
160
 
161
+ > [!NOTE]
162
+ > The full query details for a transaction are only available in the `transaction_time` notification payload. They are released after the notification fires; the `TransactionInfo` objects retained for the duration of a `count_queries` block hold only counts and timings so that memory stays flat even when many transactions are created. Query details are also only collected while queries are being counted inside a `count_queries` block (or the bundled middleware).
163
+
154
164
  #### Setting Thresholds
155
165
 
156
166
  Thresholds can be configured **globally** in an initializer:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.2.0
1
+ 3.3.0
@@ -3,11 +3,6 @@
3
3
  module ActiveRecordQueryCounter
4
4
  # Module to prepend to the connection adapter to inject the counting behavior.
5
5
  module ConnectionAdapterExtension
6
- # Clock used to measure the CPU time consumed by the current thread while a query runs.
7
- # It is not available on every platform (e.g. Windows), in which case CPU time is not
8
- # measured and is treated as zero.
9
- CPU_CLOCK_ID = (Process::CLOCK_THREAD_CPUTIME_ID if defined?(Process::CLOCK_THREAD_CPUTIME_ID))
10
-
11
6
  # Connection adapter methods that establish, verify, or reconnect the underlying database
12
7
  # connection. When these run inside a query (for example when a stale connection is
13
8
  # re-established after an idle period or a database failover), the wall clock time they
@@ -34,11 +29,12 @@ module ActiveRecordQueryCounter
34
29
  # @param sql [String] the SQL statement being executed
35
30
  # @param name [String, nil] the name of the query
36
31
  # @param binds [Array] the bind parameters
32
+ # @param connection [Object, nil] the connection adapter the query is being executed on
37
33
  # @yield executes the query and returns its result
38
34
  # @return [Object] the result of the query
39
- def measure_query(sql, name, binds)
35
+ def measure_query(sql, name, binds, connection = nil)
40
36
  gc_start = GC.total_time
41
- cpu_start = current_cpu_time
37
+ cpu_start = ActiveRecordQueryCounter.current_cpu_time
42
38
  previous_timer = ActiveRecordQueryCounter.start_connection_timer
43
39
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
44
40
  begin
@@ -48,26 +44,17 @@ module ActiveRecordQueryCounter
48
44
  end
49
45
  if result.is_a?(ActiveRecord::Result)
50
46
  end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
51
- cpu_time = current_cpu_time - cpu_start
47
+ cpu_time = ActiveRecordQueryCounter.current_cpu_time - cpu_start
52
48
  gc_time = (GC.total_time - gc_start) / 1_000_000_000.0
53
- ActiveRecordQueryCounter.add_query(sql, name, binds, result.length, start_time, end_time, gc_time, cpu_time, connection_time)
49
+ ActiveRecordQueryCounter.add_query(sql, name, binds, result.length, start_time, end_time, gc_time, cpu_time, connection_time, connection: connection)
54
50
  end
55
51
  result
56
52
  end
57
-
58
- private
59
-
60
- # The current thread CPU time in seconds, or 0.0 when the platform does not support it.
61
- #
62
- # @return [Float]
63
- def current_cpu_time
64
- CPU_CLOCK_ID ? Process.clock_gettime(CPU_CLOCK_ID) : 0.0
65
- end
66
53
  end
67
54
 
68
55
  module InternalExecQuery
69
56
  def internal_exec_query(sql, name = nil, binds = [], **kwargs)
70
- ConnectionAdapterExtension.measure_query(sql, name, binds) { super }
57
+ ConnectionAdapterExtension.measure_query(sql, name, binds, self) { super }
71
58
  end
72
59
  end
73
60
 
@@ -41,9 +41,14 @@ module ActiveRecordQueryCounter
41
41
  # @param trace [Array<String>] the trace of the transaction
42
42
  # @param start_time [Float] the monotonic time when the transaction began
43
43
  # @param end_time [Float] the monotonic time when the transaction ended
44
+ # @param query_count [Integer] the number of queries executed within the transaction
45
+ # @param gc_time [Float] the GC time in seconds that elapsed while the transaction was open
46
+ # @param cpu_time [Float] the thread CPU time in seconds spent while the transaction was open
47
+ # @param idle_time [Float] the time in seconds the transaction was open but not spent on
48
+ # queries, Ruby code, or garbage collection
44
49
  # @return [void]
45
50
  # @api private
46
- def add_transaction(trace:, start_time:, end_time:)
51
+ def add_transaction(trace:, start_time:, end_time:, query_count: 0, gc_time: 0.0, cpu_time: 0.0, idle_time: 0.0)
47
52
  trace_transactions = @transactions_hash[trace]
48
53
  if trace_transactions
49
54
  # Memory optimization so that we don't store duplicate traces for every transaction in a loop.
@@ -53,7 +58,7 @@ module ActiveRecordQueryCounter
53
58
  @transactions_hash[trace] = trace_transactions
54
59
  end
55
60
 
56
- trace_transactions << TransactionInfo.new(start_time: start_time, end_time: end_time, trace: trace)
61
+ trace_transactions << TransactionInfo.new(start_time: start_time, end_time: end_time, trace: trace, query_count: query_count, gc_time: gc_time, cpu_time: cpu_time, idle_time: idle_time)
57
62
  end
58
63
 
59
64
  # Return the number of transactions that have been tracked by the counter.
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordQueryCounter
4
+ # Data structure for storing information about a query executed within a transaction.
5
+ # Note that the start and end times are monotonic time and not wall clock time. Bind
6
+ # parameter values are deliberately not stored since they can contain sensitive data.
7
+ class QueryInfo
8
+ attr_reader :sql, :name, :row_count, :start_time, :end_time, :gc_time, :cpu_time, :connection_time
9
+
10
+ def initialize(sql:, name:, row_count:, start_time:, end_time:, gc_time: 0.0, cpu_time: 0.0, connection_time: 0.0)
11
+ @sql = sql
12
+ @name = name
13
+ @row_count = row_count
14
+ @start_time = start_time
15
+ @end_time = end_time
16
+ @gc_time = gc_time
17
+ @cpu_time = cpu_time
18
+ @connection_time = connection_time
19
+ end
20
+
21
+ # Return the wall clock time spent executing the query.
22
+ #
23
+ # @return [Float]
24
+ def elapsed_time
25
+ end_time - start_time
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordQueryCounter
4
+ # Data structure with the full details about a transaction, including the queries executed
5
+ # within it. Note that the start and end times are monotonic time and not wall clock time.
6
+ #
7
+ # This object is transient. It is created when a transaction is recorded to build the
8
+ # `transaction_time` notification payload and to derive the retained
9
+ # {ActiveRecordQueryCounter::TransactionInfo}, and is then released so the query details
10
+ # do not accumulate in memory.
11
+ class TransactionDetails
12
+ attr_reader :start_time, :end_time, :trace, :queries, :gc_time, :cpu_time
13
+
14
+ def initialize(start_time:, end_time:, trace:, queries: [], gc_time: 0.0, cpu_time: 0.0)
15
+ @start_time = start_time
16
+ @end_time = end_time
17
+ @trace = trace
18
+ @queries = queries
19
+ @gc_time = gc_time
20
+ @cpu_time = cpu_time
21
+ end
22
+
23
+ # Return the time spent in the transaction.
24
+ #
25
+ # @return [Float]
26
+ def elapsed_time
27
+ end_time - start_time
28
+ end
29
+
30
+ # Return the number of queries executed within the transaction.
31
+ #
32
+ # @return [Integer]
33
+ def query_count
34
+ queries.size
35
+ end
36
+
37
+ # Estimate the time the transaction was open but not spent executing queries, running Ruby
38
+ # code, or paused for garbage collection. A large value indicates the process was waiting
39
+ # on something other than the database (an HTTP request, a sleep, a lock, etc.) while it
40
+ # held the transaction open.
41
+ #
42
+ # The wall clock time of each query is subtracted first since it already includes any GC
43
+ # and CPU time that occurred while the query ran. Only the GC and CPU time that occurred
44
+ # outside of queries is then subtracted from the remainder. GC and CPU time normally cover
45
+ # distinct intervals, but they overlap when this thread triggers a GC; when subtracting
46
+ # both would drive the result negative, only the larger of the two is subtracted so the
47
+ # shared interval is removed once (the same heuristic used for the query time). The result
48
+ # is clamped so it is never negative and never exceeds the elapsed time.
49
+ #
50
+ # @return [Float]
51
+ def idle_time
52
+ return 0.0 if elapsed_time <= 0.0
53
+
54
+ queries_elapsed_time = 0.0
55
+ queries_gc_time = 0.0
56
+ queries_cpu_time = 0.0
57
+ queries.each do |query|
58
+ queries_elapsed_time += query.elapsed_time
59
+ queries_gc_time += query.gc_time
60
+ queries_cpu_time += query.cpu_time
61
+ end
62
+
63
+ remaining_time = elapsed_time - queries_elapsed_time
64
+ return 0.0 if remaining_time <= 0.0
65
+
66
+ other_gc_time = (gc_time - queries_gc_time).clamp(0.0, remaining_time)
67
+ other_cpu_time = (cpu_time - queries_cpu_time).clamp(0.0, remaining_time)
68
+
69
+ idle_time = remaining_time - (other_gc_time + other_cpu_time)
70
+ idle_time = remaining_time - [other_gc_time, other_cpu_time].max if idle_time.negative?
71
+ idle_time.clamp(0.0, remaining_time)
72
+ end
73
+ end
74
+ end
@@ -16,6 +16,9 @@ module ActiveRecordQueryCounter
16
16
  def initialize(...)
17
17
  super
18
18
  @active_record_query_counter_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
19
+ @active_record_query_counter_gc_start = GC.total_time
20
+ @active_record_query_counter_cpu_start = ActiveRecordQueryCounter.current_cpu_time
21
+ @active_record_query_counter_queries = ActiveRecordQueryCounter.register_transaction_queries(connection)
19
22
  end
20
23
 
21
24
  def commit(...)
@@ -44,8 +47,17 @@ module ActiveRecordQueryCounter
44
47
  return unless start_time
45
48
 
46
49
  @active_record_query_counter_start_time = nil
50
+ ActiveRecordQueryCounter.unregister_transaction_queries(connection)
47
51
  end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
48
- ActiveRecordQueryCounter.add_transaction(start_time, end_time)
52
+ gc_time = (GC.total_time - @active_record_query_counter_gc_start) / 1_000_000_000.0
53
+ cpu_time = ActiveRecordQueryCounter.current_cpu_time - @active_record_query_counter_cpu_start
54
+ ActiveRecordQueryCounter.add_transaction(
55
+ start_time,
56
+ end_time,
57
+ queries: @active_record_query_counter_queries,
58
+ gc_time: gc_time,
59
+ cpu_time: cpu_time
60
+ )
49
61
  ActiveRecordQueryCounter.increment_rollbacks if rollback
50
62
  end
51
63
  end
@@ -1,15 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordQueryCounter
4
- # Data structure for storing information about a transaction. Note that the start and end
5
- # times are monotonic time and not wall clock time.
4
+ # Data structure for storing the counts and timings of a transaction. Note that the start
5
+ # and end times are monotonic time and not wall clock time.
6
+ #
7
+ # This structure deliberately does not hold the queries executed within the transaction so
8
+ # that retaining it for the duration of a `count_queries` block stays cheap. The full query
9
+ # details are only available transiently in the `transaction_time` notification payload
10
+ # (see {ActiveRecordQueryCounter::TransactionDetails}).
6
11
  class TransactionInfo
7
- attr_reader :start_time, :end_time, :trace
12
+ attr_reader :start_time, :end_time, :trace, :query_count, :gc_time, :cpu_time, :idle_time
8
13
 
9
- def initialize(start_time:, end_time:, trace:)
14
+ def initialize(start_time:, end_time:, trace:, query_count: 0, gc_time: 0.0, cpu_time: 0.0, idle_time: 0.0)
10
15
  @start_time = start_time
11
16
  @end_time = end_time
12
17
  @trace = trace
18
+ @query_count = query_count
19
+ @gc_time = gc_time
20
+ @cpu_time = cpu_time
21
+ @idle_time = idle_time
13
22
  end
14
23
 
15
24
  # Return the time spent in the transaction.
@@ -4,7 +4,9 @@ require "securerandom"
4
4
 
5
5
  require_relative "active_record_query_counter/connection_adapter_extension"
6
6
  require_relative "active_record_query_counter/counter"
7
+ require_relative "active_record_query_counter/query_info"
7
8
  require_relative "active_record_query_counter/thresholds"
9
+ require_relative "active_record_query_counter/transaction_details"
8
10
  require_relative "active_record_query_counter/transaction_info"
9
11
  require_relative "active_record_query_counter/transaction_extension"
10
12
 
@@ -26,6 +28,10 @@ module ActiveRecordQueryCounter
26
28
  IGNORED_STATEMENTS = %w[SCHEMA EXPLAIN].freeze
27
29
  private_constant :IGNORED_STATEMENTS
28
30
 
31
+ # Clock used to measure the CPU time consumed by the current thread. It is not available on
32
+ # every platform (e.g. Windows), in which case CPU time is not measured and is treated as zero.
33
+ CPU_CLOCK_ID = (Process::CLOCK_THREAD_CPUTIME_ID if defined?(Process::CLOCK_THREAD_CPUTIME_ID))
34
+
29
35
  @lock = Mutex.new
30
36
  @default_thresholds = Thresholds.new
31
37
 
@@ -88,9 +94,11 @@ module ActiveRecordQueryCounter
88
94
  # @param cpu_time [Float] the thread CPU time in seconds spent while the query ran
89
95
  # @param connection_time [Float] the time in seconds spent establishing, verifying, or
90
96
  # reconnecting the database connection while the query ran
97
+ # @param connection [Object, nil] the connection adapter the query was executed on; used to
98
+ # attach the query to the transaction currently open on that connection, if any
91
99
  # @return [void]
92
100
  # @api private
93
- def add_query(sql, name, binds, row_count, start_time, end_time, gc_time, cpu_time, connection_time = 0.0)
101
+ def add_query(sql, name, binds, row_count, start_time, end_time, gc_time, cpu_time, connection_time = 0.0, connection: nil)
94
102
  return if IGNORED_STATEMENTS.include?(name)
95
103
 
96
104
  counter = current_counter
@@ -102,6 +110,20 @@ module ActiveRecordQueryCounter
102
110
  counter.row_count += row_count
103
111
  counter.query_time += query_time
104
112
 
113
+ queries = transaction_queries(connection)
114
+ if queries
115
+ queries << QueryInfo.new(
116
+ sql: sql,
117
+ name: name,
118
+ row_count: row_count,
119
+ start_time: start_time,
120
+ end_time: end_time,
121
+ gc_time: gc_time,
122
+ cpu_time: cpu_time,
123
+ connection_time: connection_time
124
+ )
125
+ end
126
+
105
127
  # The notification duration is the database query time, so the event ends that long after
106
128
  # it started rather than at the raw wall clock end time.
107
129
  notification_end_time = start_time + query_time
@@ -126,18 +148,50 @@ module ActiveRecordQueryCounter
126
148
  #
127
149
  # @param start_time [Float] the time the transaction started
128
150
  # @param end_time [Float] the time the transaction ended
151
+ # @param queries [Array<ActiveRecordQueryCounter::QueryInfo>] the queries executed within
152
+ # the transaction
153
+ # @param gc_time [Float] the GC time in seconds that elapsed while the transaction was open
154
+ # @param cpu_time [Float] the thread CPU time in seconds spent while the transaction was open
129
155
  # @return [void]
130
156
  # @api private
131
- def add_transaction(start_time, end_time)
157
+ def add_transaction(start_time, end_time, queries: [], gc_time: 0.0, cpu_time: 0.0)
132
158
  counter = current_counter
133
159
  return unless counter.is_a?(Counter)
134
160
 
135
- trace = backtrace
136
- counter.add_transaction(trace: trace, start_time: start_time, end_time: end_time)
161
+ # The details object holds the full query information. Only the counts and timings
162
+ # derived from it are retained on the counter; the details are released when this
163
+ # method returns so the query data does not accumulate in memory.
164
+ details = TransactionDetails.new(
165
+ start_time: start_time,
166
+ end_time: end_time,
167
+ trace: backtrace,
168
+ queries: queries,
169
+ gc_time: gc_time,
170
+ cpu_time: cpu_time
171
+ )
172
+
173
+ counter.add_transaction(
174
+ trace: details.trace,
175
+ start_time: start_time,
176
+ end_time: end_time,
177
+ query_count: details.query_count,
178
+ gc_time: gc_time,
179
+ cpu_time: cpu_time,
180
+ idle_time: details.idle_time
181
+ )
137
182
 
138
183
  transaction_time_threshold = counter.thresholds.transaction_time || -1
139
184
  if transaction_time_threshold.between?(0, end_time - start_time)
140
- send_notification("transaction_time", start_time, end_time, trace: backtrace)
185
+ send_notification(
186
+ "transaction_time",
187
+ start_time,
188
+ end_time,
189
+ trace: details.trace,
190
+ queries: details.queries,
191
+ gc_time: (gc_time * 1000.0).round(6),
192
+ cpu_time: (cpu_time * 1000.0).round(6),
193
+ idle_time: (details.idle_time * 1000.0).round(6)
194
+ )
141
195
  end
142
196
  end
143
197
 
@@ -152,6 +206,43 @@ module ActiveRecordQueryCounter
152
206
  counter.rollback_count += 1
153
207
  end
154
208
 
209
+ # The current thread CPU time in seconds, or 0.0 when the platform does not support it.
210
+ #
211
+ # @return [Float]
212
+ # @api private
213
+ def current_cpu_time
214
+ CPU_CLOCK_ID ? Process.clock_gettime(CPU_CLOCK_ID) : 0.0
215
+ end
216
+
217
+ # Register a new query list for a transaction that was opened on a connection. Queries
218
+ # executed on that connection will be appended to the list until it is unregistered.
219
+ # Registering a connection always replaces any list already registered for it.
220
+ #
221
+ # The list is bound to the counter that is active when the transaction is opened. Queries
222
+ # counted by a different counter (e.g. a nested `count_queries` block) are not appended,
223
+ # so the recorded query count for the transaction stays consistent with the counter that
224
+ # recorded the transaction.
225
+ #
226
+ # @param connection [Object] the connection adapter the transaction was opened on
227
+ # @return [Array<ActiveRecordQueryCounter::QueryInfo>] the registered query list
228
+ # @api private
229
+ def register_transaction_queries(connection)
230
+ registry = ActiveSupport::IsolatedExecutionState[:active_record_query_counter_transaction_queries] ||= {}
231
+ queries = []
232
+ registry[connection] = {counter: current_counter, queries: queries}
233
+ queries
234
+ end
235
+
236
+ # Remove the registered query list for a connection when its transaction has ended.
237
+ #
238
+ # @param connection [Object] the connection adapter the transaction was opened on
239
+ # @return [void]
240
+ # @api private
241
+ def unregister_transaction_queries(connection)
242
+ ActiveSupport::IsolatedExecutionState[:active_record_query_counter_transaction_queries]&.delete(connection)
243
+ nil
244
+ end
245
+
155
246
  # Begin measuring the time spent establishing, verifying, or reconnecting the database
156
247
  # connection for a single query. Returns the timer that was previously in effect so it can
157
248
  # be restored by {.stop_connection_timer}; this keeps nested queries (should they ever
@@ -366,6 +457,16 @@ module ActiveRecordQueryCounter
366
457
  ActiveSupport::IsolatedExecutionState[:active_record_query_counter_connection_timer] = timer
367
458
  end
368
459
 
460
+ # The query list registered for the transaction currently open on a connection, or nil
461
+ # when the connection is unknown, has no open transaction, or the transaction was opened
462
+ # under a different counter than the current one.
463
+ def transaction_queries(connection)
464
+ return nil if connection.nil?
465
+
466
+ entry = ActiveSupport::IsolatedExecutionState[:active_record_query_counter_transaction_queries]&.[](connection)
467
+ entry[:queries] if entry && entry[:counter].equal?(current_counter)
468
+ end
469
+
369
470
  def send_notification(name, start_time, end_time, payload = {})
370
471
  id = "#{name}-#{SecureRandom.hex}"
371
472
  ActiveSupport::Notifications.publish("active_record_query_counter.#{name}", start_time, end_time, id, payload)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_query_counter
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
@@ -37,9 +37,11 @@ files:
37
37
  - lib/active_record_query_counter.rb
38
38
  - lib/active_record_query_counter/connection_adapter_extension.rb
39
39
  - lib/active_record_query_counter/counter.rb
40
+ - lib/active_record_query_counter/query_info.rb
40
41
  - lib/active_record_query_counter/rack_middleware.rb
41
42
  - lib/active_record_query_counter/sidekiq_middleware.rb
42
43
  - lib/active_record_query_counter/thresholds.rb
44
+ - lib/active_record_query_counter/transaction_details.rb
43
45
  - lib/active_record_query_counter/transaction_extension.rb
44
46
  - lib/active_record_query_counter/transaction_info.rb
45
47
  homepage: https://github.com/bdurand/active_record_query_counter