n_one 1.0.0 → 1.0.3
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/.github/workflows/ci.yml +1 -1
- data/Gemfile.lock +5 -2
- data/README.md +43 -2
- data/lib/n_one/runner.rb +17 -5
- data/lib/n_one/version.rb +1 -1
- data/lib/n_one.rb +4 -4
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1d2ff9afa1af0f84f3291494c21e47d28c94d67c61abea2b9a10bba5482c1ed8
|
|
4
|
+
data.tar.gz: 2e50029cb362fbcd9f773d7dd5e4d86120a9cef425b0a167b13d659441de57fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b5585f38769f8e3a5228111d01a0b894217197f8597cc608e70b0d3d6fa35cf2047f79e3eb71c86a4a2a3a300eea4655a59e090868d4fe51008b9e175294d29
|
|
7
|
+
data.tar.gz: 78718a6479979500f2e6466bbbb25d60a122c0b5ea3199d86b39a212e1b80ba1ee423778b2df14ccc8eb36e270b76e48306a9bd01f7e86b0147c141681ba872e
|
data/.github/workflows/ci.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
n_one (1.0.
|
|
4
|
+
n_one (1.0.3)
|
|
5
5
|
pg_query
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -23,6 +23,8 @@ GEM
|
|
|
23
23
|
concurrent-ruby (1.1.8)
|
|
24
24
|
factory_bot (6.1.0)
|
|
25
25
|
activesupport (>= 5.0.0)
|
|
26
|
+
google-protobuf (3.19.4)
|
|
27
|
+
google-protobuf (3.19.4-x86_64-linux)
|
|
26
28
|
i18n (1.8.9)
|
|
27
29
|
concurrent-ruby (~> 1.0)
|
|
28
30
|
method_source (1.0.0)
|
|
@@ -30,7 +32,8 @@ GEM
|
|
|
30
32
|
parallel (1.20.1)
|
|
31
33
|
parser (3.0.1.0)
|
|
32
34
|
ast (~> 2.4.1)
|
|
33
|
-
pg_query (1.3
|
|
35
|
+
pg_query (2.1.3)
|
|
36
|
+
google-protobuf (>= 3.19.2)
|
|
34
37
|
pry (0.14.0)
|
|
35
38
|
coderay (~> 1.1)
|
|
36
39
|
method_source (~> 1.0)
|
data/README.md
CHANGED
|
@@ -74,11 +74,52 @@ end
|
|
|
74
74
|
Ignore notifications for call stacks containing one or more substrings:
|
|
75
75
|
|
|
76
76
|
```ruby
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
NOne.scan!(whitelist: ['myapp/lib/known_n_plus_ones/']) do
|
|
78
|
+
example.run
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Ignore names
|
|
83
|
+
|
|
84
|
+
Ignore queries with names:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
NOne.scan!(ignore_names: ['SCHEMA']) do
|
|
88
|
+
example.run
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
It will skip schema queries(e.g. for column names of a given table)
|
|
93
|
+
|
|
94
|
+
## Stack trace sanitizing
|
|
95
|
+
|
|
96
|
+
Sanitize the call stack trace that is used to calculate the query fingerprint:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
sanitizer = lambda do |stacktrace|
|
|
100
|
+
stacktrace.reject { |s| s.include?('/active_record/relation/delegation.rb') }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
NOne.scan!(stacktrace_sanitizer: sanitizer) do
|
|
104
|
+
example.run
|
|
105
|
+
end
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Consider the following example:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
class Foo < ActiveRecord::Base
|
|
112
|
+
def self.bar
|
|
113
|
+
first(5)
|
|
79
114
|
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
2.times { Foo.all.bar }
|
|
80
118
|
```
|
|
81
119
|
|
|
120
|
+
The subsequent `Foo.all.bar` call here will not be recognized as an N+1 query since it will have a different call stack trace (see the reason [here](https://github.com/rails/rails/blob/9a400d808bdbebd5ea50cebc79bde591d2669017/activerecord/lib/active_record/relation/delegation.rb#L82-L85)).
|
|
121
|
+
This can be fixed with the `stacktrace_sanitizer` option as described above.
|
|
122
|
+
|
|
82
123
|
## Contributing
|
|
83
124
|
|
|
84
125
|
Bug reports and pull requests are welcome on GitHub at https://github.com/prikha/n_one.
|
data/lib/n_one/runner.rb
CHANGED
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module NOne
|
|
4
4
|
class Runner # :nodoc:
|
|
5
|
-
|
|
5
|
+
# Instantiation
|
|
6
|
+
#
|
|
7
|
+
# @param whitelist [<String>] frame substrings to be ignored
|
|
8
|
+
# @param ignore_names [<String>] query names to be ignored
|
|
9
|
+
# @param stacktrace_sanitizer [Proc<Array<String>>] if given, used to filter the stack trace
|
|
10
|
+
# that is used to calculate the location key
|
|
11
|
+
#
|
|
12
|
+
def initialize(whitelist: [], ignore_names: [], stacktrace_sanitizer: nil)
|
|
6
13
|
@whitelist = ['active_record/validations/uniqueness'] + whitelist
|
|
14
|
+
@ignore_names = ignore_names
|
|
15
|
+
@stacktrace_sanitizer = stacktrace_sanitizer
|
|
7
16
|
end
|
|
8
17
|
|
|
9
18
|
def scan(&block)
|
|
@@ -20,7 +29,7 @@ module NOne
|
|
|
20
29
|
|
|
21
30
|
private
|
|
22
31
|
|
|
23
|
-
attr_reader :store, :whitelist
|
|
32
|
+
attr_reader :store, :whitelist, :ignore_names
|
|
24
33
|
|
|
25
34
|
def init_store
|
|
26
35
|
@store = {}
|
|
@@ -45,17 +54,20 @@ module NOne
|
|
|
45
54
|
end.compact
|
|
46
55
|
end
|
|
47
56
|
|
|
48
|
-
def record_sql(&block) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
57
|
+
def record_sql(&block) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
|
|
49
58
|
subscriber = ActiveSupport::Notifications.subscribe 'sql.active_record' do |_, _, _, _, data|
|
|
50
59
|
sql = data[:sql]
|
|
51
60
|
cached = data[:cached]
|
|
52
61
|
|
|
53
62
|
next if !sql.include?('SELECT') || cached
|
|
63
|
+
next if ignore_names.include?(data[:name])
|
|
54
64
|
|
|
55
65
|
sql_fingerprint = Query.fingerprint(sql)
|
|
56
66
|
next unless sql_fingerprint
|
|
57
67
|
|
|
58
|
-
|
|
68
|
+
stacktrace = caller
|
|
69
|
+
stacktrace = @stacktrace_sanitizer.call(stacktrace) if @stacktrace_sanitizer
|
|
70
|
+
location_key = Digest::SHA1.hexdigest(stacktrace.join)
|
|
59
71
|
|
|
60
72
|
store["#{sql_fingerprint}_#{location_key}"] ||= {
|
|
61
73
|
count: 0,
|
|
@@ -66,7 +78,7 @@ module NOne
|
|
|
66
78
|
store["#{sql_fingerprint}_#{location_key}"][:count] += 1
|
|
67
79
|
store["#{sql_fingerprint}_#{location_key}"][:sql] << sql
|
|
68
80
|
store["#{sql_fingerprint}_#{location_key}"][:sql].uniq!
|
|
69
|
-
store["#{sql_fingerprint}_#{location_key}"][:caller] ||=
|
|
81
|
+
store["#{sql_fingerprint}_#{location_key}"][:caller] ||= stacktrace
|
|
70
82
|
end
|
|
71
83
|
|
|
72
84
|
block.call
|
data/lib/n_one/version.rb
CHANGED
data/lib/n_one.rb
CHANGED
|
@@ -30,11 +30,11 @@ module NOne
|
|
|
30
30
|
|
|
31
31
|
module_function
|
|
32
32
|
|
|
33
|
-
def scan(
|
|
34
|
-
Runner.new(
|
|
33
|
+
def scan(**args, &block)
|
|
34
|
+
Runner.new(**args).scan(&block)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def scan!(
|
|
38
|
-
Runner.new(
|
|
37
|
+
def scan!(**args, &block)
|
|
38
|
+
Runner.new(**args).scan!(&block)
|
|
39
39
|
end
|
|
40
40
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: n_one
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sergey Prikhodko
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-02-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: pg_query
|
|
@@ -66,7 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '0'
|
|
68
68
|
requirements: []
|
|
69
|
-
rubygems_version: 3.1
|
|
69
|
+
rubygems_version: 3.0.3.1
|
|
70
70
|
signing_key:
|
|
71
71
|
specification_version: 4
|
|
72
72
|
summary: N+1 auto-detection for ActiveRecord and PostgreSQL
|