Module: RailsNexus::LoggedExceptionN1
- Defined in:
- app/models/rails_nexus/logged_exception_n1.rb
Class Method Summary collapse
- .extract_table_name(sql) ⇒ Object
-
.n_plus_one_patterns(limit: 20) ⇒ Object
N+1 Query Pattern Detection Analyzes breadcrumbs for repeated similar SQL queries.
- .n_plus_one_summary ⇒ Object
- .parse_breadcrumbs(data) ⇒ Object
- .sql_fingerprint(sql) ⇒ Object
Class Method Details
.extract_table_name(sql) ⇒ Object
78 79 80 81 82 83 84 85 |
# File 'app/models/rails_nexus/logged_exception_n1.rb', line 78 def self.extract_table_name(sql) return "unknown" if sql.blank? if sql =~ /(?:FROM|INTO|UPDATE|JOIN)\s+[`"']?(\w+)[`"']?/i $1.downcase else "unknown" end end |
.n_plus_one_patterns(limit: 20) ⇒ Object
N+1 Query Pattern Detection Analyzes breadcrumbs for repeated similar SQL queries
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'app/models/rails_nexus/logged_exception_n1.rb', line 7 def self.n_plus_one_patterns(limit: 20) patterns = {} LoggedException.where.not(breadcrumbs: nil) .where("created_at >= ?", 7.days.ago) .order(created_at: :desc) .limit(500) .find_each do |exception| crumbs = (exception.) next unless crumbs.is_a?(Array) sql_crumbs = crumbs.select { |c| c[:type] == "sql" } next if sql_crumbs.empty? sql_crumbs.group_by { |c| sql_fingerprint(c[:name]) }.each do |fingerprint, queries| next if queries.length < 3 table = extract_table_name(queries.first[:name]) patterns[fingerprint] ||= { fingerprint: fingerprint, sql_sample: queries.first[:name]&.truncate(200), table: table, count: 0, exception_ids: [], avg_duration: 0 } patterns[fingerprint][:count] += queries.length patterns[fingerprint][:exception_ids] << exception.id durations = queries.map { |q| q[:duration] }.compact if durations.any? patterns[fingerprint][:avg_duration] = (durations.sum / durations.size).round(2) end end end patterns.values .sort_by { |p| -p[:count] } .first(limit) .map { |p| p.merge(exception_ids: p[:exception_ids].uniq.first(5)) } end |
.n_plus_one_summary ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'app/models/rails_nexus/logged_exception_n1.rb', line 48 def self.n_plus_one_summary patterns = n_plus_one_patterns(limit: 100) { total_patterns: patterns.sum { |p| p[:count] }, unique_patterns: patterns.size, worst_pattern: patterns.first, top_tables: patterns.group_by { |p| p[:table] } .transform_values { |ps| ps.sum { |p| p[:count] } } .sort_by { |_, count| -count } .first(5) .map { |table, count| { table: table, count: count } } } end |
.parse_breadcrumbs(data) ⇒ Object
62 63 64 65 |
# File 'app/models/rails_nexus/logged_exception_n1.rb', line 62 def self.(data) return data if data.is_a?(Array) JSON.parse(data, symbolize_names: true) rescue [] end |
.sql_fingerprint(sql) ⇒ Object
67 68 69 70 71 72 73 74 75 76 |
# File 'app/models/rails_nexus/logged_exception_n1.rb', line 67 def self.sql_fingerprint(sql) return "" if sql.blank? sql.strip .gsub(/\s+/, " ") .gsub(/\d+/, "?") .gsub(/'[^']*'/, "'?'") .gsub(/\b\d+\.\d+\b/, "?") .gsub(/\b0x[0-9a-f]+\b/i, "?") .strip end |