class SqlEventAnalyzer
Captures Rails SQL events and crunches some statistics
Constants
- VERSION
Attributes
name[R]
stats[R]
Public Class Methods
new(name:)
click to toggle source
# File lib/sql_event_analyzer.rb, line 38 def initialize(name:) @name = name @root = Rails.root.to_s @stats = {} end
start(name: Time.current) { || ... }
click to toggle source
captures the events and writes out the result @return [File] file containing html output
# File lib/sql_event_analyzer.rb, line 12 def self.start(name: Time.current) instance = SqlEventAnalyzer.new(name: name) subscription_name = ActiveSupport::Notifications.subscribe('sql.active_record') do |*args| event = ActiveSupport::Notifications::Event.new(*args) instance.process_event(event) end if block_given? begin yield ensure ActiveSupport::Notifications.unsubscribe(subscription_name) end write_html(instance) end end
write_html(instance)
click to toggle source
# File lib/sql_event_analyzer.rb, line 30 def self.write_html(instance) output_dir = 'tmp/sql_event_analyzer' output_path = File.join(output_dir, "#{instance.name}.html") FileUtils.mkdir_p(output_dir) html = instance.render_as_html File.open(output_path, 'w') {|f| f << html} end
Public Instance Methods
process_event(event)
click to toggle source
@param [ActiveSupport::Notifications::Event] SQL event from Rails
# File lib/sql_event_analyzer.rb, line 45 def process_event(event) # Callers from Rails root and not the subscriber itself backtrace = caller.select{|line| relevent_caller?(line) } # Initialize a uniq SQL use. # `:payload` will always be the first instance of the call. stat = @stats[backtrace] ||= { order: @stats.size, count: 0, duration: 0, payload: event.payload } # increment stats stat[:count] += 1 stat[:duration] += event.duration end
render_as_html()
click to toggle source
# File lib/sql_event_analyzer.rb, line 63 def render_as_html ERB.new(erb_template).result(binding) end
Private Instance Methods
debug_rb(c)
click to toggle source
# File lib/sql_event_analyzer.rb, line 91 def debug_rb(c) return unless c first = trim_caller(c) file, line = if first =~ /(.*\.rb):(\d+)/ [$1, $2] end rb_src = File.readlines(file)[line.to_i - 1].strip <<-EOF.strip_heredoc -- #{first} -- #{rb_src} EOF end
erb_template()
click to toggle source
# File lib/sql_event_analyzer.rb, line 83 def erb_template File.read(File.join(__dir__, 'sql_event_analyzer.erb')) end
explain(payload)
click to toggle source
# File lib/sql_event_analyzer.rb, line 79 def explain(payload) ActiveRecord::Base.connection.explain(payload[:sql], payload[:binds]) end
pretty_sql(sql)
click to toggle source
# File lib/sql_event_analyzer.rb, line 73 def pretty_sql(sql) sql .delete('"') .gsub(/\b+(SELECT|FROM|WHERE|GROUP|HAVING|LEFT JOIN|LEFT OUTER JOIN|INNER JOIN|JOIN|RIGHT|ORDER|WINDOW)\b+/, "\n\\1") end
relevent_caller?(c)
click to toggle source
# File lib/sql_event_analyzer.rb, line 69 def relevent_caller?(c) c.start_with?(@root) && !c.start_with?(__FILE__) end
trim_caller(c)
click to toggle source
# File lib/sql_event_analyzer.rb, line 87 def trim_caller(c) c.gsub(@root, '.') end