Class: AndOne::Aggregate

Inherits:
Object
  • Object
show all
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

Constructor Details

#initializeAggregate

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

#detectionsObject



43
44
45
# File 'lib/and_one/aggregate.rb', line 43

def detections
  @mutex.synchronize { @entries.dup }
end

#empty?Boolean

Returns:

  • (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

#sizeObject



47
48
49
# File 'lib/and_one/aggregate.rb', line 47

def size
  @mutex.synchronize { @entries.size }
end

#summaryObject



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