-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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