prosopite 0.1.3 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c9deb860379923d4dc495bb4f24bc44932a8be7d633be9ea59435da7ea7f51f
4
- data.tar.gz: 4598734ccd6d01208847d97ac03f5bfe570c5121a3d36c6b1746b4f3ab1b455d
3
+ metadata.gz: 56809d2470b99359e6a6399d6814fa430ef1f3e57df935468b23a0c00125dc87
4
+ data.tar.gz: 85bbe7fe09890e1854ca002609af8dba88065bf33b6ac95be3f6bbefb99b3ff8
5
5
  SHA512:
6
- metadata.gz: a7f1d7d7a9a5607e5d5ea73f734b3ae62a250a49f149daa2a04bf693cfd1072cb4fb3922d97f268b65116db7ad76d629430ce1dfcc792ba9dec5e8185b0440ac
7
- data.tar.gz: b67c9e0c61ee1a5858c00ba666521699e69efb3866fccc770482e31a04a57a01661dd4e52dac5229b922461aa1f1d0fdff156c7d1ad8ad8b1b9f17d011748042
6
+ metadata.gz: d78f71f85256a83f4ca2ee94b4afed69f6b661a9af35b920acee889a7a795c9c360f55ad2dcd4b87bf4403fc6eef832916cb67d27af852fa7e14dc18c3533b44
7
+ data.tar.gz: 89b90cb2c449fc40ce2d09787ec2fa92791633c1474dfd7a5dad472de8ca087dc010c878b8cc26290e9b240feab6b508cf35fcde5cc92e741826dbd8b2b1e62d
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- prosopite (0.1.3)
5
- pg_query (~> 1.3.0)
4
+ prosopite (0.2.0)
5
+ pg_query (~> 1.3)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
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 [yyyy] [name of copyright owner]
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
@@ -77,6 +77,11 @@ Leg::Design.last(20) do |l|
77
77
  end
78
78
  ```
79
79
 
80
+ ## Why a new gem
81
+
82
+ Creating a new gem makes more sense since bullet's core mechanism is completely
83
+ different from prosopite's.
84
+
80
85
  ## How it works
81
86
 
82
87
  Prosopite monitors all SQL queries using the Active Support instrumentation
@@ -115,12 +120,14 @@ Prosopite auto-detection can be enabled on all controllers:
115
120
 
116
121
  ```ruby
117
122
  class ApplicationController < ActionController::Base
118
- before_action do
119
- Prosopite.scan
120
- end
121
-
122
- after_action do
123
- Prosopite.finish
123
+ unless Rails.env.production?
124
+ before_action do
125
+ Prosopite.scan
126
+ end
127
+
128
+ after_action do
129
+ Prosopite.finish
130
+ end
124
131
  end
125
132
  end
126
133
  ```
@@ -152,21 +159,23 @@ And each test can be scanned with:
152
159
  ```ruby
153
160
  # spec/spec_helper.rb
154
161
 
155
- config.before do
162
+ config.before(:each) do
156
163
  Prosopite.scan
157
164
  end
158
165
 
159
- config.after do
166
+ config.after(:each) do
160
167
  Prosopite.finish
161
168
  end
162
169
  ```
163
170
 
164
- ## Whitelisting
171
+ WARNING: scan/finish should run before/after **each** test and NOT before/after the whole suite.
172
+
173
+ ## Allow list
165
174
 
166
175
  Ignore notifications for call stacks containing one or more substrings:
167
176
 
168
177
  ```ruby
169
- Prosopite.whitelist = ['substring_in_call_stack']
178
+ Prosopite.allow_list = ['substring_in_call_stack']
170
179
  ```
171
180
 
172
181
  ## Scanning code outside controllers or tests
data/lib/prosopite.rb CHANGED
@@ -7,32 +7,46 @@ module Prosopite
7
7
  :stderr_logger,
8
8
  :rails_logger,
9
9
  :prosopite_logger,
10
- :whitelist
10
+ :allow_list
11
11
 
12
12
  def scan
13
+ tc[:prosopite_scan] ||= false
13
14
  return if scan?
15
+
14
16
  subscribe
15
17
 
16
- @query_counter = Hash.new(0)
17
- @query_holder = Hash.new { |h, k| h[k] = [] }
18
- @query_caller = {}
18
+ tc[:prosopite_query_counter] = Hash.new(0)
19
+ tc[:prosopite_query_holder] = Hash.new { |h, k| h[k] = [] }
20
+ tc[:prosopite_query_caller] = {}
21
+
22
+ @allow_list ||= []
19
23
 
20
- @whitelist ||= []
21
- @scan = true
24
+ tc[:prosopite_scan] = true
25
+ end
26
+
27
+ def tc
28
+ Thread.current
22
29
  end
23
30
 
24
31
  def scan?
25
- @scan
32
+ tc[:prosopite_scan]
26
33
  end
27
34
 
28
35
  def finish
29
36
  return unless scan?
30
37
 
31
- @notifications = {}
38
+ tc[:prosopite_scan] = false
39
+
40
+ create_notifications
41
+ send_notifications if tc[:prosopite_notifications].present?
42
+ end
43
+
44
+ def create_notifications
45
+ tc[:prosopite_notifications] = {}
32
46
 
33
- @query_counter.each do |location_key, count|
47
+ tc[:prosopite_query_counter].each do |location_key, count|
34
48
  if count > 1
35
- fingerprints = @query_holder[location_key].map do |q|
49
+ fingerprints = tc[:prosopite_query_holder][location_key].map do |q|
36
50
  begin
37
51
  fingerprint(q)
38
52
  rescue
@@ -40,30 +54,31 @@ module Prosopite
40
54
  end
41
55
  end
42
56
 
43
- kaller = @query_caller[location_key]
57
+ kaller = tc[:prosopite_query_caller][location_key]
44
58
 
45
- if fingerprints.uniq.size == 1 && !kaller.any? { |f| @whitelist.any? { |s| f.include?(s) } }
46
- queries = @query_holder[location_key]
59
+ if fingerprints.uniq.size == 1 && !kaller.any? { |f| @allow_list.any? { |s| f.include?(s) } }
60
+ queries = tc[:prosopite_query_holder][location_key]
47
61
 
48
62
  unless kaller.any? { |f| f.include?('active_record/validations/uniqueness') }
49
- @notifications[queries] = kaller
63
+ tc[:prosopite_notifications][queries] = kaller
50
64
  end
51
65
  end
52
66
  end
53
67
  end
68
+ end
54
69
 
55
- @scan = false
56
- Prosopite.send_notifications if @notifications.present?
70
+ def fingerprint(query)
71
+ if ActiveRecord::Base.connection.adapter_name.downcase.include?('mysql')
72
+ mysql_fingerprint(query)
73
+ else
74
+ PgQuery.fingerprint(query)
75
+ end
57
76
  end
58
77
 
59
78
  # Many thanks to https://github.com/genkami/fluent-plugin-query-fingerprint/
60
- def fingerprint(query)
79
+ def mysql_fingerprint(query)
61
80
  query = query.dup
62
81
 
63
- unless ActiveRecord::Base.connection.adapter_name.downcase.include?('mysql')
64
- return PgQuery.fingerprint(query)
65
- end
66
-
67
82
  return "mysqldump" if query =~ %r#\ASELECT /\*!40001 SQL_NO_CACHE \*/ \* FROM `#
