active_record_query_counter 3.0.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 +4 -4
- data/CHANGELOG.md +14 -0
- data/VERSION +1 -1
- data/lib/active_record_query_counter/connection_adapter_extension.rb +2 -2
- data/lib/active_record_query_counter/transaction_extension.rb +52 -0
- data/lib/active_record_query_counter.rb +39 -21
- metadata +3 -4
- data/lib/active_record_query_counter/transaction_manager_extension.rb +0 -40
- data/lib/active_record_query_counter/version.rb +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1966d4714e8fc7a3d77b86be07c8c930470d2af298dbd8d88d4bee4043a4da84
|
|
4
|
+
data.tar.gz: 8ad2b534042042259038f72daff078b6302b552f6a742b4f38f0504afe9a8919
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 13a2c8c037ea0b47ea11384e133c294d047a74b34f89564a49adb0c339e2e489a1d61ef8a48c76d31b7a7abbd9afca6caf9ebfea011dcd703dbfec19fa65e46f
|
|
7
|
+
data.tar.gz: 6ba58ac495883fa98d20e7cda7ae363444f279db7a543016a6ffcdacf1f4f2d7404c8f7f7d06425c9f159d004788c73bde3fb857ce3e63a218380d6d00e359fd
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ 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
|
+
|
|
7
21
|
## 3.0.0
|
|
8
22
|
|
|
9
23
|
### Changed
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.1.0
|
|
@@ -52,13 +52,13 @@ module ActiveRecordQueryCounter
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
module ExecQuery
|
|
55
|
-
def exec_query(sql, name = nil, binds = [],
|
|
55
|
+
def exec_query(sql, name = nil, binds = [], ...)
|
|
56
56
|
ConnectionAdapterExtension.measure_query(sql, name, binds) { super }
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
module InternalExecQuery
|
|
61
|
-
def internal_exec_query(sql, name = nil, binds = [],
|
|
61
|
+
def internal_exec_query(sql, name = nil, binds = [], ...)
|
|
62
62
|
ConnectionAdapterExtension.measure_query(sql, name, binds) { super }
|
|
63
63
|
end
|
|
64
64
|
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/
|
|
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 =
|
|
41
|
-
if 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
|
|
@@ -98,15 +104,15 @@ module ActiveRecordQueryCounter
|
|
|
98
104
|
notification_end_time = start_time + query_time
|
|
99
105
|
|
|
100
106
|
trace = nil
|
|
101
|
-
query_time_threshold =
|
|
102
|
-
if query_time_threshold
|
|
107
|
+
query_time_threshold = counter.thresholds.query_time || -1
|
|
108
|
+
if query_time_threshold.between?(0, query_time)
|
|
103
109
|
trace = backtrace
|
|
104
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)
|
|
105
111
|
send_notification("query_time", start_time, notification_end_time, **payload)
|
|
106
112
|
end
|
|
107
113
|
|
|
108
|
-
row_count_threshold =
|
|
109
|
-
if row_count_threshold
|
|
114
|
+
row_count_threshold = counter.thresholds.row_count || -1
|
|
115
|
+
if row_count_threshold.between?(0, row_count)
|
|
110
116
|
trace ||= backtrace
|
|
111
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)
|
|
112
118
|
send_notification("row_count", start_time, notification_end_time, **payload)
|
|
@@ -126,8 +132,8 @@ module ActiveRecordQueryCounter
|
|
|
126
132
|
trace = backtrace
|
|
127
133
|
counter.add_transaction(trace: trace, start_time: start_time, end_time: end_time)
|
|
128
134
|
|
|
129
|
-
transaction_time_threshold =
|
|
130
|
-
if transaction_time_threshold
|
|
135
|
+
transaction_time_threshold = counter.thresholds.transaction_time || -1
|
|
136
|
+
if transaction_time_threshold.between?(0, end_time - start_time)
|
|
131
137
|
send_notification("transaction_time", start_time, end_time, trace: backtrace)
|
|
132
138
|
end
|
|
133
139
|
end
|
|
@@ -213,7 +219,7 @@ module ActiveRecordQueryCounter
|
|
|
213
219
|
# @return [Float, nil] the monotonic time when the last transaction ended,
|
|
214
220
|
def last_transaction_end_time
|
|
215
221
|
counter = current_counter
|
|
216
|
-
counter.
|
|
222
|
+
counter.last_transaction_end_time if counter.is_a?(Counter)
|
|
217
223
|
end
|
|
218
224
|
|
|
219
225
|
# Return an array of transaction information for any transactions that have been counted
|
|
@@ -259,9 +265,7 @@ module ActiveRecordQueryCounter
|
|
|
259
265
|
# thresholds are used as the default values.
|
|
260
266
|
#
|
|
261
267
|
# @return [ActiveRecordQueryCounter::Thresholds]
|
|
262
|
-
|
|
263
|
-
@default_thresholds ||= Thresholds.new
|
|
264
|
-
end
|
|
268
|
+
attr_reader :default_thresholds
|
|
265
269
|
|
|
266
270
|
# Get the current local notification thresholds. These thresholds are only used within
|
|
267
271
|
# the current `count_queries` block.
|
|
@@ -276,25 +280,39 @@ module ActiveRecordQueryCounter
|
|
|
276
280
|
def enable!(connection_class)
|
|
277
281
|
ActiveSupport.on_load(:active_record) do
|
|
278
282
|
ConnectionAdapterExtension.inject(connection_class)
|
|
279
|
-
|
|
283
|
+
TransactionExtension.inject(ActiveRecord::ConnectionAdapters::RealTransaction)
|
|
280
284
|
end
|
|
281
285
|
|
|
282
|
-
@
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
286
292
|
end
|
|
287
293
|
end
|
|
288
294
|
end
|
|
289
295
|
|
|
290
296
|
private
|
|
291
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.
|
|
292
302
|
def current_counter
|
|
293
|
-
|
|
303
|
+
if defined?(ActiveSupport::IsolatedExecutionState)
|
|
304
|
+
ActiveSupport::IsolatedExecutionState[:active_record_query_counter]
|
|
305
|
+
else
|
|
306
|
+
Thread.current[:active_record_query_counter]
|
|
307
|
+
end
|
|
294
308
|
end
|
|
295
309
|
|
|
296
310
|
def current_counter=(counter)
|
|
297
|
-
|
|
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
|
|
298
316
|
end
|
|
299
317
|
|
|
300
318
|
def send_notification(name, start_time, end_time, 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.
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Durand
|
|
@@ -54,9 +54,8 @@ files:
|
|
|
54
54
|
- lib/active_record_query_counter/rack_middleware.rb
|
|
55
55
|
- lib/active_record_query_counter/sidekiq_middleware.rb
|
|
56
56
|
- lib/active_record_query_counter/thresholds.rb
|
|
57
|
+
- lib/active_record_query_counter/transaction_extension.rb
|
|
57
58
|
- lib/active_record_query_counter/transaction_info.rb
|
|
58
|
-
- lib/active_record_query_counter/transaction_manager_extension.rb
|
|
59
|
-
- lib/active_record_query_counter/version.rb
|
|
60
59
|
homepage: https://github.com/bdurand/active_record_query_counter
|
|
61
60
|
licenses:
|
|
62
61
|
- MIT
|
|
@@ -78,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
78
77
|
- !ruby/object:Gem::Version
|
|
79
78
|
version: '0'
|
|
80
79
|
requirements: []
|
|
81
|
-
rubygems_version:
|
|
80
|
+
rubygems_version: 4.0.3
|
|
82
81
|
specification_version: 4
|
|
83
82
|
summary: Provides detailed insights into how your code interacts with the database
|
|
84
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
|