2019-05-03 06:17:27 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-10-18 09:10:12 +08:00
|
|
|
# see https://samsaffron.com/archive/2017/10/18/fastest-way-to-profile-a-method-in-ruby
|
|
|
|
class MethodProfiler
|
2018-02-28 07:45:11 +08:00
|
|
|
def self.patch(klass, methods, name, no_recurse: false)
|
2017-10-18 09:10:12 +08:00
|
|
|
patches = methods.map do |method_name|
|
2018-02-28 07:45:11 +08:00
|
|
|
|
|
|
|
recurse_protection = ""
|
|
|
|
if no_recurse
|
|
|
|
recurse_protection = <<~RUBY
|
|
|
|
return #{method_name}__mp_unpatched(*args, &blk) if @mp_recurse_protect_#{method_name}
|
|
|
|
@mp_recurse_protect_#{method_name} = true
|
|
|
|
RUBY
|
|
|
|
end
|
|
|
|
|
2017-10-18 09:10:12 +08:00
|
|
|
<<~RUBY
|
|
|
|
unless defined?(#{method_name}__mp_unpatched)
|
|
|
|
alias_method :#{method_name}__mp_unpatched, :#{method_name}
|
|
|
|
def #{method_name}(*args, &blk)
|
|
|
|
unless prof = Thread.current[:_method_profiler]
|
|
|
|
return #{method_name}__mp_unpatched(*args, &blk)
|
|
|
|
end
|
2018-02-28 07:45:11 +08:00
|
|
|
#{recurse_protection}
|
2017-10-18 09:10:12 +08:00
|
|
|
begin
|
|
|
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
|
|
#{method_name}__mp_unpatched(*args, &blk)
|
|
|
|
ensure
|
|
|
|
data = (prof[:#{name}] ||= {duration: 0.0, calls: 0})
|
|
|
|
data[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
|
|
data[:calls] += 1
|
2018-02-28 07:45:11 +08:00
|
|
|
#{"@mp_recurse_protect_#{method_name} = false" if no_recurse}
|
2017-10-18 09:10:12 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
RUBY
|
|
|
|
end.join("\n")
|
|
|
|
|
|
|
|
klass.class_eval patches
|
|
|
|
end
|
|
|
|
|
2017-11-28 13:47:20 +08:00
|
|
|
def self.transfer
|
|
|
|
result = Thread.current[:_method_profiler]
|
|
|
|
Thread.current[:_method_profiler] = nil
|
|
|
|
result
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.start(transfer = nil)
|
|
|
|
Thread.current[:_method_profiler] = transfer || {
|
2017-10-18 09:10:12 +08:00
|
|
|
__start: Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2018-01-19 05:26:18 +08:00
|
|
|
def self.clear
|
|
|
|
Thread.current[:_method_profiler] = nil
|
|
|
|
end
|
|
|
|
|
2017-10-18 09:10:12 +08:00
|
|
|
def self.stop
|
|
|
|
finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
|
|
if data = Thread.current[:_method_profiler]
|
|
|
|
Thread.current[:_method_profiler] = nil
|
|
|
|
start = data.delete(:__start)
|
|
|
|
data[:total_duration] = finish - start
|
|
|
|
end
|
|
|
|
data
|
|
|
|
end
|
2019-03-05 19:19:11 +08:00
|
|
|
|
|
|
|
def self.ensure_discourse_instrumentation!
|
|
|
|
@@instrumentation_setup ||= begin
|
|
|
|
MethodProfiler.patch(PG::Connection, [
|
|
|
|
:exec, :async_exec, :exec_prepared, :send_query_prepared, :query, :exec_params
|
|
|
|
], :sql)
|
|
|
|
|
|
|
|
MethodProfiler.patch(Redis::Client, [
|
|
|
|
:call, :call_pipeline
|
|
|
|
], :redis)
|
|
|
|
|
|
|
|
MethodProfiler.patch(Net::HTTP, [
|
|
|
|
:request
|
|
|
|
], :net, no_recurse: true)
|
|
|
|
|
|
|
|
MethodProfiler.patch(Excon::Connection, [
|
|
|
|
:request
|
|
|
|
], :net)
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-10-18 09:10:12 +08:00
|
|
|
end
|