prosopite 2.1.1 → 2.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: d84ad4584f5e902c5bdf39af36751bc3606db38250c86eb77a3b29123f77462c
4
- data.tar.gz: 4c467310072a1ac5c20f43efd3326505d539a599408c66bd85c5a8e590a49667
3
+ metadata.gz: 3ba705386994a5f73eb64b024eadd45fd4c29db669ccc408283746d03d58bdf8
4
+ data.tar.gz: b3d52ea151dd88b9da262286df321fcdafd2245ce3b09c9539fd54e9ae54ee8d
5
5
  SHA512:
6
- metadata.gz: 8c0dec8a51eba21315ee1e35dc775d08a1ea47f9ca4b78ac29325bff289306d35023218ae0774106bbcdfd0e1758c1c7a6ebec65c79f6b71b70fc46ded5affc5
7
- data.tar.gz: 8e09ccbc95e81392a323dfd19c3837ffef541bf03cf801ab1d58413736ac4489478d5433d892c99c036b6f64175c4c760c1f907fc3d67affd7fd828ed494500b
6
+ metadata.gz: e6f74f6cd59e7537429f32300072394c428837e3ec0dd8aed971d601f447c0f3b28e2bdd68f240f98906be205d23958c3cdd8cbf181821cc26f4f435de5f17c8
7
+ data.tar.gz: 570b9ed4965908b45b5f1f5d6dc1b41d84e0b00759c93e1ff875aa181f143c445ec910ad96dfb4c52ba2020e4948660fc203d3ff8cd8c7496bc870acf0df8180
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ *.gem
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- prosopite (2.1.1)
4
+ prosopite (2.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -329,6 +329,7 @@ class ApplicationController < ActionController::Base
329
329
  end
330
330
  end
331
331
  ```
332
+
332
333
  ```ruby
333
334
  # app/controllers/books_controller.rb
334
335
  class BooksController < ApplicationController
@@ -343,6 +344,7 @@ class BooksController < ApplicationController
343
344
  @book.reviews.map(&:author) # This will not raise N+1 errors
344
345
  end
345
346
  end
347
+ ```
346
348
 
347
349
  ## Custom Logging Configuration
348
350
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Prosopite
4
- VERSION = "2.1.1"
4
+ VERSION = "2.2.0"
5
5
  end
data/lib/prosopite.rb CHANGED
@@ -51,6 +51,7 @@ module Prosopite
51
51
  tc[:prosopite_query_counter] = Hash.new(0)
52
52
  tc[:prosopite_query_holder] = Hash.new { |h, k| h[k] = [] }
53
53
  tc[:prosopite_query_caller] = {}
54
+ tc[:prosopite_query_duration] = Hash.new(0.0)
54
55
 
55
56
  @allow_stack_paths ||= []
56
57
  @ignore_pauses ||= false
@@ -111,6 +112,7 @@ module Prosopite
111
112
  tc[:prosopite_query_counter] = nil
112
113
  tc[:prosopite_query_holder] = nil
113
114
  tc[:prosopite_query_caller] = nil
115
+ tc[:prosopite_query_duration] = nil
114
116
  end
115
117
 
116
118
  def start_raise
@@ -146,13 +148,14 @@ module Prosopite
146
148
 
147
149
  next unless queries.any?
148
150
 
149
- kaller = tc[:prosopite_query_caller][location_key]
151
+ kaller = tc[:prosopite_query_caller][location_key].map(&:to_s)
150
152
  allow_list = (@allow_stack_paths + DEFAULT_ALLOW_LIST)
151
153
  is_allowed = kaller.any? { |f| allow_list.any? { |s| f.match?(s) } }
152
154
 
153
155
  unless is_allowed
156
+ duration_ms = tc[:prosopite_query_duration][location_key]
154
157
  queries.each do |q|
155
- tc[:prosopite_notifications][q] = kaller
158
+ tc[:prosopite_notifications][q] = { kaller: kaller, duration_ms: duration_ms }
156
159
  end
157
160
  end
158
161
  end
@@ -160,12 +163,7 @@ module Prosopite
160
163
  end
161
164
 
162
165
  def fingerprint(query)
163
- conn = if ActiveRecord::Base.respond_to?(:lease_connection)
164
- ActiveRecord::Base.lease_connection
165
- else
166
- ActiveRecord::Base.connection
167
- end
168
- db_adapter = conn.adapter_name.downcase
166
+ db_adapter = ActiveRecord::Base.connection_db_config.adapter
169
167
  if db_adapter.include?('mysql') || db_adapter.include?('trilogy')
170
168
  mysql_fingerprint(query)
171
169
  else
@@ -236,8 +234,12 @@ module Prosopite
236
234
 
237
235
  notifications_str = String.new('')
238
236
 
239
- tc[:prosopite_notifications].each do |queries, kaller|
240
- notifications_str << "N+1 queries detected:\n"
237
+ tc[:prosopite_notifications].each do |queries, info|
238
+ kaller = info[:kaller]
239
+ duration_ms = info[:duration_ms]
240
+ time_str = duration_ms ? " (#{duration_ms.round(1)}ms)" : ''
241
+
242
+ notifications_str << "N+1 queries detected#{time_str}:\n"
241
243
 
242
244
  queries.each { |q| notifications_str << " #{q}\n" }
243
245
 
@@ -277,18 +279,25 @@ module Prosopite
277
279
  @subscribed ||= false
278
280
  return if @subscribed
279
281
 
280
- ActiveSupport::Notifications.subscribe 'sql.active_record' do |_, _, _, _, data|
282
+ ActiveSupport::Notifications.subscribe 'sql.active_record' do |_, start, finish, _, data|
281
283
  sql, name = data[:sql], data[:name]
282
284
 
283
285
  if scan? && name != "SCHEMA" && sql.include?('SELECT') && data[:cached].nil? && !ignore_query?(sql)
284
- query_caller = caller
285
- location_key = Digest::SHA256.hexdigest(query_caller.hash.to_s)
286
+ query_caller = caller_locations
287
+ # Calculate the location key with as few allocations as possible
288
+ location_key = [].tap do |array|
289
+ query_caller.each do |loc|
290
+ array << loc.path
291
+ array << loc.lineno
292
+ end
293
+ end.hash
286
294
 
287
295
  tc[:prosopite_query_counter][location_key] += 1
288
296
  tc[:prosopite_query_holder][location_key] << sql
297
+ tc[:prosopite_query_duration][location_key] += (finish - start) * 1000 if tc[:prosopite_query_duration]
289
298
 
290
299
  if tc[:prosopite_query_counter][location_key] > 1
291
- tc[:prosopite_query_caller][location_key] = query_caller.dup
300
+ tc[:prosopite_query_caller][location_key] = query_caller
292
301
  end
293
302
  end
294
303
  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: 2.1.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mpampis Kostas
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-04 00:00:00.000000000 Z
11
+ date: 2026-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -133,7 +133,7 @@ licenses:
133
133
  metadata:
134
134
  homepage_uri: https://github.com/charkost/prosopite
135
135
  source_code_uri: https://github.com/charkost/prosopite
136
- post_install_message:
136
+ post_install_message:
137
137
  rdoc_options: []
138
138
  require_paths:
139
139
  - lib
@@ -149,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
149
  version: '0'
150
150
  requirements: []
151
151
  rubygems_version: 3.5.3
152
- signing_key:
152
+ signing_key:
153
153
  specification_version: 4
154
154
  summary: N+1 auto-detection for Rails with zero false positives / false negatives
155
155
  test_files: []