Class: AndOne::Aggregate
- Inherits:
-
Object
- Object
- AndOne::Aggregate
- Defined in:
- lib/and_one/aggregate.rb
Overview
Tracks unique N+1 detections across requests/jobs in a server session. Each unique N+1 (by fingerprint) is only reported once. Subsequent occurrences are silently counted.
The aggregate can be queried at any time:
AndOne.aggregate.summary # => formatted string
AndOne.aggregate.detections # => { fingerprint => { detection:, count:, first_seen_at: } }
AndOne.aggregate.reset!
Defined Under Namespace
Classes: Entry
Instance Method Summary collapse
- #detections ⇒ Object
- #empty? ⇒ Boolean
-
#initialize ⇒ Aggregate
constructor
A new instance of Aggregate.
-
#record(detection) ⇒ Object
Record a detection.
- #reset! ⇒ Object
- #size ⇒ Object
- #summary ⇒ Object
Constructor Details
#initialize ⇒ Aggregate
Returns a new instance of Aggregate.
16 17 18 19 |
# File 'lib/and_one/aggregate.rb', line 16 def initialize @mutex = Mutex.new @entries = {} end |
Instance Method Details
#detections ⇒ Object
43 44 45 |
# File 'lib/and_one/aggregate.rb', line 43 def detections @mutex.synchronize { @entries.dup } end |
#empty? ⇒ Boolean
51 52 53 |
# File 'lib/and_one/aggregate.rb', line 51 def empty? @mutex.synchronize { @entries.empty? } end |
#record(detection) ⇒ Object
Record a detection. Returns true if this is a NEW unique detection (first time seeing this fingerprint), false if it's a repeat.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/and_one/aggregate.rb', line 23 def record(detection) fp = detection.fingerprint @mutex.synchronize do if @entries.key?(fp) @entries[fp].occurrences += 1 @entries[fp].last_seen_at = Time.now false else @entries[fp] = Entry.new( detection: detection, occurrences: 1, first_seen_at: Time.now, last_seen_at: Time.now ) true end end end |
#reset! ⇒ Object
55 56 57 |
# File 'lib/and_one/aggregate.rb', line 55 def reset! @mutex.synchronize { @entries.clear } end |
#size ⇒ Object
47 48 49 |
# File 'lib/and_one/aggregate.rb', line 47 def size @mutex.synchronize { @entries.size } end |
#summary ⇒ Object
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/and_one/aggregate.rb', line 59 def summary @mutex.synchronize do return "No N+1 queries detected this session." if @entries.empty? lines = [] lines << "" lines << "🏀 AndOne Session Summary: #{@entries.size} unique N+1 pattern#{"s" if @entries.size != 1}" lines << ("─" * 60) @entries.each_with_index do |(fp, entry), i| det = entry.detection lines << " #{i + 1}) #{det.table_name || "unknown"} — #{entry.occurrences} occurrence#{"s" if entry.occurrences != 1}" lines << " #{det.sample_query[0, 120]}" lines << " origin: #{det.origin_frame}" if det.origin_frame lines << " fingerprint: #{fp}" lines << "" end lines << ("─" * 60) lines.join("\n") end end |