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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +11 -1
- data/VERSION +1 -1
- data/lib/active_record_query_counter/connection_adapter_extension.rb +6 -19
- data/lib/active_record_query_counter/counter.rb +7 -2
- data/lib/active_record_query_counter/query_info.rb +28 -0
- data/lib/active_record_query_counter/transaction_details.rb +74 -0
- data/lib/active_record_query_counter/transaction_extension.rb +13 -1
- data/lib/active_record_query_counter/transaction_info.rb +13 -4
- data/lib/active_record_query_counter.rb +106 -5
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 499ad971fe53532f23bfa674961133b6c39f850b39d89b102d959be760ba386c
|
|
4
|
+
data.tar.gz: 0f2eb66f8d2d274ddd2b173fce6f21ba35a7069d398f0f72e03052905f0b9221
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
136
|
-
counter
|
|
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(
|
|
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.
|
|
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
|