Changeset 8065
- Timestamp:
- 11/01/07 08:01:42 (10 months ago)
- Files:
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/actionpack/lib/action_controller/request_profiler.rb
r8017 r8065 1 1 require 'optparse' 2 require 'action_controller/integration' 2 3 3 4 module ActionController 4 5 class RequestProfiler 5 # CGI with stubbed environment and standard input.6 class S tubCGI < CGI7 attr_accessor :env_table, :stdinput6 # Wrap up the integration session runner. 7 class Sandbox 8 include Integration::Runner 8 9 9 def initialize(env_table, stdinput) 10 @env_table = env_table 11 super 12 @stdinput = stdinput 13 end 14 end 15 16 # Stripped-down dispatcher. 17 class Sandbox 18 attr_accessor :env, :body 19 20 def self.benchmark(n, env, body) 21 Benchmark.realtime { n.times { new(env, body).dispatch } } 10 def self.benchmark(n, script) 11 new(script).benchmark(n) 22 12 end 23 13 24 def initialize(env, body) 25 @env, @body = env, body 14 def initialize(script_path) 15 @quiet = false 16 define_run_method(File.read(script_path)) 17 reset! 26 18 end 27 19 28 def dispatch 29 cgi = StubCGI.new(env, StringIO.new(body)) 20 def benchmark(n) 21 @quiet = true 22 print ' ' 23 result = Benchmark.realtime do 24 n.times do |i| 25 run 26 print i % 10 == 0 ? 'x' : '.' 27 $stdout.flush 28 end 29 end 30 puts 31 result 32 ensure 33 @quiet = false 34 end 30 35 31 request = CgiRequest.new(cgi) 32 response = CgiResponse.new(cgi) 36 def say(message) 37 puts " #{message}" unless @quiet 38 end 33 39 34 controller = Routing::Routes.recognize(request) 35 controller.process(request, response) 36 end 40 private 41 def define_run_method(script) 42 instance_eval "def run; #{script}; end", __FILE__, __LINE__ 43 end 37 44 end 38 45 … … 52 59 53 60 def run 54 warmup 55 options[:benchmark] ? benchmark : profile 61 sandbox = Sandbox.new(options[:script]) 62 63 puts 'Warming up once' 64 65 elapsed = warmup(sandbox) 66 puts '%.2f sec, %d requests, %d req/sec' % [elapsed, sandbox.request_count, sandbox.request_count / elapsed] 67 puts "\n#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x" 68 69 options[:benchmark] ? benchmark(sandbox) : profile(sandbox) 56 70 end 57 71 58 def profile 72 def profile(sandbox) 59 73 load_ruby_prof 60 74 61 results = RubyProf.profile { benchmark }75 results = RubyProf.profile { benchmark(sandbox) } 62 76 63 77 show_profile_results results … … 65 79 end 66 80 67 def benchmark 68 puts '%d req/sec' % (options[:n] / Sandbox.benchmark(options[:n], env, body)) 81 def benchmark(sandbox) 82 sandbox.request_count = 0 83 elapsed = sandbox.benchmark(options[:n]).to_f 84 count = sandbox.request_count.to_i 85 puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed] 69 86 end 70 87 71 def warmup 72 puts "#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x" 73 puts "\nrequest headers: #{env.to_yaml}" 74 75 response = Sandbox.new(env, body).dispatch 76 77 puts "\nresponse body: #{response.body[0...100]}#{'[...]' if response.body.size > 100}" 78 puts "\nresponse headers: #{response.headers.to_yaml}" 79 puts 88 def warmup(sandbox) 89 Benchmark.realtime { sandbox.run } 80 90 end 81 91 82 83 def uri84 URI.parse(options[:uri])85 rescue URI::InvalidURIError86 URI.parse(default_uri)87 end88 89 def default_uri90 '/'91 end92 93 def env94 @env ||= default_env95 end96 97 def default_env98 defaults = {99 'HTTP_HOST' => "#{uri.host || 'localhost'}:#{uri.port || 3000}",100 'REQUEST_URI' => uri.path,101 'REQUEST_METHOD' => method,102 'CONTENT_LENGTH' => body.size }103 104 if fixture = options[:fixture]105 defaults['CONTENT_TYPE'] = "multipart/form-data; boundary=#{extract_multipart_boundary(fixture)}"106 end107 108 defaults109 end110 111 def method112 options[:method] || (options[:fixture] ? 'POST' : 'GET')113 end114 115 def body116 options[:fixture] ? File.read(options[:fixture]) : ''117 end118 119 120 92 def default_options 121 { :n => 100 0, :open => 'open %s &' }93 { :n => 100, :open => 'open %s &' } 122 94 end 123 95 … … 125 97 def parse_options(args) 126 98 OptionParser.new do |opt| 127 opt.banner = "USAGE: #{$0} uri [options]"99 opt.banner = "USAGE: #{$0} [options] [session script path]" 128 100 129 opt.on('-u', '--uri [URI]', 'Request URI. Defaults to http://localhost:3000/') { |v| options[:uri] = v } 130 opt.on('-n', '--times [0000]', 'How many requests to process. Defaults to 1000.') { |v| options[:n] = v.to_i } 101 opt.on('-n', '--times [0000]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i } 131 102 opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v } 132 opt.on('--method [GET]', 'HTTP request method. Defaults to GET.') { |v| options[:method] = v.upcase }133 opt.on('--fixture [FILE]', 'Path to POST fixture file') { |v| options[:fixture] = v }134 103 opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v } 135 104 opt.on('-h', '--help', 'Show this help') { puts opt; exit } 136 105 137 106 opt.parse args 107 108 if args.empty? 109 puts opt 110 exit 111 end 112 options[:script] = args.pop 138 113 end 139 114 end … … 147 122 abort '`gem install ruby-prof` to use the profiler' 148 123 end 149 end150 151 def extract_multipart_boundary(path)152 File.open(path) { |f| f.readline }153 124 end 154 125