Skip to content

Instantly share code, notes, and snippets.

@nz
Forked from defunkt/require_benchmarking.rb
Created February 11, 2010 21:01
Show Gist options
  • Save nz/301948 to your computer and use it in GitHub Desktop.
Save nz/301948 to your computer and use it in GitHub Desktop.

Revisions

  1. @mcmire mcmire created this gist Jan 14, 2010.
    206 changes: 206 additions & 0 deletions require_benchmarking.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,206 @@
    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