#!/usr/bin/env ruby # Example: # tools/profile activesupport/lib/active_support.rb [ruby-prof mode] [ruby-prof printer] ENV['NO_RELOAD'] ||= '1' ENV['RAILS_ENV'] ||= 'development' module CodeTools class Profiler attr_reader :path, :mode def initialize(path, mode=nil) @path, @mode = path, mode require 'benchmark' end def profile_requires GC.start before_rss = `ps -o rss= -p #{Process.pid}`.to_i if mode require 'ruby-prof' RubyProf.measure_mode = RubyProf.const_get(mode.upcase) RubyProf.start else Object.instance_eval { include RequireProfiler } end elapsed = Benchmark.realtime { require path } results = RubyProf.stop if mode GC.start after_rss = `ps -o rss= -p #{Process.pid}`.to_i if mode if printer = ARGV.shift puts "RubyProf outputting to stderr with printer #{printer}" RubyProf.const_get("#{printer.to_s.classify}Printer").new(results).print($stdout) elsif RubyProf.const_defined?(:CallStackPrinter) filename = "#{File.basename(path, '.rb')}.#{mode}.html" puts "RubyProf outputting to #{filename}" File.open(filename, 'w') do |out| RubyProf::CallStackPrinter.new(results).print(out) end else filename = "#{File.basename(path, '.rb')}.#{mode}.callgrind" puts "RubyProf outputting to #{filename}" File.open(filename, 'w') do |out| RubyProf::CallTreePrinter.new(results).print(out) end end end RequireProfiler.stats.each do |file, depth, sec| if sec puts "%8.1f ms %s%s" % [sec * 1000, ' ' * depth, file] else puts "#{' ' * (13 + depth)}#{file}" end end puts "%8.1f ms %d KB RSS" % [elapsed * 1000, after_rss - before_rss] end module RequireProfiler private def require(file, *args) RequireProfiler.profile(file) { super } end def load(file, *args) RequireProfiler.profile(file) { super } end @depth, @stats = 0, [] class << self attr_accessor :depth attr_accessor :stats def profile(file) stats << [file, depth] self.depth += 1 result = nil elapsed = Benchmark.realtime { result = yield } self.depth -= 1 stats.pop if stats.last.first == file stats << [file, depth, elapsed] if result result end end end end end if $0 == __FILE__ if (filename = ARGV.shift) path = File.expand_path(filename) mode = ARGV.shift CodeTools::Profiler.new(path, mode).profile_requires else STDERR.puts "No file path entered. Usage is tools/profile path/to/file.rb [ruby-prof mode] [ruby-prof printer]" end end