Skip to content

Instantly share code, notes, and snippets.

@pmvenegas
Last active March 19, 2024 01:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pmvenegas/264de37fb4c477a23dc1958a273de9cb to your computer and use it in GitHub Desktop.
Save pmvenegas/264de37fb4c477a23dc1958a273de9cb to your computer and use it in GitHub Desktop.
A basic profiler
require 'benchmark'
module Profiler
def self.watch(*targets)
targets.each do |target|
target.prepend(self)
end
end
def self.prepended(target)
target.instance_methods(false).each do |m|
wrap(m)
end
target.private_instance_methods(false).each do |m|
wrap(m, make_private: true)
end
end
def self.record
@@record ||= []
end
def self.wrap(m, make_private: false)
define_method m do |*args, **kwargs|
return_value = nil
invocation = [self.class, m, nil]
Sofr::Profiler.record << invocation
elapsed = Benchmark.realtime { return_value = super(*args, **kwargs) }
invocation[-1] = elapsed
return_value
end
private m if make_private
end
def self.profile
reset
invocation = [self, :profile, nil]
record << invocation
elapsed = Benchmark.realtime { yield if block_given? }
invocation[-1] = elapsed
record
end
def self.reset
@@record = []
end
# Summarise a profile result if given, or the current record.
# The sort parameter can be one of :median, :total, or :count.
def self.summarise(result = nil, sort_by: :median)
result ||= record
# Create a hash-of-hashes with default values
summary = Hash.new do |hash, key|
hash[key] = Hash.new { |h, v| h[v] = [] }
end
# Group elapsed times by target class and method
result.each do |target, m, elapsed|
summary[target][m] << elapsed
end
# Summarise each method's invocations and sort per target class
summary.transform_values! do |methods|
methods.map do |m, invocations|
{
method: m,
median: median(invocations).round(2),
total: invocations.sum.round(2),
count: invocations.length
}
end.sort { |a, b| b[sort_by] <=> a[sort_by] }
end
# Implementation in full source
print_table(summary)
summary
end
def self.print_table(summary)
fields = %i[method median total count]
class_width = summary.keys.map(&:to_s).map(&:length).max + 2
method_width = summary.values.flatten.map(&:values).map(&:first).map(&:length).max
# Print summary in a table
headers = "#{' ' * class_width}%-#{method_width}s%8s%8s%6s" % fields
puts headers
puts '-' * headers.length
summary.each do |target, methods|
methods.each_with_index do |m, index|
if index == 0
puts "%-#{class_width}s%-#{method_width}s%8s%8s%6s" % ([target] + m.values)
next
end
puts "#{' ' * class_width}%-#{method_width}s%8s%8s%6s" % m.values
end
puts
end
end
def self.median(list)
return nil if list.empty?
list = list.sort
mid_index = list.length / 2
return list[mid_index.floor].to_f if list.length.odd?
(list[mid_index - 1] + list[mid_index]).fdiv(2)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment