lib/rails-footnotes/notes/queries_note.rb in rails3-footnotes-4.0.0.pre.2 vs lib/rails-footnotes/notes/queries_note.rb in rails3-footnotes-4.0.0.pre.3
- old
+ new
@@ -1,187 +1,122 @@
require "#{File.dirname(__FILE__)}/abstract_note"
module Footnotes
module Notes
class QueriesNote < AbstractNote
- @@alert_db_time = 0.16
+ @@alert_db_time = 16.0
@@alert_sql_number = 8
@@sql = []
@@include_when_new_relic_installed = false
@@loaded = false
-
+ @@query_subscriber = nil
cattr_accessor :sql, :alert_db_time, :alert_sql_number, :alert_explain, :loaded, :sql_explain, :instance_writter => false
cattr_reader :include_when_new_relic_installed
-
+
def self.include_when_new_relic_installed=(include_me)
@@include_when_new_relic_installed = include_me
load if include_me
end
-
+
def self.start!(controller)
@@sql = []
+ @@query_subscriber = Footnotes::Notes::QuerySubscriber.new
+ # There appears to be nothing wrong with registering with a non-existant notifier, so we just attach to both :-).
+ ActiveSupport::LogSubscriber.attach_to(:data_mapper, @@query_subscriber)
+ ActiveSupport::LogSubscriber.attach_to(:active_record, @@query_subscriber)
end
def self.to_sym
:queries
end
def title
- db_time = @@sql.inject(0){|sum, item| sum += item.time }
- query_color = generate_red_color(@@sql.length, alert_sql_number)
- db_color = generate_red_color(db_time, alert_db_time)
+ queries = @@query_subscriber.events.length
+ total_time = @@query_subscriber.events.inject(0){|sum, item| sum += item.payload[:duration]} / 1000.0
+ query_color = generate_red_color(@@query_subscriber.events.length, alert_sql_number)
+ db_color = generate_red_color(total_time, alert_db_time)
<<-TITLE
- <span style="background-color:#{query_color}">Queries (#{@@sql.length})</span>
- <span style="background-color:#{db_color}">DB (#{"%.6f" % db_time}s)</span>
+ <span style="background-color:#{query_color}">Queries (#{queries})</span>
+ <span style="background-color:#{db_color}">DB (#{"%.3f" % total_time}ms)</span>
TITLE
end
def stylesheet
<<-STYLESHEET
- #queries_debug_info table td, #queries_debug_info table th{border:1px solid #A00; padding:0 3px; text-align:center;}
- #queries_debug_info table thead, #queries_debug_info table tbody {color:#A00;}
- #queries_debug_info p {background-color:#F3F3FF; border:1px solid #CCC; margin:12px; padding:4px 6px;}
- #queries_debug_info a:hover {text-decoration:underline;}
+ #queries_debug_info table td, #queries_debug_info table th{border:1px solid #A00; padding:0 3px; text-align:center;}
+ #queries_debug_info table thead, #queries_debug_info table tbody {color:#A00;}
+ #queries_debug_info p {background-color:#F3F3FF; border:1px solid #CCC; margin:12px; padding:4px 6px;}
+ #queries_debug_info a:hover {text-decoration:underline;}
STYLESHEET
end
def content
html = ''
- @@sql.each_with_index do |item, i|
- sql_links = []
- sql_links << "<a href=\"javascript:Footnotes.toggle('qtable_#{i}')\" style=\"color:#A00;\">explain</a>" if item.explain
- sql_links << "<a href=\"javascript:Footnotes.toggle('qtrace_#{i}')\" style=\"color:#00A;\">trace</a>" if item.trace
-
+ @@query_subscriber.events.each_with_index do |item, i|
html << <<-HTML
- <b id="qtitle_#{i}">#{escape(item.type.to_s.upcase)}</b> (#{sql_links.join(' | ')})<br />
- #{print_name_and_time(item.name, item.time)}<br />
- <span id="explain_#{i}">#{print_query(item.query)}</span><br />
- #{print_explain(i, item.explain) if item.explain}
- <p id="qtrace_#{i}" style="display:none;">#{parse_trace(item.trace) if item.trace}</p><br />
+ #{print_name_and_time(item.payload[:name], item.payload[:duration] / 1000.0)}
+ <span id="explain_#{i}">#{print_query(item.payload[:sql])}</span><br />
HTML
end
return html
end
-
+
def self.load
- #only include when NewRelic is installed if configured to do so
- if !loaded and included? and defined?(ActiveRecord)
- ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, Footnotes::Extensions::AbstractAdapter
- ActiveRecord::ConnectionAdapters.local_constants.each do |adapter|
- next unless adapter =~ /.*[^Abstract]Adapter$/
- next if adapter =~ /(SQLite|Salesforce)Adapter$/
- eval("ActiveRecord::ConnectionAdapters::#{adapter}").send :include, Footnotes::Extensions::QueryAnalyzer
- self.loaded = true
- end
- end
+ self.loaded = true unless loaded
end
-
+
protected
- def parse_explain(results)
- table = []
- table << results.fetch_fields.map(&:name)
- results.each do |row|
- table << row
- end
- table
- end
+ def print_name_and_time(name, time)
+ "<span style='background-color:#{generate_red_color(time, alert_ratio)}'>#{escape(name || 'SQL')} (#{'%.3fms' % time})</span>"
+ end
- def parse_trace(trace)
- trace.map do |t|
- s = t.split(':')
- %[<a href="#{escape(Footnotes::Filter.prefix("#{Rails.root.to_s}/#{s[0]}", s[1].to_i, 1))}">#{escape(t)}</a><br />]
- end.join
- end
+ def print_query(query)
+ escape(query.to_s.gsub(/(\s)+/, ' ').gsub('`', ''))
+ end
- def print_name_and_time(name, time)
- "<span style='background-color:#{generate_red_color(time, alert_ratio)}'>#{escape(name || 'SQL')} (#{sprintf('%f', time)}s)</span>"
- 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
- def print_query(query)
- escape(query.to_s.gsub(/(\s)+/, ' ').gsub('`', ''))
- end
+ c = (15-c).to_s(16)
+ "#ff#{c*4}"
+ end
- def print_explain(i, explain)
- mount_table(parse_explain(explain), :id => "qtable_#{i}", :style => 'margin:10px;display:none;', :summary => "Debug information for #{title}")
- end
+ def alert_ratio
+ alert_db_time / alert_sql_number
+ 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
-
end
- end
- module Extensions
- class Sql
- attr_accessor :type, :name, :time, :query, :explain, :trace
+ class QuerySubscriber < ActiveSupport::LogSubscriber
+ attr_accessor :events
- def initialize(type, name, time, query, explain)
- @type = type
- @name = name
- @time = time
- @query = query
- @explain = explain
+ def self.runtime=(value)
+ Thread.current["orm_sql_runtime"] = value
+ end
- # Strip, select those ones from app and reject first two, because they
- # are from the plugin
- @trace = Kernel.caller.collect(&:strip).select{|i| i.gsub!(/^#{Rails.root}\//im, '') }[2..-1]
+ def self.runtime
+ Thread.current["orm_sql_runtime"] ||= 0
end
- end
- module QueryAnalyzer
- def self.included(base)
- base.class_eval do
- alias_method_chain :execute, :analyzer
- end
+ def self.reset_runtime
+ rt, self.runtime = runtime, 0
+ rt
end
- def execute_with_analyzer(query, name = nil)
- query_results = nil
- time = Benchmark.realtime { query_results = execute_without_analyzer(query, name) }
-
- if query =~ /^(select|create|update|delete)\b/i
- type = $&.downcase.to_sym
- explain = nil
-
- if adapter_name == 'MySQL' && type == :select && Footnotes::Notes::QueriesNote.sql_explain
- log_silence do
- explain = execute_without_analyzer("EXPLAIN #{query}", name)
- end
- end
- Footnotes::Notes::QueriesNote.sql << Footnotes::Extensions::Sql.new(type, name, time, query, explain)
- end
-
- query_results
+ def initialize
+ @events = Array.new
+ super
end
- end
- module AbstractAdapter
- def log_silence
- result = nil
- if @logger
- @logger.silence do
- result = yield
- end
- else
- result = yield
- end
- result
+ def sql(event)
+ @events << event.dup
end
end
-
end
end
Footnotes::Notes::QueriesNote.load