From dbcf59d47d49e8054a4d1c361dbd4856fe501244 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Thu, 1 Nov 2007 08:01:42 +0000 Subject: Profile an integration session instead of a single request git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8065 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- .../lib/action_controller/request_profiler.rb | 145 +++++++++------------ 1 file changed, 58 insertions(+), 87 deletions(-) (limited to 'actionpack/lib/action_controller') diff --git a/actionpack/lib/action_controller/request_profiler.rb b/actionpack/lib/action_controller/request_profiler.rb index 88e25737a6..62f6e665f1 100755 --- a/actionpack/lib/action_controller/request_profiler.rb +++ b/actionpack/lib/action_controller/request_profiler.rb @@ -1,39 +1,46 @@ require 'optparse' +require 'action_controller/integration' 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. + # Wrap up the integration session runner. class Sandbox - attr_accessor :env, :body + include Integration::Runner - def self.benchmark(n, env, body) - Benchmark.realtime { n.times { new(env, body).dispatch } } + def self.benchmark(n, script) + new(script).benchmark(n) end - def initialize(env, body) - @env, @body = env, body + def initialize(script_path) + @quiet = false + define_run_method(File.read(script_path)) + reset! end - def dispatch - cgi = StubCGI.new(env, StringIO.new(body)) - - request = CgiRequest.new(cgi) - response = CgiResponse.new(cgi) + def benchmark(n) + @quiet = true + print ' ' + result = Benchmark.realtime do + n.times do |i| + run + print i % 10 == 0 ? 'x' : '.' + $stdout.flush + end + end + puts + result + ensure + @quiet = false + end - controller = Routing::Routes.recognize(request) - controller.process(request, response) + def say(message) + puts " #{message}" unless @quiet end + + private + def define_run_method(script) + instance_eval "def run; #{script}; end", __FILE__, __LINE__ + end end @@ -51,90 +58,58 @@ module ActionController 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 + sandbox = Sandbox.new(options[:script]) - def warmup - puts "#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x" - puts "\nrequest headers: #{env.to_yaml}" + puts 'Warming up once' - response = Sandbox.new(env, body).dispatch + elapsed = warmup(sandbox) + puts '%.2f sec, %d requests, %d req/sec' % [elapsed, sandbox.request_count, sandbox.request_count / elapsed] + puts "\n#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x" - puts "\nresponse body: #{response.body[0...100]}#{'[...]' if response.body.size > 100}" - puts "\nresponse headers: #{response.headers.to_yaml}" - puts + options[:benchmark] ? benchmark(sandbox) : profile(sandbox) end + def profile(sandbox) + load_ruby_prof - 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 } + results = RubyProf.profile { benchmark(sandbox) } - if fixture = options[:fixture] - defaults['CONTENT_TYPE'] = "multipart/form-data; boundary=#{extract_multipart_boundary(fixture)}" - end - - defaults + show_profile_results results + results end - def method - options[:method] || (options[:fixture] ? 'POST' : 'GET') + def benchmark(sandbox) + sandbox.request_count = 0 + elapsed = sandbox.benchmark(options[:n]).to_f + count = sandbox.request_count.to_i + puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed] end - def body - options[:fixture] ? File.read(options[:fixture]) : '' + def warmup(sandbox) + Benchmark.realtime { sandbox.run } end - def default_options - { :n => 1000, :open => 'open %s &' } + { :n => 100, :open => 'open %s &' } end # Parse command-line options def parse_options(args) OptionParser.new do |opt| - opt.banner = "USAGE: #{$0} uri [options]" + opt.banner = "USAGE: #{$0} [options] [session script path]" - 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('-n', '--times [0000]', 'How many requests to process. Defaults to 100.') { |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 + + if args.empty? + puts opt + exit + end + options[:script] = args.pop end end @@ -148,10 +123,6 @@ module ActionController 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) -- cgit v1.2.3