active_record_query_counter 2.3.0 → 3.1.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: 9b460515835154824cb80fec7db1e62dc4f4f983f8779fc1b80bec3c728646a4
4
- data.tar.gz: 3d26302dd73d2ae010f4732ca4711a7dd1daa4ca0cca55e5ccd50349e89b282c
3
+ metadata.gz: 1966d4714e8fc7a3d77b86be07c8c930470d2af298dbd8d88d4bee4043a4da84
4
+ data.tar.gz: 8ad2b534042042259038f72daff078b6302b552f6a742b4f38f0504afe9a8919
5
5
  SHA512:
6
- metadata.gz: d1af45ed8c0659df4fcbf665621576b07380565a1289458e868b7cb33825c2d055e7b489eb1e490c31a566157a01f328eedd2771f9f3f5bffdece2b357445a01
7
- data.tar.gz: ba21e07f668e1c3f97c61ce6f48fd10ef2d019afdd7d9d579e3487c710d58e8b44a9b1c5042572bc74be55a907c506d6385947dbe85a10b14b57e2b7b94037b7
6
+ metadata.gz: 13a2c8c037ea0b47ea11384e133c294d047a74b34f89564a49adb0c339e2e489a1d61ef8a48c76d31b7a7abbd9afca6caf9ebfea011dcd703dbfec19fa65e46f
7
+ data.tar.gz: 6ba58ac495883fa98d20e7cda7ae363444f279db7a543016a6ffcdacf1f4f2d7404c8f7f7d06425c9f159d004788c73bde3fb857ce3e63a218380d6d00e359fd
data/CHANGELOG.md CHANGED
@@ -4,6 +4,34 @@ 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.1.0
8
+
9
+ ### Changed
10
+
11
+ - Transaction time now ends when the COMMIT or ROLLBACK statement completes rather than before it is sent, so it includes the commit itself. Time spent in after commit and after rollback callbacks is not included since those run after the database transaction is over.
12
+ - The counter is now stored in `ActiveSupport::IsolatedExecutionState` when available (Rails 7+) so that query counting follows the application's configured thread or fiber isolation level. On Rails 6.x the counter remains fiber-local.
13
+
14
+ ### Fixed
15
+
16
+ - A transaction whose COMMIT statement fails (e.g. a deadlock or serialization failure detected at commit time) is now counted as a rollback. Previously it was recorded as a successful commit and the rollback count was not incremented.
17
+ - `ActiveRecordQueryCounter.last_transaction_end_time` now returns the latest transaction end time when transactions on multiple connections overlap. Previously it returned the end time of the transaction that started last.
18
+ - Cached queries for ignored statements (`SCHEMA`, `EXPLAIN`) are no longer counted in the cached query count.
19
+ - Setting up the query cache subscription in `ActiveRecordQueryCounter.enable!` is now thread safe, so concurrent calls can no longer create duplicate subscriptions that would double count cached queries.
20
+
21
+ ## 3.0.0
22
+
23
+ ### Changed
24
+
25
+ - Query time now excludes GC pause time and Ruby thread CPU time so that it more closely reflects the time actually spent waiting on the database. This is what is now reported as the event duration in the `query_time` and `row_count` notifications.
26
+
27
+ ### Added
28
+
29
+ - Added `:elapsed_time` (the raw wall clock time), `:gc_time`, and `:cpu_time` (all in milliseconds) to the `query_time` and `row_count` notification payloads.
30
+
31
+ ### Removed
32
+
33
+ - Dropped support for Ruby versions older than 3.1 (required for `GC.total_time`).
34
+
7
35
  ## 2.3.0
8
36
 
9
37
  ### Added
data/README.md CHANGED
@@ -55,6 +55,15 @@ ActiveRecordQueryCounter.count_queries do
55
55
  end
