active_record_query_counter 1.1.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -5
- data/README.md +153 -7
- data/VERSION +1 -1
- data/active_record_query_counter.gemspec +7 -3
- data/lib/active_record_query_counter/connection_adapter_extension.rb +16 -0
- data/lib/active_record_query_counter/counter.rb +88 -0
- data/lib/active_record_query_counter/rack_middleware.rb +25 -0
- data/lib/active_record_query_counter/sidekiq_middleware.rb +44 -0
- data/lib/active_record_query_counter/thresholds.rb +48 -0
- data/lib/active_record_query_counter/transaction_info.rb +22 -0
- data/lib/active_record_query_counter/transaction_manager_extension.rb +31 -0
- data/lib/active_record_query_counter/version.rb +5 -0
- data/lib/active_record_query_counter.rb +170 -101
- metadata +18 -13
- data/.github/dependabot.yml +0 -12
- data/.github/workflows/continuous_integration.yml +0 -73
- data/.standard.yml +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d653eb9665511008bc78023c7e39ae8eb189e051847d83e16244424b5cbe595
|
4
|
+
data.tar.gz: 6e9643c840a635228ea0a04cb02277b91c34b1ea5e69bd5b93753066be8ab4fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df192239b02e3ed039eac91a64393d5332cad36dce242e9e02948b3ec0cbe23e7ae5d4b23dc58ebf01158fa885680135e6719f444a871abd37779e85a9f85d2d
|
7
|
+
data.tar.gz: '069e6959ba3f7ead49824ae91d35774b3b336afe18248c84800eaca65115daa040bdf5871d0c3e591b648cce2b0d5414078fe51ef39bef8d9f0e777be8e7330d'
|
data/CHANGELOG.md
CHANGED
@@ -4,20 +4,50 @@ 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
|
+
## 2.1.0
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- Added count of queries that hit the query cache instead of being sent to the database.
|
12
|
+
|
13
|
+
### Removed
|
14
|
+
|
15
|
+
- Dropped support for ActiveRecord 5.0.
|
16
|
+
|
17
|
+
## 2.0.0
|
18
|
+
|
19
|
+
### Added
|
20
|
+
|
21
|
+
- Added capability to send ActiveSupport notifications when query thresholds are exceeded.
|
22
|
+
|
23
|
+
### Changed
|
24
|
+
|
25
|
+
- Calculate elapsed time using monotonic time rather than wall clock time.
|
26
|
+
- Schema queries to get the table structure and explain plan queries are no longer counted.
|
27
|
+
- **Breaking change**: transaction information is now returned in an array of `ActiveRecordQueryCounter::TransactionInfo` objects.
|
28
|
+
- **Breaking change**: internal API for tracking queries and transactions has changed
|
29
|
+
|
7
30
|
## 1.1.2
|
31
|
+
|
8
32
|
### Added
|
9
|
-
|
33
|
+
|
34
|
+
- Ruby 3.0 compatibility
|
35
|
+
|
10
36
|
### Removed
|
11
|
-
|
37
|
+
|
38
|
+
- Dropped support for ActiveRecord 4.2
|
12
39
|
|
13
40
|
## 1.1.1
|
14
41
|
### Added
|
15
|
-
|
42
|
+
|
43
|
+
- Expose stack traces where transactions are being committed.
|
16
44
|
|
17
45
|
## 1.1.0
|
18
46
|
### Added
|
19
|
-
|
47
|
+
|
48
|
+
- Add number of transactions to statistics being tracked.
|
20
49
|
|
21
50
|
## 1.0.0
|
22
51
|
### Added
|
23
|
-
|
52
|
+
|
53
|
+
- Track stats about queries run by ActiveRecord within a block.
|
data/README.md
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
# ActiveRecordQueryCounter
|
2
2
|
|
3
3
|
![Continuous Integration](https://github.com/bdurand/active_record_query_counter/workflows/Continuous%20Integration/badge.svg)
|
4
|
-
[![Maintainability](https://api.codeclimate.com/v1/badges/21094ecec0c151983bb1/maintainability)](https://codeclimate.com/github/bdurand/active_record_query_counter/maintainability)
|
5
|
-
[![Test Coverage](https://api.codeclimate.com/v1/badges/21094ecec0c151983bb1/test_coverage)](https://codeclimate.com/github/bdurand/active_record_query_counter/test_coverage)
|
6
4
|
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
|
7
5
|
|
8
|
-
This gem injects itself into ActiveRecord to
|
6
|
+
This gem injects itself into ActiveRecord to give you insight into how your code is using the database.
|
9
7
|
|
10
|
-
|
8
|
+
Within a block of code, it will count:
|
9
|
+
|
10
|
+
- the number of queries
|
11
|
+
- the number of rows returned
|
12
|
+
- the amount of time spent on queries
|
13
|
+
- the number of transactions used
|
14
|
+
- the amount of time spent inside transactions
|
15
|
+
|
16
|
+
The intended use is to gather instrumentation stats for finding hot spots in your code that produce a lot of queries or slow queries or queries that return a lot of rows. It can also be used to find code that is not using transactions when making multiple updates to the database.
|
11
17
|
|
12
18
|
## Usage
|
13
19
|
|
@@ -38,9 +44,7 @@ ActiveRecordQueryCounter.count_queries do
|
|
38
44
|
end
|
39
45
|
```
|
40
46
|
|
41
|
-
This gem includes middleware for both Rack and Sidekiq that will enable query counting.
|
42
|
-
|
43
|
-
If you are using Rails with Sidekiq, you can enable both with an initializer.
|
47
|
+
This gem includes middleware for both Rack and Sidekiq that will enable query counting on web requests and in workers. If you are using Rails with Sidekiq, you can enable both with an initializer.
|
44
48
|
|
45
49
|
```ruby
|
46
50
|
ActiveSupport.on_load(:active_record) do
|
@@ -55,3 +59,145 @@ Sidekiq.configure_server do |config|
|
|
55
59
|
end
|
56
60
|
end
|
57
61
|
```
|
62
|
+
|
63
|
+
### Notifications
|
64
|
+
|
65
|
+
You can also subscribe to ActiveSupport notifications to get notified when query thresholds are exceeded.
|
66
|
+
|
67
|
+
#### active_record_query_counter.query_time notification
|
68
|
+
|
69
|
+
This notification is triggered when a query takes longer than the `query_time` threshold. The payload contains the following keys:
|
70
|
+
|
71
|
+
- `:sql` - The SQL statement that was executed.
|
72
|
+
- `:binds` - The bind parameters that were used.
|
73
|
+
- `:row_count` - The number of rows returned.
|
74
|
+
- `:trace` - The stack trace of where the query was executed.
|
75
|
+
|
76
|
+
#### active_record_query_counter.row_count notification
|
77
|
+
|
78
|
+
This notification is triggered when a query returns more rows than the `row_count` threshold. The payload contains the following keys:
|
79
|
+
|
80
|
+
- `:sql` - The SQL statement that was executed.
|
81
|
+
- `:binds` - The bind parameters that were used.
|
82
|
+
- `:row_count` - The number of rows returned.
|
83
|
+
- `:trace` - The stack trace of where the query was executed.
|
84
|
+
|
85
|
+
#### active_record_query_counter.transaction_time notification
|
86
|
+
|
87
|
+
This notification is triggered when a transaction takes longer than the `transaction_time` threshold. The payload contains the following keys:
|
88
|
+
|
89
|
+
- `:trace` - The stack trace of where the transaction was completed.
|
90
|
+
|
91
|
+
#### active_record_query_counter.transaction_count notification
|
92
|
+
|
93
|
+
This notification is triggered when a transaction takes longer than the `transaction_count` threshold. The payload contains the following keys:
|
94
|
+
|
95
|
+
- `:transactions` - An array of `ActiveRecordQueryCounter::TransactionInfo` objects.
|
96
|
+
|
97
|
+
The duration of the notification event is the time between when the first transaction was started and the last transaction was completed.
|
98
|
+
|
99
|
+
#### Thresholds
|
100
|
+
|
101
|
+
The thresholds for triggering notifications can be set globally in an initializer:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
ActiveRecordQueryCounter.default_thresholds.set(
|
105
|
+
query_time: 2.0,
|
106
|
+
row_count: 1000,
|
107
|
+
transaction_time: 5.0,
|
108
|
+
transaction_count: 2
|
109
|
+
)
|
110
|
+
```
|
111
|
+
|
112
|
+
They can be set locally inside a `count_queries` block with the `thresholds` object. Local thresholds will override the global thresholds only inside the block and will not change any global state.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
ActiveRecordQueryCounter.count_queries do
|
116
|
+
ActiveRecordQueryCounter.thresholds.set(
|
117
|
+
query_time: 1.0,
|
118
|
+
row_count: 100,
|
119
|
+
transaction_time: 2.0,
|
120
|
+
transaction_count: 1
|
121
|
+
)
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
You can pass thresholds to individual Sidekiq workers via the `sidekiq_options` on the worker.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
class MyWorker
|
129
|
+
include Sidekiq::Worker
|
130
|
+
|
131
|
+
sidekiq_options(
|
132
|
+
active_record_query_counter: {
|
133
|
+
thresholds: {
|
134
|
+
query_time: 1.0,
|
135
|
+
row_count: 100,
|
136
|
+
transaction_time: 2.0,
|
137
|
+
transaction_count: 1
|
138
|
+
}
|
139
|
+
}
|
140
|
+
)
|
141
|
+
# You can disable thresholds for the worker by setting `thresholds: false`.
|
142
|
+
|
143
|
+
def perform
|
144
|
+
do_something
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
You can set separate thresholds on the Rack middleware when you install it.
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
Rails.application.config.middleware.use(ActiveRecordQueryCounter::RackMiddleware, thresholds: {
|
153
|
+
query_time: 1.0,
|
154
|
+
row_count: 100,
|
155
|
+
transaction_time: 2.0,
|
156
|
+
transaction_count: 1
|
157
|
+
})
|
158
|
+
```
|
159
|
+
|
160
|
+
#### Example Notification Subscriptions
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
ActiveRecordQueryCounter.default_thresholds.query_time = 1.0
|
164
|
+
ActiveRecordQueryCounter.default_thresholds.row_count = 1000
|
165
|
+
ActiveRecordQueryCounter.default_thresholds.transaction_time = 2.0
|
166
|
+
ActiveRecordQueryCounter.default_thresholds.transaction_count = 1
|
167
|
+
|
168
|
+
ActiveSupport::Notifications.subscribe('active_record_query_counter.query_time') do |*args|
|
169
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
170
|
+
puts "Query time exceeded (#{event.duration}ms): #{event.payload[:sql]}"
|
171
|
+
puts event.payload[:trace].join("\n")
|
172
|
+
end
|
173
|
+
|
174
|
+
ActiveSupport::Notifications.subscribe('active_record_query_counter.row_count') do |*args|
|
175
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
176
|
+
puts "Row count exceeded (#{event.payload[:row_count]} rows): #{event.payload[:sql]}"
|
177
|
+
puts event.payload[:trace].join("\n")
|
178
|
+
end
|
179
|
+
|
180
|
+
ActiveSupport::Notifications.subscribe('active_record_query_counter.transaction_time') do |*args|
|
181
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
182
|
+
puts "Transaction time exceeded (#{event.duration}ms)"
|
183
|
+
puts event.payload[:trace].join("\n")
|
184
|
+
end
|
185
|
+
|
186
|
+
ActiveSupport::Notifications.subscribe('active_record_query_counter.transaction_count') do |*args|
|
187
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
188
|
+
puts "Transaction count exceeded (#{event.payload[:transactions].size} transactions in #{event.duration}ms)"
|
189
|
+
event.payload[:transactions].each do |info|
|
190
|
+
puts info.trace.join("\n")
|
191
|
+
end
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
## Contributing
|
196
|
+
|
197
|
+
Open a pull request on GitHub.
|
198
|
+
|
199
|
+
Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
|
200
|
+
|
201
|
+
## License
|
202
|
+
|
203
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.1.0
|
@@ -11,12 +11,14 @@ Gem::Specification.new do |spec|
|
|
11
11
|
# Specify which files should be added to the gem when it is released.
|
12
12
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
13
13
|
ignore_files = %w[
|
14
|
-
.
|
15
|
-
.travis.yml
|
14
|
+
.
|
16
15
|
Appraisals
|
17
16
|
Gemfile
|
18
17
|
Gemfile.lock
|
19
18
|
Rakefile
|
19
|
+
config.ru
|
20
|
+
assets/
|
21
|
+
bin/
|
20
22
|
gemfiles/
|
21
23
|
spec/
|
22
24
|
]
|
@@ -26,7 +28,9 @@ Gem::Specification.new do |spec|
|
|
26
28
|
|
27
29
|
spec.require_paths = ["lib"]
|
28
30
|
|
29
|
-
spec.add_dependency "activerecord", ">= 5.
|
31
|
+
spec.add_dependency "activerecord", ">= 5.1"
|
30
32
|
|
31
33
|
spec.add_development_dependency "bundler"
|
34
|
+
|
35
|
+
spec.required_ruby_version = ">= 2.5"
|
32
36
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordQueryCounter
|
4
|
+
# Module to prepend to the connection adapter to inject the counting behavior.
|
5
|
+
module ConnectionAdapterExtension
|
6
|
+
def exec_query(sql, name = nil, binds = [], *args, **kwargs)
|
7
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
8
|
+
result = super
|
9
|
+
if result.is_a?(ActiveRecord::Result)
|
10
|
+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
11
|
+
ActiveRecordQueryCounter.add_query(sql, name, binds, result.length, start_time, end_time)
|
12
|
+
end
|
13
|
+
result
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordQueryCounter
|
4
|
+
# Data structure for storing query information encountered within a block.
|
5
|
+
class Counter
|
6
|
+
attr_accessor :query_count, :row_count, :query_time, :cached_query_count
|
7
|
+
attr_reader :thresholds
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@query_count = 0
|
11
|
+
@row_count = 0
|
12
|
+
@query_time = 0.0
|
13
|
+
@cached_query_count = 0
|
14
|
+
@transactions_hash = {}
|
15
|
+
@thresholds = ActiveRecordQueryCounter.default_thresholds.dup
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return the percentage of queries that used the query cache instead of going to the database.
|
19
|
+
#
|
20
|
+
# @return [Float]
|
21
|
+
def cache_hit_rate
|
22
|
+
total_queries = query_count + cached_query_count
|
23
|
+
if total_queries > 0
|
24
|
+
(cached_query_count.to_f / total_queries)
|
25
|
+
else
|
26
|
+
0.0
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return an array of transaction information for any transactions that have been tracked
|
31
|
+
# by the counter.
|
32
|
+
#
|
33
|
+
# @return [Array<ActiveRecordQueryCounter::TransactionInfo>]
|
34
|
+
def transactions
|
35
|
+
@transactions_hash.values.flatten.sort_by(&:start_time)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Add a tracked transaction.
|
39
|
+
#
|
40
|
+
# @param trace [Array<String>] the trace of the transaction
|
41
|
+
# @param start_time [Float] the monotonic time when the transaction began
|
42
|
+
# @param end_time [Float] the monotonic time when the transaction ended
|
43
|
+
# @return [void]
|
44
|
+
# @api private
|
45
|
+
def add_transaction(trace:, start_time:, end_time:)
|
46
|
+
trace_transactions = @transactions_hash[trace]
|
47
|
+
if trace_transactions
|
48
|
+
# Memory optimization so that we don't store duplicate traces for every transaction in a loop.
|
49
|
+
trace = trace_transactions.first.trace
|
50
|
+
else
|
51
|
+
trace_transactions = []
|
52
|
+
@transactions_hash[trace] = trace_transactions
|
53
|
+
end
|
54
|
+
|
55
|
+
trace_transactions << TransactionInfo.new(start_time: start_time, end_time: end_time, trace: trace)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Return the number of transactions that have been tracked by the counter.
|
59
|
+
#
|
60
|
+
# @return [Integer]
|
61
|
+
def transaction_count
|
62
|
+
@transactions_hash.values.flatten.size
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return the total time spent in transactions that have been tracked by the counter.
|
66
|
+
#
|
67
|
+
# @return [Float]
|
68
|
+
def transaction_time
|
69
|
+
@transactions_hash.values.flatten.sum(&:elapsed_time)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get the time when the first transaction began.
|
73
|
+
#
|
74
|
+
# @return [Float, nil] the monotonic time when the first transaction began,
|
75
|
+
# or nil if no transactions have been tracked
|
76
|
+
def first_transaction_start_time
|
77
|
+
transactions.first&.start_time
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get the time when the last transaction completed.
|
81
|
+
#
|
82
|
+
# @return [Float, nil] the monotonic time when the first transaction completed,
|
83
|
+
# or nil if no transactions have been tracked
|
84
|
+
def last_transaction_end_time
|
85
|
+
transactions.max_by(&:end_time)&.end_time
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordQueryCounter
|
4
|
+
# Rack middleware to count queries on a request.
|
5
|
+
class RackMiddleware
|
6
|
+
# @param app [Object] The Rack application.
|
7
|
+
# @param thresholds [Hash] Options for the notification thresholds. Valid keys are:
|
8
|
+
# * `:query_time` - The minimum query time to send a notification for.
|
9
|
+
# * `:row_count` - The minimum row count to send a notification for.
|
10
|
+
# * `:transaction_time` - The minimum transaction time to send a notification for.
|
11
|
+
# * `:transaction_count` - The minimum transaction count to send a notification for.
|
12
|
+
def initialize(app, thresholds: nil)
|
13
|
+
@app = app
|
14
|
+
@thresholds = thresholds.dup.freeze if thresholds
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
ActiveRecordQueryCounter.count_queries do
|
19
|
+
ActiveRecordQueryCounter.thresholds.set(@thresholds) if @thresholds
|
20
|
+
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordQueryCounter
|
4
|
+
# Sidekiq middleware to count queries on a job.
|
5
|
+
#
|
6
|
+
# Notification thresholds can be set per worker with the `active_record_query_counter.thresholds` key in the
|
7
|
+
# `sidekiq_options` hash. Valid keys are:
|
8
|
+
# * `:query_time` - The minimum query time to send a notification for.
|
9
|
+
# * `:row_count` - The minimum row count to send a notification for.
|
10
|
+
# * `:transaction_time` - The minimum transaction time to send a notification for.
|
11
|
+
# * `:transaction_count` - The minimum transaction count to send a notification for.
|
12
|
+
#
|
13
|
+
# Thresholds can be disabled for a worker by setting `active_record_query_counter.thresholds` to `false`.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
#
|
17
|
+
# class MyWorker
|
18
|
+
# include Sidekiq::Worker
|
19
|
+
#
|
20
|
+
# sidekiq_options active_record_query_counter: {thresholds: {query_time: 1.5}}
|
21
|
+
#
|
22
|
+
# def perform
|
23
|
+
# # ...
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
class SidekiqMiddleware
|
27
|
+
if defined?(Sidekiq::ServerMiddleware)
|
28
|
+
include Sidekiq::ServerMiddleware
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(job_instance, job_payload, queue)
|
32
|
+
ActiveRecordQueryCounter.count_queries do
|
33
|
+
thresholds = job_payload.dig("active_record_query_counter", "thresholds")
|
34
|
+
if thresholds.is_a?(Hash)
|
35
|
+
ActiveRecordQueryCounter.thresholds.set(thresholds)
|
36
|
+
elsif thresholds == false
|
37
|
+
ActiveRecordQueryCounter.thresholds.clear
|
38
|
+
end
|
39
|
+
|
40
|
+
yield
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordQueryCounter
|
4
|
+
# Thresholds for sending notifications based on query time, row count, transaction time, and
|
5
|
+
# transaction count.
|
6
|
+
class Thresholds
|
7
|
+
attr_reader :query_time, :row_count, :transaction_time, :transaction_count
|
8
|
+
|
9
|
+
def query_time=(value)
|
10
|
+
@query_time = value&.to_f
|
11
|
+
end
|
12
|
+
|
13
|
+
def row_count=(value)
|
14
|
+
@row_count = value&.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
def transaction_time=(value)
|
18
|
+
@transaction_time = value&.to_f
|
19
|
+
end
|
20
|
+
|
21
|
+
def transaction_count=(value)
|
22
|
+
@transaction_count = value&.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
# Set threshold values from a hash.
|
26
|
+
#
|
27
|
+
# @param attributes [Hash] the attributes to set
|
28
|
+
# @return [void]
|
29
|
+
def set(values)
|
30
|
+
values.each do |key, value|
|
31
|
+
setter = "#{key}="
|
32
|
+
if respond_to?(setter)
|
33
|
+
public_send("#{key}=", value)
|
34
|
+
else
|
35
|
+
raise ArgumentError, "Unknown threshold: #{key}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Clear all threshold values.
|
41
|
+
def clear
|
42
|
+
@query_time = nil
|
43
|
+
@row_count = nil
|
44
|
+
@transaction_time = nil
|
45
|
+
@transaction_count = nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
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.
|
6
|
+
class TransactionInfo
|
7
|
+
attr_reader :start_time, :end_time, :trace
|
8
|
+
|
9
|
+
def initialize(start_time:, end_time:, trace:)
|
10
|
+
@start_time = start_time
|
11
|
+
@end_time = end_time
|
12
|
+
@trace = trace
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return the time spent in the transaction.
|
16
|
+
#
|
17
|
+
# @return [Float]
|
18
|
+
def elapsed_time
|
19
|
+
end_time - start_time
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordQueryCounter
|
4
|
+
# Extension to ActiveRecord::ConnectionAdapters::TransactionManager to count transactions.
|
5
|
+
module TransactionManagerExtension
|
6
|
+
def begin_transaction(*args, **kwargs)
|
7
|
+
if open_transactions == 0
|
8
|
+
@active_record_query_counter_transaction_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
9
|
+
end
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def commit_transaction(*args)
|
14
|
+
if @active_record_query_counter_transaction_start_time && open_transactions == 1
|
15
|
+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
16
|
+
ActiveRecordQueryCounter.add_transaction(@active_record_query_counter_transaction_start_time, end_time)
|
17
|
+
@active_record_query_counter_transaction_start_time = nil
|
18
|
+
end
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def rollback_transaction(*args)
|
23
|
+
if @active_record_query_counter_transaction_start_time && open_transactions == 1
|
24
|
+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
25
|
+
ActiveRecordQueryCounter.add_transaction(@active_record_query_counter_transaction_start_time, end_time)
|
26
|
+
@active_record_query_counter_transaction_start_time = nil
|
27
|
+
end
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,8 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "active_record_query_counter/connection_adapter_extension"
|
4
|
+
require_relative "active_record_query_counter/counter"
|
5
|
+
require_relative "active_record_query_counter/rack_middleware"
|
6
|
+
require_relative "active_record_query_counter/sidekiq_middleware"
|
7
|
+
require_relative "active_record_query_counter/thresholds"
|
8
|
+
require_relative "active_record_query_counter/transaction_info"
|
9
|
+
require_relative "active_record_query_counter/transaction_manager_extension"
|
10
|
+
require_relative "active_record_query_counter/version"
|
11
|
+
|
3
12
|
# Everything you need to count ActiveRecord queries and row counts within a block.
|
4
13
|
#
|
5
|
-
#
|
14
|
+
# @example
|
6
15
|
#
|
7
16
|
# ActiveRecordQueryCounter.count_queries do
|
8
17
|
# yield
|
@@ -10,116 +19,205 @@
|
|
10
19
|
# puts ActiveRecordQueryCounter.row_count
|
11
20
|
# end
|
12
21
|
module ActiveRecordQueryCounter
|
13
|
-
|
14
|
-
|
15
|
-
attr_reader :transactions
|
16
|
-
|
17
|
-
def initialize
|
18
|
-
@query_count = 0
|
19
|
-
@row_count = 0
|
20
|
-
@query_time = 0.0
|
21
|
-
@transactions = {}
|
22
|
-
end
|
23
|
-
|
24
|
-
def transaction_count
|
25
|
-
@transactions.size
|
26
|
-
end
|
27
|
-
|
28
|
-
def transaction_time
|
29
|
-
@transactions.values.sum { |count, time| time }
|
30
|
-
end
|
31
|
-
end
|
22
|
+
IGNORED_STATEMENTS = %w[SCHEMA EXPLAIN].freeze
|
23
|
+
private_constant :IGNORED_STATEMENTS
|
32
24
|
|
33
25
|
class << self
|
34
26
|
# Enable query counting within a block.
|
27
|
+
#
|
28
|
+
# @return [Object] the result of the block
|
35
29
|
def count_queries
|
36
|
-
|
30
|
+
save_counter = current_counter
|
37
31
|
begin
|
38
|
-
|
39
|
-
|
32
|
+
counter = Counter.new
|
33
|
+
self.current_counter = counter
|
34
|
+
|
35
|
+
retval = yield
|
36
|
+
|
37
|
+
transaction_count = counter.transaction_count
|
38
|
+
if transaction_count > 0
|
39
|
+
transaction_threshold = (counter.thresholds.transaction_count || -1)
|
40
|
+
if transaction_threshold >= 0 && transaction_count >= transaction_threshold
|
41
|
+
send_notification("transaction_count", counter.first_transaction_start_time, counter.last_transaction_end_time, transactions: counter.transactions)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
retval
|
40
46
|
ensure
|
41
|
-
|
47
|
+
self.current_counter = save_counter
|
42
48
|
end
|
43
49
|
end
|
44
50
|
|
45
|
-
# Increment the query counters
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
51
|
+
# Increment the query counters.
|
52
|
+
#
|
53
|
+
# @param row_count [Integer] the number of rows returned by the query
|
54
|
+
# @param elapsed_time [Float] the time spent executing the query
|
55
|
+
# @return [void]
|
56
|
+
# @api private
|
57
|
+
def add_query(sql, name, binds, row_count, start_time, end_time)
|
58
|
+
return if IGNORED_STATEMENTS.include?(name)
|
59
|
+
|
60
|
+
counter = current_counter
|
61
|
+
return unless counter.is_a?(Counter)
|
62
|
+
|
63
|
+
elapsed_time = end_time - start_time
|
64
|
+
counter.query_count += 1
|
65
|
+
counter.row_count += row_count
|
66
|
+
counter.query_time += elapsed_time
|
67
|
+
|
68
|
+
trace = nil
|
69
|
+
query_time_threshold = (counter.thresholds.query_time || -1)
|
70
|
+
if query_time_threshold >= 0 && elapsed_time >= query_time_threshold
|
71
|
+
trace = backtrace
|
72
|
+
send_notification("query_time", start_time, end_time, sql: sql, binds: binds, row_count: row_count, trace: trace)
|
73
|
+
end
|
74
|
+
|
75
|
+
row_count_threshold = (counter.thresholds.row_count || -1)
|
76
|
+
if row_count_threshold >= 0 && row_count >= row_count_threshold
|
77
|
+
trace ||= backtrace
|
78
|
+
send_notification("row_count", start_time, end_time, sql: sql, binds: binds, row_count: row_count, trace: trace)
|
52
79
|
end
|
53
80
|
end
|
54
81
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
counter.transactions[trace] = info
|
72
|
-
end
|
82
|
+
# Increment the transaction counters.
|
83
|
+
#
|
84
|
+
# @param start_time [Float] the time the transaction started
|
85
|
+
# @param end_time [Float] the time the transaction ended
|
86
|
+
# @return [void]
|
87
|
+
# @api private
|
88
|
+
def add_transaction(start_time, end_time)
|
89
|
+
counter = current_counter
|
90
|
+
return unless counter.is_a?(Counter)
|
91
|
+
|
92
|
+
trace = backtrace
|
93
|
+
counter.add_transaction(trace: trace, start_time: start_time, end_time: end_time)
|
94
|
+
|
95
|
+
transaction_time_threshold = (counter.thresholds.transaction_time || -1)
|
96
|
+
if transaction_time_threshold >= 0 && end_time - start_time >= transaction_time_threshold
|
97
|
+
send_notification("transaction_time", start_time, end_time, trace: backtrace)
|
73
98
|
end
|
74
99
|
end
|
75
100
|
|
101
|
+
# Return the number of queries that have been counted within the current block.
|
102
|
+
# Returns nil if not inside a block where queries are being counted.
|
103
|
+
#
|
104
|
+
# @return [Integer, nil]
|
76
105
|
def query_count
|
77
|
-
counter =
|
106
|
+
counter = current_counter
|
78
107
|
counter.query_count if counter.is_a?(Counter)
|
79
108
|
end
|
80
109
|
|
110
|
+
# Return the number of queries that hit the query cache and were not sent to the database
|
111
|
+
# that have been counted within the current block. Returns nil if not inside a block where
|
112
|
+
# queries are being counted.
|
113
|
+
#
|
114
|
+
# @return [Integer, nil]
|
115
|
+
def cached_query_count
|
116
|
+
counter = current_counter
|
117
|
+
counter.cached_query_count if counter.is_a?(Counter)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Return the number of rows that have been counted within the current block.
|
121
|
+
# Returns nil if not inside a block where queries are being counted.
|
122
|
+
#
|
123
|
+
# @return [Integer, nil]
|
81
124
|
def row_count
|
82
|
-
counter =
|
125
|
+
counter = current_counter
|
83
126
|
counter.row_count if counter.is_a?(Counter)
|
84
127
|
end
|
85
128
|
|
129
|
+
# Return the total time spent executing queries within the current block.
|
130
|
+
# Returns nil if not inside a block where queries are being counted.
|
131
|
+
#
|
132
|
+
# @return [Float, nil]
|
86
133
|
def query_time
|
87
|
-
counter =
|
134
|
+
counter = current_counter
|
88
135
|
counter.query_time if counter.is_a?(Counter)
|
89
136
|
end
|
90
137
|
|
138
|
+
# Return the number of transactions that have been counted within the current block.
|
139
|
+
# Returns nil if not inside a block where queries are being counted.
|
140
|
+
#
|
141
|
+
# @return [Integer, nil]
|
91
142
|
def transaction_count
|
92
|
-
counter =
|
143
|
+
counter = current_counter
|
93
144
|
counter.transaction_count if counter.is_a?(Counter)
|
94
145
|
end
|
95
146
|
|
147
|
+
# Return the total time spent in transactions that have been counted within the current block.
|
148
|
+
# Returns nil if not inside a block where queries are being counted.
|
149
|
+
#
|
150
|
+
# @return [Float, nil]
|
96
151
|
def transaction_time
|
97
|
-
counter =
|
152
|
+
counter = current_counter
|
98
153
|
counter.transaction_time if counter.is_a?(Counter)
|
99
154
|
end
|
100
155
|
|
156
|
+
# Return the time when the first transaction began within the current block.
|
157
|
+
# Returns nil if not inside a block where queries are being counted or there are no transactions.
|
158
|
+
#
|
159
|
+
# @return [Float, nil] the monotonic time when the first transaction began,
|
160
|
+
def first_transaction_start_time
|
161
|
+
counter = current_counter
|
162
|
+
counter.first_transaction_start_time if counter.is_a?(Counter)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Return the time when the last transaction ended within the current block.
|
166
|
+
# Returns nil if not inside a block where queries are being counted or there are no transactions.
|
167
|
+
#
|
168
|
+
# @return [Float, nil] the monotonic time when the last transaction ended,
|
169
|
+
def last_transaction_end_time
|
170
|
+
counter = current_counter
|
171
|
+
counter.transactions.last&.end_time if counter.is_a?(Counter)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Return an array of transaction information for any transactions that have been counted
|
175
|
+
# within the current block. Returns nil if not inside a block where queries are being counted.
|
176
|
+
#
|
177
|
+
# @return [Array<ActiveRecordQueryCounter::TransactionInfo>, nil]
|
101
178
|
def transactions
|
102
|
-
counter =
|
103
|
-
counter.transactions
|
179
|
+
counter = current_counter
|
180
|
+
counter.transactions if counter.is_a?(Counter)
|
104
181
|
end
|
105
182
|
|
106
183
|
# Return the query info as a hash with keys :query_count, :row_count, :query_time
|
107
184
|
# :transaction_count, and :transaction_type or nil if not inside a block where queries
|
108
185
|
# are being counted.
|
186
|
+
#
|
187
|
+
# @return [Hash, nil]
|
109
188
|
def info
|
110
|
-
counter =
|
189
|
+
counter = current_counter
|
111
190
|
if counter.is_a?(Counter)
|
112
191
|
{
|
113
192
|
query_count: counter.query_count,
|
114
193
|
row_count: counter.row_count,
|
115
194
|
query_time: counter.query_time,
|
195
|
+
cached_query_count: counter.cached_query_count,
|
196
|
+
cache_hit_rate: counter.cache_hit_rate,
|
116
197
|
transaction_count: counter.transaction_count,
|
117
198
|
transaction_time: counter.transaction_time
|
118
199
|
}
|
119
200
|
end
|
120
201
|
end
|
121
202
|
|
203
|
+
# The global notification thresholds for sending notifications. The values set in these
|
204
|
+
# thresholds are used as the default values.
|
205
|
+
#
|
206
|
+
# @return [ActiveRecordQueryCounter::Thresholds]
|
207
|
+
def default_thresholds
|
208
|
+
@default_thresholds ||= Thresholds.new
|
209
|
+
end
|
210
|
+
|
211
|
+
# Get the current local notification thresholds. These thresholds are only used within
|
212
|
+
# the current `count_queries` block.
|
213
|
+
def thresholds
|
214
|
+
current_counter&.thresholds || default_thresholds.dup
|
215
|
+
end
|
216
|
+
|
122
217
|
# Enable the query counting behavior on a connection adapter class.
|
218
|
+
#
|
219
|
+
# @param connection_class [Class] the connection adapter class to extend
|
220
|
+
# @return [void]
|
123
221
|
def enable!(connection_class)
|
124
222
|
unless connection_class.include?(ConnectionAdapterExtension)
|
125
223
|
connection_class.prepend(ConnectionAdapterExtension)
|
@@ -127,61 +225,32 @@ module ActiveRecordQueryCounter
|
|
127
225
|
unless ActiveRecord::ConnectionAdapters::TransactionManager.include?(TransactionManagerExtension)
|
128
226
|
ActiveRecord::ConnectionAdapters::TransactionManager.prepend(TransactionManagerExtension)
|
129
227
|
end
|
130
|
-
end
|
131
|
-
end
|
132
228
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
if result.is_a?(ActiveRecord::Result)
|
139
|
-
ActiveRecordQueryCounter.increment(result.length, Time.now - start_time)
|
229
|
+
@cache_subscription ||= ActiveSupport::Notifications.subscribe("sql.active_record") do |_name, _start_time, _end_time, _id, payload|
|
230
|
+
if payload[:cached]
|
231
|
+
counter = current_counter
|
232
|
+
counter.cached_query_count += 1 if counter
|
233
|
+
end
|
140
234
|
end
|
141
|
-
result
|
142
235
|
end
|
143
|
-
end
|
144
236
|
|
145
|
-
|
146
|
-
def begin_transaction(*args, **kwargs)
|
147
|
-
if open_transactions == 0
|
148
|
-
@active_record_query_counter_transaction_start_time = Time.current
|
149
|
-
end
|
150
|
-
super
|
151
|
-
end
|
237
|
+
private
|
152
238
|
|
153
|
-
def
|
154
|
-
|
155
|
-
ActiveRecordQueryCounter.increment_transaction(Time.current - @active_record_query_counter_transaction_start_time)
|
156
|
-
@active_record_query_counter_transaction_start_time = nil
|
157
|
-
end
|
158
|
-
super
|
239
|
+
def current_counter
|
240
|
+
Thread.current[:active_record_query_counter]
|
159
241
|
end
|
160
242
|
|
161
|
-
def
|
162
|
-
|
163
|
-
ActiveRecordQueryCounter.increment_transaction(Time.current - @active_record_query_counter_transaction_start_time)
|
164
|
-
@active_record_query_counter_transaction_start_time = nil
|
165
|
-
end
|
166
|
-
super
|
243
|
+
def current_counter=(counter)
|
244
|
+
Thread.current[:active_record_query_counter] = counter
|
167
245
|
end
|
168
|
-
end
|
169
246
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
@app = app
|
247
|
+
def send_notification(name, start_time, end_time, payload = {})
|
248
|
+
id = "#{name}-#{SecureRandom.hex}"
|
249
|
+
ActiveSupport::Notifications.publish("active_record_query_counter.#{name}", start_time, end_time, id, payload)
|
174
250
|
end
|
175
251
|
|
176
|
-
def
|
177
|
-
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
# Sidekiq middleware to count queries on a job.
|
182
|
-
class SidekiqMiddleware
|
183
|
-
def call(worker, job, queue, &block)
|
184
|
-
ActiveRecordQueryCounter.count_queries(&block)
|
252
|
+
def backtrace
|
253
|
+
caller.reject { |line| line.start_with?(__dir__) }
|
185
254
|
end
|
186
255
|
end
|
187
256
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_query_counter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Durand
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '5.
|
19
|
+
version: '5.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '5.
|
26
|
+
version: '5.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,27 +38,32 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
-
description:
|
41
|
+
description:
|
42
42
|
email:
|
43
43
|
- bbdurand@gmail.com
|
44
44
|
executables: []
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
|
-
- ".github/dependabot.yml"
|
49
|
-
- ".github/workflows/continuous_integration.yml"
|
50
|
-
- ".standard.yml"
|
51
48
|
- CHANGELOG.md
|
52
49
|
- MIT_LICENSE
|
53
50
|
- README.md
|
54
51
|
- VERSION
|
55
52
|
- active_record_query_counter.gemspec
|
56
53
|
- lib/active_record_query_counter.rb
|
54
|
+
- lib/active_record_query_counter/connection_adapter_extension.rb
|
55
|
+
- lib/active_record_query_counter/counter.rb
|
56
|
+
- lib/active_record_query_counter/rack_middleware.rb
|
57
|
+
- lib/active_record_query_counter/sidekiq_middleware.rb
|
58
|
+
- lib/active_record_query_counter/thresholds.rb
|
59
|
+
- 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
|
57
62
|
homepage: https://github.com/bdurand/active_record_query_counter
|
58
63
|
licenses:
|
59
64
|
- MIT
|
60
65
|
metadata: {}
|
61
|
-
post_install_message:
|
66
|
+
post_install_message:
|
62
67
|
rdoc_options: []
|
63
68
|
require_paths:
|
64
69
|
- lib
|
@@ -66,15 +71,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
66
71
|
requirements:
|
67
72
|
- - ">="
|
68
73
|
- !ruby/object:Gem::Version
|
69
|
-
version: '
|
74
|
+
version: '2.5'
|
70
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
76
|
requirements:
|
72
77
|
- - ">="
|
73
78
|
- !ruby/object:Gem::Version
|
74
79
|
version: '0'
|
75
80
|
requirements: []
|
76
|
-
rubygems_version: 3.
|
77
|
-
signing_key:
|
81
|
+
rubygems_version: 3.4.10
|
82
|
+
signing_key:
|
78
83
|
specification_version: 4
|
79
84
|
summary: Count total number of ActiveRecord queries and row counts inside a block
|
80
85
|
test_files: []
|
data/.github/dependabot.yml
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
# Dependabot update strategy
|
2
|
-
version: 2
|
3
|
-
updates:
|
4
|
-
- package-ecosystem: bundler
|
5
|
-
directory: "/"
|
6
|
-
schedule:
|
7
|
-
interval: daily
|
8
|
-
allow:
|
9
|
-
# Automatically keep all runtime dependencies updated
|
10
|
-
- dependency-name: "*"
|
11
|
-
dependency-type: "production"
|
12
|
-
versioning-strategy: lockfile-only
|
@@ -1,73 +0,0 @@
|
|
1
|
-
name: Continuous Integration
|
2
|
-
on:
|
3
|
-
push:
|
4
|
-
branches:
|
5
|
-
- master
|
6
|
-
- actions-*
|
7
|
-
tags:
|
8
|
-
- v*
|
9
|
-
pull_request:
|
10
|
-
env:
|
11
|
-
BUNDLE_CLEAN: "true"
|
12
|
-
BUNDLE_PATH: vendor/bundle
|
13
|
-
BUNDLE_JOBS: 3
|
14
|
-
BUNDLE_RETRY: 3
|
15
|
-
CC_TEST_REPORTER_ID: 8f360e59f6cb98313ccbb27fecf82f667455acec33cf23ae115c2db121852700
|
16
|
-
jobs:
|
17
|
-
specs:
|
18
|
-
name: ${{ matrix.job }} ruby-${{ matrix.combo.ruby || matrix.ruby }} ${{ matrix.combo.activerecord && format('activerecord-{0}', matrix.combo.activerecord) }}
|
19
|
-
runs-on: ubuntu-latest
|
20
|
-
strategy:
|
21
|
-
fail-fast: false
|
22
|
-
matrix:
|
23
|
-
combo:
|
24
|
-
- activerecord: "latest"
|
25
|
-
ruby: "3.0"
|
26
|
-
coverage: true
|
27
|
-
- activerecord: "6.1"
|
28
|
-
ruby: "3.0"
|
29
|
-
- activerecord: "6.0"
|
30
|
-
ruby: "2.7"
|
31
|
-
- activerecord: "5.2"
|
32
|
-
ruby: "2.6"
|
33
|
-
- activerecord: "5.1"
|
34
|
-
ruby: "2.6"
|
35
|
-
- activerecord: "5.0"
|
36
|
-
ruby: "2.5"
|
37
|
-
job: [ rspec ]
|
38
|
-
include:
|
39
|
-
- ruby: "3.0"
|
40
|
-
activerecord: "latest"
|
41
|
-
job: rspec
|
42
|
-
- ruby: "2.7"
|
43
|
-
activerecord: "latest"
|
44
|
-
job: standardrb
|
45
|
-
steps:
|
46
|
-
- name: checkout
|
47
|
-
uses: actions/checkout@v2
|
48
|
-
- name: set up Ruby
|
49
|
-
uses: ruby/setup-ruby@v1
|
50
|
-
with:
|
51
|
-
ruby-version: ${{ matrix.combo.ruby || matrix.ruby }}
|
52
|
-
- name: setup bundler
|
53
|
-
run: |
|
54
|
-
if [ "${{ matrix.combo.bundler }}" != "" ]; then
|
55
|
-
gem uninstall bundler --all
|
56
|
-
gem install bundler --no-document --version ${{ matrix.combo.bundler }}
|
57
|
-
fi
|
58
|
-
if [ "${{ matrix.combo.activerecord || matrix.activerecord }}" != "latest" ]; then
|
59
|
-
echo "using gemfile gemfiles/activerecord_${{ matrix.combo.activerecord || matrix.activerecord }}.gemfile"
|
60
|
-
bundle config set gemfile "gemfiles/activerecord_${{ matrix.combo.activerecord || matrix.activerecord }}.gemfile"
|
61
|
-
fi
|
62
|
-
bundle update
|
63
|
-
- name: install dependencies
|
64
|
-
run: bundle install
|
65
|
-
- name: specs
|
66
|
-
if: matrix.job == 'rspec'
|
67
|
-
run: bundle exec rake spec
|
68
|
-
- name: code coverage
|
69
|
-
if: matrix.coverage == true
|
70
|
-
uses: paambaati/codeclimate-action@v2.7.5
|
71
|
-
- name: standardrb
|
72
|
-
if: matrix.job == 'standardrb'
|
73
|
-
run: bundle exec rake standard
|