prosopite 0.1.5 → 1.0.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/Gemfile +1 -0
- data/Gemfile.lock +2 -2
- data/LICENSE.txt +1 -1
- data/README.md +16 -8
- data/lib/prosopite.rb +38 -25
- data/lib/prosopite/version.rb +1 -1
- data/prosopite.gemspec +0 -2
- metadata +2 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3df6a0299972911e48ea44330995864e2cd2191ec66c199296ba324a5fa8e639
|
4
|
+
data.tar.gz: 4f9ee347a8cf52fd742ee66586c7e4b601c8436a4bcabe1b1afdef5e09293f44
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 668bc7e58bcabd77231adc831054d56ca572e1e9857f87736d1fd06048c1eeaac9c0c0ab970c6ed6f8ce70670ae51277c3c4f56012d0ec61240c0e728c1f3795
|
7
|
+
data.tar.gz: '0341920f4f1beddc2b9c46553f9284724ecddfe50f30579a7cc9627c93fdf2d3c714bb57fd453dd7a4783a86951b9dd2cb0a79e721189bce3b89eda0c119c8bf'
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
prosopite (0.1
|
5
|
-
pg_query (~> 1.3)
|
4
|
+
prosopite (0.2.1)
|
6
5
|
|
7
6
|
GEM
|
8
7
|
remote: https://rubygems.org/
|
@@ -44,6 +43,7 @@ DEPENDENCIES
|
|
44
43
|
activerecord
|
45
44
|
factory_bot
|
46
45
|
minitest
|
46
|
+
pg_query
|
47
47
|
prosopite!
|
48
48
|
pry
|
49
49
|
rake (~> 13.0)
|
data/LICENSE.txt
CHANGED
@@ -187,7 +187,7 @@
|
|
187
187
|
same "printed page" as the copyright notice for easier
|
188
188
|
identification within third-party archives.
|
189
189
|
|
190
|
-
Copyright
|
190
|
+
Copyright 2021 Mpampis Kostas
|
191
191
|
|
192
192
|
Licensed under the Apache License, Version 2.0 (the "License");
|
193
193
|
you may not use this file except in compliance with the License.
|
data/README.md
CHANGED
@@ -97,6 +97,12 @@ Add this line to your application's Gemfile:
|
|
97
97
|
gem 'prosopite'
|
98
98
|
```
|
99
99
|
|
100
|
+
If you're **not** using MySQL/MariaDB, you should also add:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
gem 'pg_query'
|
104
|
+
```
|
105
|
+
|
100
106
|
And then execute:
|
101
107
|
|
102
108
|
$ bundle install
|
@@ -120,12 +126,14 @@ Prosopite auto-detection can be enabled on all controllers:
|
|
120
126
|
|
121
127
|
```ruby
|
122
128
|
class ApplicationController < ActionController::Base
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
+
unless Rails.env.production?
|
130
|
+
before_action do
|
131
|
+
Prosopite.scan
|
132
|
+
end
|
133
|
+
|
134
|
+
after_action do
|
135
|
+
Prosopite.finish
|
136
|
+
end
|
129
137
|
end
|
130
138
|
end
|
131
139
|
```
|
@@ -168,12 +176,12 @@ end
|
|
168
176
|
|
169
177
|
WARNING: scan/finish should run before/after **each** test and NOT before/after the whole suite.
|
170
178
|
|
171
|
-
##
|
179
|
+
## Allow list
|
172
180
|
|
173
181
|
Ignore notifications for call stacks containing one or more substrings:
|
174
182
|
|
175
183
|
```ruby
|
176
|
-
Prosopite.
|
184
|
+
Prosopite.allow_list = ['substring_in_call_stack']
|
177
185
|
```
|
178
186
|
|
179
187
|
## Scanning code outside controllers or tests
|
data/lib/prosopite.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'pg_query'
|
2
1
|
|
3
2
|
module Prosopite
|
4
3
|
class NPlusOneQueriesError < StandardError; end
|
@@ -7,42 +6,46 @@ module Prosopite
|
|
7
6
|
:stderr_logger,
|
8
7
|
:rails_logger,
|
9
8
|
:prosopite_logger,
|
10
|
-
:
|
9
|
+
:allow_list
|
11
10
|
|
12
11
|
def scan
|
13
|
-
|
12
|
+
tc[:prosopite_scan] ||= false
|
14
13
|
return if scan?
|
15
14
|
|
16
15
|
subscribe
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
tc[:prosopite_query_counter] = Hash.new(0)
|
18
|
+
tc[:prosopite_query_holder] = Hash.new { |h, k| h[k] = [] }
|
19
|
+
tc[:prosopite_query_caller] = {}
|
21
20
|
|
22
|
-
@
|
21
|
+
@allow_list ||= []
|
23
22
|
|
24
|
-
|
23
|
+
tc[:prosopite_scan] = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def tc
|
27
|
+
Thread.current
|
25
28
|
end
|
26
29
|
|
27
30
|
def scan?
|
28
|
-
|
31
|
+
tc[:prosopite_scan]
|
29
32
|
end
|
30
33
|
|
31
34
|
def finish
|
32
35
|
return unless scan?
|
33
36
|
|
34
|
-
|
37
|
+
tc[:prosopite_scan] = false
|
35
38
|
|
36
39
|
create_notifications
|
37
|
-
send_notifications if
|
40
|
+
send_notifications if tc[:prosopite_notifications].present?
|
38
41
|
end
|
39
42
|
|
40
43
|
def create_notifications
|
41
|
-
|
44
|
+
tc[:prosopite_notifications] = {}
|
42
45
|
|
43
|
-
|
46
|
+
tc[:prosopite_query_counter].each do |location_key, count|
|
44
47
|
if count > 1
|
45
|
-
fingerprints =
|
48
|
+
fingerprints = tc[:prosopite_query_holder][location_key].map do |q|
|
46
49
|
begin
|
47
50
|
fingerprint(q)
|
48
51
|
rescue
|
@@ -50,13 +53,13 @@ module Prosopite
|
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
53
|
-
kaller =
|
56
|
+
kaller = tc[:prosopite_query_caller][location_key]
|
54
57
|
|
55
|
-
if fingerprints.uniq.size == 1 && !kaller.any? { |f| @
|
56
|
-
queries =
|
58
|
+
if fingerprints.uniq.size == 1 && !kaller.any? { |f| @allow_list.any? { |s| f.include?(s) } }
|
59
|
+
queries = tc[:prosopite_query_holder][location_key]
|
57
60
|
|
58
61
|
unless kaller.any? { |f| f.include?('active_record/validations/uniqueness') }
|
59
|
-
|
62
|
+
tc[:prosopite_notifications][queries] = kaller
|
60
63
|
end
|
61
64
|
end
|
62
65
|
end
|
@@ -67,6 +70,12 @@ module Prosopite
|
|
67
70
|
if ActiveRecord::Base.connection.adapter_name.downcase.include?('mysql')
|
68
71
|
mysql_fingerprint(query)
|
69
72
|
else
|
73
|
+
begin
|
74
|
+
require 'pg_query'
|
75
|
+
rescue LoadError => e
|
76
|
+
msg = "Could not load the 'pg_query' gem. Add `gem 'pg_query'` to your Gemfile"
|
77
|
+
raise LoadError, msg, e.backtrace
|
78
|
+
end
|
70
79
|
PgQuery.fingerprint(query)
|
71
80
|
end
|
72
81
|
end
|
@@ -126,7 +135,7 @@ module Prosopite
|
|
126
135
|
|
127
136
|
notifications_str = ''
|
128
137
|
|
129
|
-
|
138
|
+
tc[:prosopite_notifications].each do |queries, kaller|
|
130
139
|
notifications_str << "N+1 queries detected:\n"
|
131
140
|
queries.each { |q| notifications_str << " #{q}\n" }
|
132
141
|
notifications_str << "Call stack:\n"
|
@@ -136,8 +145,8 @@ module Prosopite
|
|
136
145
|
notifications_str << "\n"
|
137
146
|
end
|
138
147
|
|
139
|
-
Rails.logger.warn(notifications_str) if @rails_logger
|
140
|
-
$stderr.puts(notifications_str) if @stderr_logger
|
148
|
+
Rails.logger.warn(red(notifications_str)) if @rails_logger
|
149
|
+
$stderr.puts(red(notifications_str)) if @stderr_logger
|
141
150
|
|
142
151
|
if @prosopite_logger
|
143
152
|
File.open(File.join(Rails.root, 'log', 'prosopite.log'), 'a') do |f|
|
@@ -148,6 +157,10 @@ module Prosopite
|
|
148
157
|
raise NPlusOneQueriesError.new(notifications_str) if @raise
|
149
158
|
end
|
150
159
|
|
160
|
+
def red(str)
|
161
|
+
str.split("\n").map { |line| "\e[91m#{line}\e[0m" }.join("\n")
|
162
|
+
end
|
163
|
+
|
151
164
|
def subscribe
|
152
165
|
@subscribed ||= false
|
153
166
|
return if @subscribed
|
@@ -158,11 +171,11 @@ module Prosopite
|
|
158
171
|
if scan? && sql.include?('SELECT') && data[:cached].nil?
|
159
172
|
location_key = Digest::SHA1.hexdigest(caller.join)
|
160
173
|
|
161
|
-
|
162
|
-
|
174
|
+
tc[:prosopite_query_counter][location_key] += 1
|
175
|
+
tc[:prosopite_query_holder][location_key] << sql
|
163
176
|
|
164
|
-
if
|
165
|
-
|
177
|
+
if tc[:prosopite_query_counter][location_key] > 1
|
178
|
+
tc[:prosopite_query_caller][location_key] = caller.dup
|
166
179
|
end
|
167
180
|
end
|
168
181
|
end
|
data/lib/prosopite/version.rb
CHANGED
data/prosopite.gemspec
CHANGED
@@ -24,8 +24,6 @@ Gem::Specification.new do |spec|
|
|
24
24
|
end
|
25
25
|
spec.require_paths = ["lib"]
|
26
26
|
|
27
|
-
spec.add_runtime_dependency 'pg_query', '~> 1.3'
|
28
|
-
|
29
27
|
spec.add_development_dependency "pry"
|
30
28
|
spec.add_development_dependency "minitest"
|
31
29
|
spec.add_development_dependency "factory_bot"
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prosopite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mpampis Kostas
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-03-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: pg_query
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.3'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.3'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: pry
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|