Skip to content

Instantly share code, notes, and snippets.

@defunkt
Forked from mcmire/require_profiler.md
Created January 16, 2010 20:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save defunkt/278994 to your computer and use it in GitHub Desktop.
Save defunkt/278994 to your computer and use it in GitHub Desktop.
Override 'require' to record the time it takes to require a Ruby file.
require 'benchmark'
require 'yaml'
require 'set'
# This overrides 'require' to records the time it takes to require a file, and
# then generate a report. It's intelligent enough to figure out where files were
# required from and construct a hierarchy of the required files.
#
# To use, copy this file to lib/require_benchmarking.rb, then add this to the
# top of the Rails::Initializer block in environment.rb:
#
# # Benchmark requires
# require File.dirname(__FILE__) + '/../lib/require_benchmarking'
# RequireBenchmarking.hook(config)
#
# Then, start your Rails app using script/server. After the app has been initialized,
# the report will be generated and saved to RAILS_ROOT/boot.log. If you need
# to regenerate this report, simply run `ruby lib/require_benchmarking.rb`.
# By default, this will generate a flat report of only top-level requires, but
# pass `--all` to list all files in their respective hierarchy.
#
module RequireBenchmarking
class << self
def hook(config)
Kernel.class_eval do
alias_method :__require_benchmarking_old_require, :require
def require(path, *args)
RequireBenchmarking.benchmark_require(path, caller) { __require_benchmarking_old_require(path, *args) }
end
end
config.after_initialize { RequireBenchmarking.store_benchmark_data }
@hooked = true
end
def hooked?
@hooked
end
def benchmark_require(path, full_backtrace, &block)
output = nil
backtrace = full_backtrace.reject {|x| x =~ /require|dependencies/ }
caller = File.expand_path(backtrace[0].split(":")[0])
parent = required_files.find {|f| f[:fullpath] == caller }
unless parent
parent = {
:index => required_files.size,
:fullpath => caller,
:parent => nil,
:is_root => true
}
required_files << parent
end
fullpath = find_file(path)
expanded_path = path; expanded_path = File.expand_path(path) if path =~ /^\//
new_file = {
:index => required_files.size,
:path => expanded_path,
:fullpath => fullpath,
:backtrace => full_backtrace,
:parent => parent,
:is_root => false
}
# add this before the file is required so that anything that is required
# within the file that's about to be required already has a parent present
required_files << new_file
benchmark = Benchmark.measure do
output = yield # do the require here
end
new_file[:time] = benchmark.real
output
end
def store_benchmark_data
File.open(data_file, "w") {|f| YAML.dump(@required_files, f) }
puts "Wrote data to #{data_file}."
generate_benchmark_report(false)
exit
end
def generate_benchmark_report(regenerating_report=true)
puts "Now generating benchmark report, please wait..."
if regenerating_report
@required_files = File.open(data_file) {|f| YAML.load(f) }
end
@report_fh = File.open(report_file, "w")
@indent_level = 0
root_files = @required_files.select {|file| file[:is_root] }
if ARGV.include?("--all")
generate_benchmark_report_level(root_files)
else
generate_benchmark_report_level(@required_files.select {|file| !file[:is_root] && file[:time] }, true)
end
@report_fh.close
out = "Wrote report to #{report_file}."
out << " Run `ruby lib/require_benchmarking.rb` if you want to regenerate the report." unless regenerating_report
puts(out)
end
private
def required_files
@required_files ||= []
end
def printed_files
@printed_files ||= []
end
def data_file
"#{proj_dir}/boot.yml"
end
def report_file
"#{proj_dir}/boot.log"
end
def proj_dir
@proj_dir ||= File.expand_path(File.dirname(__FILE__) + "/..")
end
def find_file(path)
return File.expand_path(path) if path =~ /^\//
expanded_path = nil
# Try to find the path in the ActiveSupport load paths and then the built-in load paths
catch :found_path do
%w(rb bundle so).each do |ext|
path_suffix = path; path_suffix = "#{path}.#{ext}" unless path_suffix =~ /\.#{ext}$/
(ActiveSupport::Dependencies.load_paths + $:).each do |path_prefix|
possible_path = File.join(path_prefix, path_suffix)
if File.file? possible_path
expanded_path = File.expand_path(possible_path)
throw :found_path
end
end
expanded_path
end
end
expanded_path
end
def generate_benchmark_report_level(files, printing_all=false)
if printing_all
files = files.sort {|a,b| b[:time] <=> a[:time] }
else
files = files.sort_by {|f| [(f[:parent] ? 1 : 0), -(f[:time] || 0), f[:index]] }
end
for file in files
already_printed = printed_files.include?(file[:fullpath])
# don't print this file if it's already been printed,
# or it will have been printed
next if already_printed
if file[:parent] && !printing_all
next if file[:index] < file[:parent][:index]
end
path = file[:fullpath] ? format_path(file[:fullpath]) : file[:path]
out = "#{file[:index]+1}) "
if file[:time] && !already_printed
#if file[:time] >= 0.5
# out << "%s: %.4f s" % [path, file[:time]]
#else
ms = file[:time].to_f * 1000
out << "%s: %.1f ms" % [path, ms]
#end
else
out << path
end
if file[:is_root] && file[:parent]
out << " (required by #{file[:parent][:fullpath]})"
end
unless file[:parent]
out << " (already loaded)"
end
if already_printed
out << " (already printed)"
end
indent(out)
unless already_printed
printed_files << file[:fullpath]
unless printing_all
children = @required_files.select {|f| !f[:is_root] && f[:parent] && f[:parent][:fullpath] == file[:fullpath] }
if children.any?
@indent_level += 1
generate_benchmark_report_level(children)
@indent_level -= 1
end
end
end
end
end
def indent(msg)
@report_fh.print(" " * @indent_level)
@report_fh.puts(msg)
end
def format_path(path)
path.sub(proj_dir, "*")
end
end
end
at_exit do
RequireBenchmarking.generate_benchmark_report unless RequireBenchmarking.hooked?
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment