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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
require 'optparse'
module ActionController
class RequestProfiler
# CGI with stubbed environment and standard input.
class StubCGI < CGI
attr_accessor :env_table, :stdinput
def initialize(env_table, stdinput)
@env_table = env_table
super
@stdinput = stdinput
end
end
# Stripped-down dispatcher.
class Sandbox
attr_accessor :env, :body
def self.benchmark(n, env, body)
Benchmark.realtime { n.times { new(env, body).dispatch } }
end
def initialize(env, body)
@env, @body = env, body
end
def dispatch
cgi = StubCGI.new(env, StringIO.new(body))
request = CgiRequest.new(cgi)
response = CgiResponse.new(cgi)
controller = Routing::Routes.recognize(request)
controller.process(request, response)
end
end
attr_reader :options
def initialize(options = {})
@options = default_options.merge(options)
end
def self.run(args = nil, options = {})
profiler = new(options)
profiler.parse_options(args) if args
profiler.run
end
def run
warmup
options[:benchmark] ? benchmark : profile
end
def profile
load_ruby_prof
results = RubyProf.profile { benchmark }
show_profile_results results
results
end
def benchmark
puts '%d req/sec' % (options[:n] / Sandbox.benchmark(options[:n], env, body))
end
def warmup
puts "#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x"
puts "\nrequest headers: #{env.to_yaml}"
response = Sandbox.new(env, body).dispatch
puts "\nresponse body: #{response.body[0...100]}#{'[...]' if response.body.size > 100}"
puts "\nresponse headers: #{response.headers.to_yaml}"
puts
end
def uri
URI.parse(options[:uri])
rescue URI::InvalidURIError
URI.parse(default_uri)
end
def default_uri
'/'
end
def env
@env ||= default_env
end
def default_env
defaults = {
'HTTP_HOST' => "#{uri.host || 'localhost'}:#{uri.port || 3000}",
'REQUEST_URI' => uri.path,
'REQUEST_METHOD' => method,
'CONTENT_LENGTH' => body.size }
if fixture = options[:fixture]
defaults['CONTENT_TYPE'] = "multipart/form-data; boundary=#{extract_multipart_boundary(fixture)}"
end
defaults
end
def method
options[:method] || (options[:fixture] ? 'POST' : 'GET')
end
def body
options[:fixture] ? File.read(options[:fixture]) : ''
end
def default_options
{ :n => 1000, :open => 'open %s &' }
end
# Parse command-line options
def parse_options(args)
OptionParser.new do |opt|
opt.banner = "USAGE: #{$0} uri [options]"
opt.on('-u', '--uri [URI]', 'Request URI. Defaults to http://localhost:3000/') { |v| options[:uri] = v }
opt.on('-n', '--times [0000]', 'How many requests to process. Defaults to 1000.') { |v| options[:n] = v.to_i }
opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
opt.on('--method [GET]', 'HTTP request method. Defaults to GET.') { |v| options[:method] = v.upcase }
opt.on('--fixture [FILE]', 'Path to POST fixture file') { |v| options[:fixture] = v }
opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v }
opt.on('-h', '--help', 'Show this help') { puts opt; exit }
opt.parse args
end
end
protected
def load_ruby_prof
begin
require 'ruby-prof'
#RubyProf.measure_mode = RubyProf::ALLOCATED_OBJECTS
rescue LoadError
abort '`gem install ruby-prof` to use the profiler'
end
end
def extract_multipart_boundary(path)
File.open(path) { |f| f.readline }
end
def show_profile_results(results)
File.open "#{RAILS_ROOT}/tmp/profile-graph.html", 'w' do |file|
RubyProf::GraphHtmlPrinter.new(results).print(file)
`#{options[:open] % file.path}` if options[:open]
end
File.open "#{RAILS_ROOT}/tmp/profile-flat.txt", 'w' do |file|
RubyProf::FlatPrinter.new(results).print(file)
`#{options[:open] % file.path}` if options[:open]
end
end
end
end
|