You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trustieforge/lib/rack-mini-profiler/lib/patches/sql_patches.rb

285 lines
8.9 KiB

class SqlPatches
def self.patched?
@patched
end
def self.patched=(val)
@patched = val
end
def self.class_exists?(name)
eval(name + ".class").to_s.eql?('Class')
rescue NameError
false
end
def self.module_exists?(name)
eval(name + ".class").to_s.eql?('Module')
rescue NameError
false
end
end
# The best kind of instrumentation is in the actual db provider, however we don't want to double instrument
if SqlPatches.class_exists? "Mysql2::Client"
class Mysql2::Result
alias_method :each_without_profiling, :each
def each(*args, &blk)
return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
start = Time.now
result = each_without_profiling(*args,&blk)
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
@miniprofiler_sql_id.report_reader_duration(elapsed_time)
result
end
end
class Mysql2::Client
alias_method :query_without_profiling, :query
def query(*args,&blk)
current = ::Rack::MiniProfiler.current
return query_without_profiling(*args,&blk) unless current && current.measure
start = Time.now
result = query_without_profiling(*args,&blk)
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
record = ::Rack::MiniProfiler.record_sql(args[0], elapsed_time)
result.instance_variable_set("@miniprofiler_sql_id", record) if result
result
end
end
SqlPatches.patched = true
end
# PG patches, keep in mind exec and async_exec have a exec{|r| } semantics that is yet to be implemented
if SqlPatches.class_exists? "PG::Result"
class PG::Result
alias_method :each_without_profiling, :each
alias_method :values_without_profiling, :values
def values(*args, &blk)
return values_without_profiling(*args, &blk) unless @miniprofiler_sql_id
start = Time.now
result = values_without_profiling(*args,&blk)
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
@miniprofiler_sql_id.report_reader_duration(elapsed_time)
result
end
def each(*args, &blk)
return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
start = Time.now
result = each_without_profiling(*args,&blk)
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
@miniprofiler_sql_id.report_reader_duration(elapsed_time)
result
end
end
class PG::Connection
alias_method :exec_without_profiling, :exec
alias_method :async_exec_without_profiling, :async_exec
alias_method :exec_prepared_without_profiling, :exec_prepared
alias_method :send_query_prepared_without_profiling, :send_query_prepared
alias_method :prepare_without_profiling, :prepare
def prepare(*args,&blk)
# we have no choice but to do this here,
# if we do the check for profiling first, our cache may miss critical stuff
@prepare_map ||= {}
@prepare_map[args[0]] = args[1]
# dont leak more than 10k ever
@prepare_map = {} if @prepare_map.length > 1000
current = ::Rack::MiniProfiler.current
return prepare_without_profiling(*args,&blk) unless current && current.measure
prepare_without_profiling(*args,&blk)
end
def exec(*args,&blk)
current = ::Rack::MiniProfiler.current
return exec_without_profiling(*args,&blk) unless current && current.measure
start = Time.now
result = exec_without_profiling(*args,&blk)
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
record = ::Rack::MiniProfiler.record_sql(args[0], elapsed_time)
result.instance_variable_set("@miniprofiler_sql_id", record) if result
result
end
def exec_prepared(*args,&blk)
current = ::Rack::MiniProfiler.current
return exec_prepared_without_profiling(*args,&blk) unless current && current.measure
start = Time.now
result = exec_prepared_without_profiling(*args,&blk)
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
mapped = args[0]
mapped = @prepare_map[mapped] || args[0] if @prepare_map
record = ::Rack::MiniProfiler.record_sql(mapped, elapsed_time)
result.instance_variable_set("@miniprofiler_sql_id", record) if result
result
end
def send_query_prepared(*args,&blk)
current = ::Rack::MiniProfiler.current
return send_query_prepared_without_profiling(*args,&blk) unless current && current.measure
start = Time.now
result = send_query_prepared_without_profiling(*args,&blk)
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
mapped = args[0]
mapped = @prepare_map[mapped] || args[0] if @prepare_map
record = ::Rack::MiniProfiler.record_sql(mapped, elapsed_time)
result.instance_variable_set("@miniprofiler_sql_id", record) if result
result
end
def async_exec(*args,&blk)
current = ::Rack::MiniProfiler.current
return exec_without_profiling(*args,&blk) unless current && current.measure
start = Time.now
result = exec_without_profiling(*args,&blk)
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
record = ::Rack::MiniProfiler.record_sql(args[0], elapsed_time)
result.instance_variable_set("@miniprofiler_sql_id", record) if result
result
end
alias_method :query, :exec
end
SqlPatches.patched = true
end
# Mongoid 3 patches
if SqlPatches.class_exists?("Moped::Node")
class Moped::Node
alias_method :process_without_profiling, :process
def process(*args,&blk)
current = ::Rack::MiniProfiler.current
return process_without_profiling(*args,&blk) unless current && current.measure
start = Time.now
result = process_without_profiling(*args,&blk)
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
::Rack::MiniProfiler.record_sql(args[0].log_inspect, elapsed_time)
result
end
end
end
if SqlPatches.class_exists?("RSolr::Connection") && RSolr::VERSION[0] != "0" # requires at least v1.0.0
class RSolr::Connection
alias_method :execute_without_profiling, :execute
def execute_with_profiling(client, request_context)
current = ::Rack::MiniProfiler.current
return execute_without_profiling(client, request_context) unless current && current.measure
start = Time.now
result = execute_without_profiling(client, request_context)
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
data = "#{request_context[:method].upcase} #{request_context[:uri]}"
if request_context[:method] == :post and request_context[:data]
if request_context[:headers].include?("Content-Type") and request_context[:headers]["Content-Type"] == "text/xml"
# it's xml, unescaping isn't needed
data << "\n#{request_context[:data]}"
else
data << "\n#{Rack::Utils.unescape(request_context[:data])}"
end
end
::Rack::MiniProfiler.record_sql(data, elapsed_time)
result
end
alias_method :execute, :execute_with_profiling
end
end
# Fallback for sequel
if SqlPatches.class_exists?("Sequel::Database") && !SqlPatches.patched?
module Sequel
class Database
alias_method :log_duration_original, :log_duration
def log_duration(duration, message)
# `duration` will be in seconds, but we need it in milliseconds for internal consistency.
::Rack::MiniProfiler.record_sql(message, duration * 1000)
log_duration_original(duration, message)
end
end
end
end
## based off https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/active_record.rb
## fallback for alls sorts of weird dbs
if SqlPatches.module_exists?('ActiveRecord') && !SqlPatches.patched?
module Rack
class MiniProfiler
module ActiveRecordInstrumentation
def self.included(instrumented_class)
instrumented_class.class_eval do
unless instrumented_class.method_defined?(:log_without_miniprofiler)
alias_method :log_without_miniprofiler, :log
alias_method :log, :log_with_miniprofiler
protected :log
end
end
end
def log_with_miniprofiler(*args, &block)
current = ::Rack::MiniProfiler.current
return log_without_miniprofiler(*args, &block) unless current && current.measure
sql, name, binds = args
t0 = Time.now
rval = log_without_miniprofiler(*args, &block)
# Don't log schema queries if the option is set
return rval if Rack::MiniProfiler.config.skip_schema_queries and name =~ /SCHEMA/
elapsed_time = ((Time.now - t0).to_f * 1000).round(1)
Rack::MiniProfiler.record_sql(sql, elapsed_time)
rval
end
end
end
def self.insert_instrumentation
ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
include ::Rack::MiniProfiler::ActiveRecordInstrumentation
end
end
if defined?(::Rails) && !SqlPatches.patched?
insert_instrumentation
end
end
end