1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
#!/usr/bin/env ruby
# Profile require calls giving information about the time and the files that are called
# when loading the provided file.
#
# 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
Error = Class.new(StandardError)
attr_reader :path, :mode
def initialize(path, mode=nil)
assert_ruby_file_exists(path)
@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
private
def assert_ruby_file_exists(path)
fail Error.new("No such file") unless File.exist?(path)
fail Error.new("#{path} is a directory") if File.directory?(path)
ruby_extension = File.extname(path) == ".rb"
ruby_executable = File.open(path, "rb") { |f| f.readline } =~ [/\A#!.*ruby/]
fail Error.new("Not a ruby file") unless ruby_extension or ruby_executable
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
# ruby-prof printer name causes the third arg to be sent :classify
# which is probably overkill if you already know the name of the ruby-prof
# printer you want to use, e.g. Graph
begin
require "active_support/inflector"
require "active_support/core_ext/string/inflections"
rescue LoadError
STDERR.puts $!.message
class String
# File activesupport/lib/active_support/inflector/methods.rb, line 150
def classify
# strip out any leading schema name
camelize(sub(/.*\./, ""))
end
# File activesupport/lib/active_support/inflector/methods.rb, line 68
def camelize(uppercase_first_letter = true)
string = self
if uppercase_first_letter
string = string.sub(/^[a-z\d]*/) { |match| match.capitalize }
else
string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
end
string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.gsub("/", "::")
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
|