68
83
  return "percona-toolkit" if query =~ %r#\*\w+\.\w+:[0-9]/[0-9]\*/#
69
84
  if match = /\A\s*(call\s+\S+)\(/i.match(query)
@@ -108,9 +123,14 @@ module Prosopite
108
123
  end
109
124
 
110
125
  def send_notifications
126
+ @rails_logger ||= false
127
+ @stderr_logger ||= false
128
+ @prosopite_logger ||= false
129
+ @raise ||= false
130
+
111
131
  notifications_str = ''
112
132
 
113
- @notifications.each do |queries, kaller|
133
+ tc[:prosopite_notifications].each do |queries, kaller|
114
134
  notifications_str << "N+1 queries detected:\n"
115
135
  queries.each { |q| notifications_str << " #{q}\n" }
116
136
  notifications_str << "Call stack:\n"
@@ -120,8 +140,8 @@ module Prosopite
120
140
  notifications_str << "\n"
121
141
  end
122
142
 
123
- Rails.logger.warn(notifications_str) if @rails_logger
124
- $stderr.puts(notifications_str) if @stderr_logger
143
+ Rails.logger.warn(red(notifications_str)) if @rails_logger
144
+ $stderr.puts(red(notifications_str)) if @stderr_logger
125
145
 
126
146
  if @prosopite_logger
127
147
  File.open(File.join(Rails.root, 'log', 'prosopite.log'), 'a') do |f|
@@ -132,9 +152,13 @@ module Prosopite
132
152
  raise NPlusOneQueriesError.new(notifications_str) if @raise
133
153
  end
134
154
 
155
+ def red(str)
156
+ "\e[91m#{str}\e[0m\n"
157
+ end
158
+
135
159
  def subscribe
160
+ @subscribed ||= false
136
161
  return if @subscribed
137
- @subscribed = true
138
162
 
139
163
  ActiveSupport::Notifications.subscribe 'sql.active_record' do |_, _, _, _, data|
140
164
  sql = data[:sql]
@@ -142,14 +166,16 @@ module Prosopite
142
166
  if scan? && sql.include?('SELECT') && data[:cached].nil?
143
167
  location_key = Digest::SHA1.hexdigest(caller.join)
144
168
 
145
- @query_counter[location_key] += 1
146
- @query_holder[location_key] << sql
169
+ tc[:prosopite_query_counter][location_key] += 1
170
+ tc[:prosopite_query_holder][location_key] << sql
147
171
 
148
- if @query_counter[location_key] > 1
149
- @query_caller[location_key] = caller.dup
172
+ if tc[:prosopite_query_counter][location_key] > 1
173
+ tc[:prosopite_query_caller][location_key] = caller.dup
150
174
  end
151
175
  end
152
176
  end
177
+
178
+ @subscribed = true
153
179
  end
154
180
  end
155
181
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Prosopite
4
- VERSION = "0.1.3"
4
+ VERSION = "0.2.0"
5
5
  end
data/prosopite.gemspec CHANGED
@@ -24,7 +24,7 @@ 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.0'
27
+ spec.add_runtime_dependency 'pg_query', '~> 1.3'
28
28
 
29
29
  spec.add_development_dependency "pry"
30
30
  spec.add_development_dependency "minitest"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prosopite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.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-02-25 00:00:00.000000000 Z
11
+ date: 2021-03-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg_query
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.3.0
19
+ version: '1.3'
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: 1.3.0
26
+ version: '1.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pry
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -132,7 +132,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
132
  - !ruby/object:Gem::Version
133
133
  version: '0'
134
134
  requirements: []
135
- rubygems_version: 3.1.2
135
+ rubyforge_project:
136
+ rubygems_version: 2.7.6.2
136
137
  signing_key:
137
138
  specification_version: 4
138
139
  summary: N+1 auto-detection for Rails with zero false positives / false negatives