prosopite 0.1.4 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0ac26c0cb3a9cab7e0702a6a03b9ad1e369312b2b57548bce8a46c484efaecd
4
- data.tar.gz: 9fd0432b2de7d83b9f3605bb73cd7df23481d07714a7a8f904bac9397e602430
3
+ metadata.gz: a5c95516b64f760903487cb161f0e507cd960a21770bd2729d39fd219526ce99
4
+ data.tar.gz: 890f117df692947849ffb243dff96ddfb1b808b0728bd5a5e613969eb2b11838
5
5
  SHA512:
6
- metadata.gz: '0360859d4cce85abf4f997a79c77b011ef6540d45fd98fe01b9ee7b3465c766df62beee31a1ea67d770c596497dc928d304b05df04af8536c164d9aaacd2e193'
7
- data.tar.gz: 4175589affdabf476f9d346d25117f5a171c94ad95b82b53d4141f39298fdb364e1baaa95f8d9ba95de86d28397b0489217ef632041d5841553a351b92ac3d48
6
+ metadata.gz: 991261e0e5cb2002e2031e4d2a53c620732752425a77dee6d3fd63b06acb779cdba11aebd61531922beafc67492cc41eaa222f33a53c843e01660fe1a7b268f2
7
+ data.tar.gz: 6a09687449111aabcea1cc7d58a64b325e8115504b557b41fd8bf313666eb8098713eda6aaa9d37f83716159c62cfcdce78444ed422dc6a43e40c8568e8237a8
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- prosopite (0.1.4)
4
+ prosopite (0.2.0)
5
5
  pg_query (~> 1.3)
6
6
 
7
7
  GEM
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
  ```
@@ -163,12 +170,12 @@ end
163
170
 
164
171
  WARNING: scan/finish should run before/after **each** test and NOT before/after the whole suite.
165
172
 
166
- ## Whitelisting
173
+ ## Allow list
167
174
 
168
175
  Ignore notifications for call stacks containing one or more substrings:
169
176
 
170
177
  ```ruby
171
- Prosopite.whitelist = ['substring_in_call_stack']
178
+ Prosopite.allow_list = ['substring_in_call_stack']
172
179
  ```
173
180
 
174
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
+ str.split("\n").map { |line| "\e[91m#{line}\e[0m" }.join("\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.4"
4
+ VERSION = "0.2.1"
5
5
  end
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.4
4
+ version: 0.2.1
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-26 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