require 'benchmark/ips' require 'redis' class MethodProfiler def self.patch(klass, methods, name) patches = methods.map do |method_name| <<~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 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 end end end RUBY end.join("\n") klass.class_eval patches end def self.start Thread.current[:_method_profiler] = { __start: Process.clock_gettime(Process::CLOCK_MONOTONIC) } end 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 data end end end $redis = Redis.new class Test def work #$redis.get 'X' end def method work end def method_unpatched work end end MethodProfiler.patch(Test, [:method], :a_thing) MethodProfiler.start t = Test.new Benchmark.ips do |b| b.report "method" do |times| i = 0 while i < times t.method i += 1 end end b.report "method_unpatched" do |times| i = 0 while i < times t.method_unpatched i += 1 end end end