56
56
  ```
57
57
 
58
+ ### Query Time
59
+
60
+ The query time (`ActiveRecordQueryCounter.query_time` and the duration reported by the notifications) is **not** the raw wall clock time a query took. The wall clock time includes time the thread was not actually waiting on the database, such as GC pauses (which can be triggered by other threads and stop the world) and the Ruby CPU work of building the result objects. On a busy, multi-threaded server these can add up to seconds, making a trivial query look pathologically slow.
61
+
62
+ To report the time actually spent waiting on the database as closely as possible, the GC time and thread CPU time that elapsed while the query ran are subtracted from the wall clock time. The raw wall clock time is still available as `:elapsed_time` in the notification payloads.
63
+
64
+ > [!NOTE]
65
+ > Measuring GC time requires Ruby's GC total time measurement, which is enabled by default (`GC.measure_total_time`). Thread CPU time is measured via `Process::CLOCK_THREAD_CPUTIME_ID`; on platforms that do not provide it, CPU time is treated as zero.
66
+
58
67
  ### Middleware Integration
59
68
 
60
69
  For **Rails** and **Sidekiq**, middleware is included to enable query counting in web requests and workers.
@@ -106,6 +115,11 @@ Triggered when a query exceeds the query_time threshold with the payload:
106
115
  - `:binds` - The bind parameters that were used.
107
116
  - `:row_count` - The number of rows returned.
108
117
  - `:trace` - The stack trace of where the query was executed.
118
+ - `:elapsed_time` - The raw wall clock time the query took (in milliseconds).
119
+ - `:gc_time` - The GC time that elapsed while the query ran (in milliseconds).
120
+ - `:cpu_time` - The thread CPU time spent while the query ran (in milliseconds).
121
+
122
+ The duration of the notification event is the query time: the wall clock time with the GC time and CPU time subtracted out (see [Query Time](#query-time)). The raw wall clock time is still available as `:elapsed_time`.
109
123
 
110
124
  ##### 2. active_record_query_counter.row_count notification
111
125
 
@@ -115,6 +129,9 @@ Triggered when a query exceeds the row_count threshold with the payload:
115
129
  - `:binds` - The bind parameters that were used.
116
130
  - `:row_count` - The number of rows returned.
117
131
  - `:trace` - The stack trace of where the query was executed.
132
+ - `:elapsed_time` - The raw wall clock time the query took (in milliseconds).
133
+ - `:gc_time` - The GC time that elapsed while the query ran (in milliseconds).
134
+ - `:cpu_time` - The thread CPU time spent while the query ran (in milliseconds).
118
135
 
119
136
  ##### 3. active_record_query_counter.transaction_time notification
120
137
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.3.0
1
+ 3.1.0
@@ -34,9 +34,9 @@ Gem::Specification.new do |spec|
34
34
 
35
35
  spec.require_paths = ["lib"]
36
36
 
37
- spec.add_dependency "activerecord", ">= 5.1"
37
+ spec.add_dependency "activerecord", ">= 6.0"
38
38
 
39
39
  spec.add_development_dependency "bundler"
40
40
 
41
- spec.required_ruby_version = ">= 2.5"
41
+ spec.required_ruby_version = ">= 3.1"
42
42
  end
@@ -3,37 +3,63 @@
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
+
6
11
  class << self
7
12
  def inject(connection_class)
8
13
  # Rails 7.1+ uses internal_exec_query instead of exec_query.
9
- mod = (connection_class.instance_methods.include?(:internal_exec_query) ? InternalExecQuery : ExecQuery)
14
+ mod = (connection_class.method_defined?(:internal_exec_query) ? InternalExecQuery : ExecQuery)
10
15
  unless connection_class.include?(mod)
11
16
  connection_class.prepend(mod)
12
17
  end
13
18
  end
14
- end
15
19
 
16
- module ExecQuery
17
- def exec_query(sql, name = nil, binds = [], *args, **kwargs)
20
+ # Measure a query by wrapping its execution. In addition to the wall clock time, the GC
21
+ # time and thread CPU time spent while the query runs are captured so that the time
22
+ # actually spent waiting on the database can be isolated from time lost to garbage
23
+ # collection and Ruby VM work.
24
+ #
25
+ # @param sql [String] the SQL statement being executed
26
+ # @param name [String, nil] the name of the query
27
+ # @param binds [Array] the bind parameters
28
+ # @yield executes the query and returns its result
29
+ # @return [Object] the result of the query
30
+ def measure_query(sql, name, binds)
31
+ gc_start = GC.total_time
32
+ cpu_start = current_cpu_time
18
33
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
19
- result = super
34
+ result = yield
20
35
  if result.is_a?(ActiveRecord::Result)
21
36
  end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
22
- ActiveRecordQueryCounter.add_query(sql, name, binds, result.length, start_time, end_time)
37
+ cpu_time = current_cpu_time - cpu_start
38
+ gc_time = (GC.total_time - gc_start) / 1_000_000_000.0
39
+ ActiveRecordQueryCounter.add_query(sql, name, binds, result.length, start_time, end_time, gc_time, cpu_time)
23
40
  end
24
41
  result
25
42
  end
43
+
44
+ private
45
+
46
+ # The current thread CPU time in seconds, or 0.0 when the platform does not support it.
47
+ #
48
+ # @return [Float]
49
+ def current_cpu_time
50
+ CPU_CLOCK_ID ? Process.clock_gettime(CPU_CLOCK_ID) : 0.0
51
+ end
52
+ end
53
+
54
+ module ExecQuery
55
+ def exec_query(sql, name = nil, binds = [], ...)
56
+ ConnectionAdapterExtension.measure_query(sql, name, binds) { super }
57
+ end
26
58
  end
27
59
 
28
60
  module InternalExecQuery
29
- def internal_exec_query(sql, name = nil, binds = [], *args, **kwargs)
30
- start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
31
- result = super
32
- if result.is_a?(ActiveRecord::Result)
33
- end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
34
- ActiveRecordQueryCounter.add_query(sql, name, binds, result.length, start_time, end_time)
35
- end
36
- result
61
+ def internal_exec_query(sql, name = nil, binds = [], ...)
62
+ ConnectionAdapterExtension.measure_query(sql, name, binds) { super }
37
63
  end
38
64
  end
39
65
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordQueryCounter
4
+ # Extension to ActiveRecord::ConnectionAdapters::RealTransaction to count transactions.
5
+ # Real transactions are always the outermost database transaction; savepoint transactions
6
+ # nested inside them are considered part of the transaction and are not counted separately.
7
+ module TransactionExtension
8
+ class << self
9
+ def inject(transaction_class)
10
+ unless transaction_class.include?(self)
11
+ transaction_class.prepend(self)
12
+ end
13
+ end
14
+ end
15
+
16
+ def initialize(...)
17
+ super
18
+ @active_record_query_counter_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
19
+ end
20
+
21
+ def commit(...)
22
+ # The transaction is only recorded after the COMMIT succeeds. If it raises, the start
23
+ # time is left in place so the rollback that Rails performs next is counted as a
24
+ # rollback rather than a successful commit. Recording here rather than in the
25
+ # transaction manager keeps time spent in after commit callbacks (which run after this
26
+ # method returns) out of the transaction time.
27
+ retval = super
28
+ active_record_query_counter_record_transaction
29
+ retval
30
+ end
31
+
32
+ def rollback(...)
33
+ super
34
+ ensure
35
+ # Recorded even if the ROLLBACK itself raises (e.g. the connection died) since the
36
+ # transaction is over either way.
37
+ active_record_query_counter_record_transaction(rollback: true)
38
+ end
39
+
40
+ private
41
+
42
+ def active_record_query_counter_record_transaction(rollback: false)
43
+ start_time = @active_record_query_counter_start_time
44
+ return unless start_time
45
+
46
+ @active_record_query_counter_start_time = nil
47
+ end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
48
+ ActiveRecordQueryCounter.add_transaction(start_time, end_time)
49
+ ActiveRecordQueryCounter.increment_rollbacks if rollback
50
+ end
51
+ end
52
+ end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "securerandom"
4
+
3
5
  require_relative "active_record_query_counter/connection_adapter_extension"
4
6
  require_relative "active_record_query_counter/counter"
5
7
  require_relative "active_record_query_counter/thresholds"
6
8
  require_relative "active_record_query_counter/transaction_info"
7
- require_relative "active_record_query_counter/transaction_manager_extension"
9
+ require_relative "active_record_query_counter/transaction_extension"
8
10
 
9
11
  # Everything you need to count ActiveRecord queries and row counts within a block.
10
12
  #
@@ -16,13 +18,17 @@ require_relative "active_record_query_counter/transaction_manager_extension"
16
18
  # puts ActiveRecordQueryCounter.row_count
17
19
  # end
18
20
  module ActiveRecordQueryCounter
21
+ VERSION = File.read(File.join(__dir__, "..", "VERSION")).strip
22
+
19
23
  autoload :RackMiddleware, "active_record_query_counter/rack_middleware"
20
24
  autoload :SidekiqMiddleware, "active_record_query_counter/sidekiq_middleware"
21
- autoload :VERSION, "active_record_query_counter/version"
22
25
 
23
26
  IGNORED_STATEMENTS = %w[SCHEMA EXPLAIN].freeze
24
27
  private_constant :IGNORED_STATEMENTS
25
28
 
29
+ @lock = Mutex.new
30
+ @default_thresholds = Thresholds.new
31
+
26
32
  class << self
27
33
  # Enable query counting within a block.
28
34
  #
@@ -37,8 +43,8 @@ module ActiveRecordQueryCounter
37
43
 
38
44
  transaction_count = counter.transaction_count
39
45
  if transaction_count > 0
40
- transaction_threshold = (counter.thresholds.transaction_count || -1)
41
- if transaction_threshold >= 0 && transaction_count >= transaction_threshold
46
+ transaction_threshold = counter.thresholds.transaction_count || -1
47
+ if transaction_threshold.between?(0, transaction_count)
42
48
  send_notification("transaction_count", counter.first_transaction_start_time, counter.last_transaction_end_time, transactions: counter.transactions)
43
49
  end
44
50
  end
@@ -65,32 +71,51 @@ module ActiveRecordQueryCounter
65
71
 
66
72
  # Increment the query counters.
67
73
  #
74
+ # The reported query time is the wall clock time spent executing the query with the GC
75
+ # time and Ruby thread CPU time subtracted out so that it reflects the time actually
76
+ # spent waiting on the database as closely as possible (see {.database_query_time}). This
77
+ # query time, rather than the raw wall clock time, is what is accumulated, compared against
78
+ # the threshold, and used as the duration of the emitted notification.
79
+ #
80
+ # @param sql [String] the SQL statement that was executed
81
+ # @param name [String, nil] the name of the query
82
+ # @param binds [Array] the bind parameters
68
83
  # @param row_count [Integer] the number of rows returned by the query
69
- # @param elapsed_time [Float] the time spent executing the query
84
+ # @param start_time [Float] the monotonic time when the query started
85
+ # @param end_time [Float] the monotonic time when the query ended
86
+ # @param gc_time [Float] the GC time in seconds that elapsed while the query ran
87
+ # @param cpu_time [Float] the thread CPU time in seconds spent while the query ran
70
88
  # @return [void]
71
89
  # @api private
72
- def add_query(sql, name, binds, row_count, start_time, end_time)
90
+ def add_query(sql, name, binds, row_count, start_time, end_time, gc_time, cpu_time)
73
91
  return if IGNORED_STATEMENTS.include?(name)
74
92
 
75
93
  counter = current_counter
76
94
  return unless counter.is_a?(Counter)
77
95
 
78
96
  elapsed_time = end_time - start_time
97
+ query_time = database_query_time(elapsed_time, gc_time, cpu_time)
79
98
  counter.query_count += 1
80
99
  counter.row_count += row_count
81
- counter.query_time += elapsed_time
100
+ counter.query_time += query_time
101
+
102
+ # The notification duration is the database query time, so the event ends that long after
103
+ # it started rather than at the raw wall clock end time.
104
+ notification_end_time = start_time + query_time
82
105
 
83
106
  trace = nil
84
- query_time_threshold = (counter.thresholds.query_time || -1)
85
- if query_time_threshold >= 0 && elapsed_time >= query_time_threshold
107
+ query_time_threshold = counter.thresholds.query_time || -1
108
+ if query_time_threshold.between?(0, query_time)
86
109
  trace = backtrace
87
- send_notification("query_time", start_time, end_time, sql: sql, binds: binds, row_count: row_count, trace: trace)
110
+ 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)
111
+ send_notification("query_time", start_time, notification_end_time, **payload)
88
112
  end
89
113
 
90
- row_count_threshold = (counter.thresholds.row_count || -1)
91
- if row_count_threshold >= 0 && row_count >= row_count_threshold
114
+ row_count_threshold = counter.thresholds.row_count || -1
115
+ if row_count_threshold.between?(0, row_count)
92
116
  trace ||= backtrace
93
- send_notification("row_count", start_time, end_time, sql: sql, binds: binds, row_count: row_count, trace: trace)
117
+ 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)
118
+ send_notification("row_count", start_time, notification_end_time, **payload)
94
119
  end
95
120
  end
96
121
 
@@ -107,8 +132,8 @@ module ActiveRecordQueryCounter
107
132
  trace = backtrace
108
133
  counter.add_transaction(trace: trace, start_time: start_time, end_time: end_time)
109
134
 
110
- transaction_time_threshold = (counter.thresholds.transaction_time || -1)
111
- if transaction_time_threshold >= 0 && end_time - start_time >= transaction_time_threshold
135
+ transaction_time_threshold = counter.thresholds.transaction_time || -1
136
+ if transaction_time_threshold.between?(0, end_time - start_time)
112
137
  send_notification("transaction_time", start_time, end_time, trace: backtrace)
113
138
  end
114
139
  end
@@ -194,7 +219,7 @@ module ActiveRecordQueryCounter
194
219
  # @return [Float, nil] the monotonic time when the last transaction ended,
195
220
  def last_transaction_end_time
196
221
  counter = current_counter
197
- counter.transactions.last&.end_time if counter.is_a?(Counter)
222
+ counter.last_transaction_end_time if counter.is_a?(Counter)
198
223
  end
199
224
 
200
225
  # Return an array of transaction information for any transactions that have been counted
@@ -240,9 +265,7 @@ module ActiveRecordQueryCounter
240
265
  # thresholds are used as the default values.
241
266
  #
242
267
  # @return [ActiveRecordQueryCounter::Thresholds]
243
- def default_thresholds
244
- @default_thresholds ||= Thresholds.new
245
- end
268
+ attr_reader :default_thresholds
246
269
 
247
270
  # Get the current local notification thresholds. These thresholds are only used within
248
271
  # the current `count_queries` block.
@@ -257,25 +280,39 @@ module ActiveRecordQueryCounter
257
280
  def enable!(connection_class)
258
281
  ActiveSupport.on_load(:active_record) do
259
282
  ConnectionAdapterExtension.inject(connection_class)
260
- TransactionManagerExtension.inject(ActiveRecord::ConnectionAdapters::TransactionManager)
283
+ TransactionExtension.inject(ActiveRecord::ConnectionAdapters::RealTransaction)
261
284
  end
262
285
 
263
- @cache_subscription ||= ActiveSupport::Notifications.subscribe("sql.active_record") do |_name, _start_time, _end_time, _id, payload|
264
- if payload[:cached]
265
- counter = current_counter
266
- counter.cached_query_count += 1 if counter
286
+ @lock.synchronize do
287
+ @cache_subscription ||= ActiveSupport::Notifications.subscribe("sql.active_record") do |_name, _start_time, _end_time, _id, payload|
288
+ if payload[:cached] && !IGNORED_STATEMENTS.include?(payload[:name])
289
+ counter = current_counter
290
+ counter.cached_query_count += 1 if counter.is_a?(Counter)
291
+ end
267
292
  end
268
293
  end
269
294
  end
270
295
 
271
296
  private
272
297
 
298
+ # The counter is stored in ActiveSupport::IsolatedExecutionState when available so that
299
+ # it follows the application's configured isolation level (thread or fiber). The fallback
300
+ # for ActiveSupport 6.x uses Thread.current, which is fiber-local, so on those versions
301
+ # queries executed in a fiber spawned inside the block are not counted.
273
302
  def current_counter
274
- Thread.current[:active_record_query_counter]
303
+ if defined?(ActiveSupport::IsolatedExecutionState)
304
+ ActiveSupport::IsolatedExecutionState[:active_record_query_counter]
305
+ else
306
+ Thread.current[:active_record_query_counter]
307
+ end
275
308
  end
276
309
 
277
310
  def current_counter=(counter)
278
- Thread.current[:active_record_query_counter] = counter
311
+ if defined?(ActiveSupport::IsolatedExecutionState)
312
+ ActiveSupport::IsolatedExecutionState[:active_record_query_counter] = counter
313
+ else
314
+ Thread.current[:active_record_query_counter] = counter
315
+ end
279
316
  end
280
317
 
281
318
  def send_notification(name, start_time, end_time, payload = {})
@@ -283,6 +320,42 @@ module ActiveRecordQueryCounter
283
320
  ActiveSupport::Notifications.publish("active_record_query_counter.#{name}", start_time, end_time, id, payload)
284
321
  end
285
322
 
323
+ def notification_payload(sql:, binds:, row_count:, trace:, elapsed_time:, gc_time:, cpu_time:)
324
+ {
325
+ sql: sql,
326
+ binds: binds,
327
+ row_count: row_count,
328
+ trace: trace,
329
+ elapsed_time: (elapsed_time * 1000.0).round(6),
330
+ gc_time: (gc_time * 1000.0).round(6),
331
+ cpu_time: (cpu_time * 1000.0).round(6)
332
+ }
333
+ end
334
+
335
+ # Estimate the time spent waiting on the database by subtracting the GC time and thread CPU
336
+ # time from the wall clock time the query took.
337
+ #
338
+ # The GC time and CPU time normally measure distinct, non-overlapping intervals: a GC pause
339
+ # triggered by another thread happens while this thread is parked waiting on the database
340
+ # (off CPU, so it does not count as CPU time), while CPU time covers the Ruby work of
341
+ # building the result. They only overlap when the query's own thread triggers a GC, which
342
+ # runs on that thread and so counts as both GC time and CPU time. When that overlap is large
343
+ # enough to drive the result negative, only the larger of the two is subtracted so the shared
344
+ # interval is removed once. The result is clamped so it never exceeds the wall clock time and
345
+ # is never negative.
346
+ #
347
+ # @param elapsed_time [Float] the wall clock time the query took in seconds
348
+ # @param gc_time [Float] the GC time in seconds that elapsed while the query ran
349
+ # @param cpu_time [Float] the thread CPU time in seconds spent while the query ran
350
+ # @return [Float] the estimated database time in seconds
351
+ def database_query_time(elapsed_time, gc_time, cpu_time)
352
+ return 0.0 if elapsed_time <= 0.0
353
+
354
+ query_time = elapsed_time - (gc_time + cpu_time)
355
+ query_time = elapsed_time - [gc_time, cpu_time].max if query_time.negative?
356
+ query_time.clamp(0.0, elapsed_time)
357
+ end
358
+
286
359
  def backtrace
287
360
  caller.reject { |line| line.start_with?(__dir__) }
288
361
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_query_counter
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-12-17 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activerecord
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '5.1'
18
+ version: '6.0'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: '5.1'
25
+ version: '6.0'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: bundler
29
28
  requirement: !ruby/object:Gem::Requirement
@@ -38,7 +37,6 @@ dependencies:
38
37
  - - ">="
39
38
  - !ruby/object:Gem::Version
40
39
  version: '0'
41
- description:
42
40
  email:
43
41
  - bbdurand@gmail.com
44
42
  executables: []
@@ -56,9 +54,8 @@ files:
56
54
  - lib/active_record_query_counter/rack_middleware.rb
57
55
  - lib/active_record_query_counter/sidekiq_middleware.rb
58
56
  - lib/active_record_query_counter/thresholds.rb
57
+ - lib/active_record_query_counter/transaction_extension.rb
59
58
  - lib/active_record_query_counter/transaction_info.rb
60
- - lib/active_record_query_counter/transaction_manager_extension.rb
61
- - lib/active_record_query_counter/version.rb
62
59
  homepage: https://github.com/bdurand/active_record_query_counter
63
60
  licenses:
64
61
  - MIT
@@ -66,7 +63,6 @@ metadata:
66
63
  homepage_uri: https://github.com/bdurand/active_record_query_counter
67
64
  source_code_uri: https://github.com/bdurand/active_record_query_counter
68
65
  changelog_uri: https://github.com/bdurand/active_record_query_counter/blob/main/CHANGELOG.md
69
- post_install_message:
70
66
  rdoc_options: []
71
67
  require_paths:
72
68
  - lib
@@ -74,15 +70,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
74
70
  requirements:
75
71
  - - ">="
76
72
  - !ruby/object:Gem::Version
77
- version: '2.5'
73
+ version: '3.1'
78
74
  required_rubygems_version: !ruby/object:Gem::Requirement
79
75
  requirements:
80
76
  - - ">="
81
77
  - !ruby/object:Gem::Version
82
78
  version: '0'
83
79
  requirements: []
84
- rubygems_version: 3.4.10
85
- signing_key:
80
+ rubygems_version: 4.0.3
86
81
  specification_version: 4
87
82
  summary: Provides detailed insights into how your code interacts with the database
88
83
  by hooking into ActiveRecord.
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveRecordQueryCounter
4
- # Extension to ActiveRecord::ConnectionAdapters::TransactionManager to count transactions.
5
- module TransactionManagerExtension
6
- class << self
7
- def inject(transaction_manager_class)
8
- unless transaction_manager_class.include?(self)
9
- transaction_manager_class.prepend(self)
10
- end
11
- end
12
- end
13
-
14
- def begin_transaction(*args, **kwargs)
15
- if open_transactions == 0
16
- @active_record_query_counter_transaction_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
17
- end
18
- super
19
- end
20
-
21
- def commit_transaction(*args)
22
- if @active_record_query_counter_transaction_start_time && open_transactions == 1
23
- end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
24
- ActiveRecordQueryCounter.add_transaction(@active_record_query_counter_transaction_start_time, end_time)
25
- @active_record_query_counter_transaction_start_time = nil
26
- end
27
- super
28
- end
29
-
30
- def rollback_transaction(*args)
31
- if @active_record_query_counter_transaction_start_time && open_transactions == 1
32
- end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
33
- ActiveRecordQueryCounter.add_transaction(@active_record_query_counter_transaction_start_time, end_time)
34
- ActiveRecordQueryCounter.increment_rollbacks
35
- @active_record_query_counter_transaction_start_time = nil
36
- end
37
- super
38
- end
39
- end
40
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveRecordQueryCounter
4
- VERSION = File.read(File.join(__dir__, "..", "..", "VERSION")).strip
5
- end