module Footnotes module Notes class QueriesNote < AbstractNote cattr_accessor :alert_db_time, :alert_sql_number, :orm, :instance_writter => false @@alert_db_time = 16.0 @@alert_sql_number = 8 @@query_subscriber = nil @@orm = [:active_record, :data_mapper] 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 / 1000.0 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(' | ')})
#{print_query(event.payload[:sql])}
#{print_name_and_time(event.payload[:name], event.duration / 1000.0)} 
HTML end 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 return @type if @type @type = self.query.match(/^(select|insert|update|delete|alter)\b/i) || 'Unknown' end end class QuerySubscriber < ActiveSupport::LogSubscriber attr_accessor :events def initialize(orm) @events = [] orm.each {|adapter| ActiveSupport::LogSubscriber.attach_to adapter, self} end def reset! self.events.clear end def sql(event) @events << QuerySubscriberNotifactionEvent.new(event.dup, caller) end end end end