module Footnotes
module Notes
class QueriesNote < AbstractNote
cattr_accessor :alert_db_time, :alert_sql_number, :orm, :ignored_regexps, :instance_writter => false
@@alert_db_time = 16.0
@@alert_sql_number = 8
@@query_subscriber = nil
@@orm = [:active_record, :data_mapper]
@@ignored_regexps = [%r{(pg_table|pg_attribute|pg_namespace|show\stables|pragma|sqlite_master)}i]
def self.start!(controller)
self.query_subscriber.reset!
end
def self.query_subscriber
@@query_subscriber ||= Footnotes::Notes::QuerySubscriber.new(self.orm)
end
def events
self.class.query_subscriber.events
end
def title
queries = self.events.count
total_time = self.events.map(&:duration).sum
query_color = generate_red_color(self.events.count, alert_sql_number)
db_color = generate_red_color(total_time, alert_db_time)
<<-TITLE
Queries (#{queries})
DB (#{"%.3f" % total_time}ms)
TITLE
end
def content
html = '
'
self.events.each_with_index do |event, index|
sql_links = []
sql_links << "trace"
html << <<-HTML
#{escape(event.type.to_s.upcase)} (#{sql_links.join(' | ')})
#{parse_trace(event.trace)}
|
#{print_query(event.payload[:sql])}
|
#{print_name_and_time(event.payload[:name], event.duration / 1000.0)} |
HTML
end
html << '
'
return html
end
protected
def print_name_and_time(name, time)
"#{escape(name || 'SQL')} (#{'%.3fms' % time})"
end
def print_query(query)
escape(query.to_s.gsub(/(\s)+/, ' ').gsub('`', ''))
end
def generate_red_color(value, alert)
c = ((value.to_f/alert).to_i - 1) * 16
c = 0 if c < 0; c = 15 if c > 15; c = (15-c).to_s(16)
"#ff#{c*4}"
end
def alert_ratio
alert_db_time / alert_sql_number
end
def parse_trace(trace)
trace.map do |t|
s = t.split(':')
%[#{escape(t)}
]
end.join
end
end
class QuerySubscriberNotifactionEvent
attr_reader :event, :trace, :query
delegate :name, :payload, :duration, :time, :type, :to => :event
def initialize(event, ctrace)
@event, @ctrace, @query = event, ctrace, event.payload[:sql]
end
def trace
@trace ||= @ctrace.collect(&:strip).select{|i| i.gsub!(/^#{Rails.root.to_s}\//, '') } || []
end
def type
@type ||= self.query.match(/^(\s*)(select|insert|update|delete|alter)\b/im) || 'Unknown'
end
end
class QuerySubscriber < ActiveSupport::LogSubscriber
attr_accessor :events, :ignore_regexps
def initialize(orm)
super()
@events = []
orm.each {|adapter| ActiveSupport::LogSubscriber.attach_to adapter, self}
end
def reset!
self.events.clear
end
def sql(event)
unless QueriesNote.ignored_regexps.any? {|rex| event.payload[:sql] =~ rex }
@events << QuerySubscriberNotifactionEvent.new(event.dup, caller)
end
end
end
end
end