Module: QueryOwl::Detector

Defined in:
lib/query_owl/detector.rb

Constant Summary collapse

NORMALIZE_PATTERNS =
[
  [/'[^']*'/, "?"],
  [/\b[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\b/i, "?"],
  [/\$\d+/, "?"],
  [/\b\d+\.?\d*\b/, "?"],
  [/\bIN\s*\(\s*\?(?:\s*,\s*\?)*\s*\)/i, "IN (?)"],
  [/"([^"]+)"/, '\1'],
  [/`([^`]+)`/, '\1'],
  [/\s+/, " "]
].freeze

Class Method Summary collapse

Class Method Details

.detect_n_plus_one(queries) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/query_owl/detector.rb', line 15

def detect_n_plus_one(queries)
  threshold = QueryOwl.config.n_plus_one_threshold

  queries
    .reject { |q| q[:cached] }
    .group_by { |q| normalize(q[:sql]) }
    .filter_map do |normalized_sql, group|
      next if group.length < threshold

      {
        type: :n_plus_one,
        sql: normalized_sql,
        count: group.length,
        backtrace: group.first[:backtrace]
      }
    end
end

.detect_slow_queries(queries) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/query_owl/detector.rb', line 33

def detect_slow_queries(queries)
  threshold = QueryOwl.config.slow_query_threshold_ms

  queries.filter_map do |q|
    next if q[:cached]
    next if q[:duration_ms] < threshold

    {
      type: :slow_query,
      sql: normalize(q[:sql]),
      duration_ms: q[:duration_ms],
      backtrace: q[:backtrace]
    }
  end
end

.detect_unused_eager_loads(eager_data) ⇒ Object



49
50
51
52
53
54
55
56
57
# File 'lib/query_owl/detector.rb', line 49

def detect_unused_eager_loads(eager_data)
  preloaded = eager_data[:preloaded] || []
  accessed  = eager_data[:accessed]  || Set.new

  preloaded
    .uniq { |e| "#{e[:model]}##{e[:association]}" }
    .reject { |e| accessed.include?("#{e[:model]}##{e[:association]}") }
    .map { |e| { type: :unused_eager_load, model: e[:model], association: e[:association] } }
end

.normalize(sql) ⇒ Object



59
60
61
62
63
# File 'lib/query_owl/detector.rb', line 59

def normalize(sql)
  NORMALIZE_PATTERNS
    .reduce(sql.to_s) { |s, (pattern, replacement)| s.gsub(pattern, replacement) }
    .strip
end