Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 8065

Show
Ignore:
Timestamp:
11/01/07 08:01:42 (10 months ago)
Author:
bitsweat
Message:

Profile an integration session instead of a single request

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/lib/action_controller/request_profiler.rb

    r8017 r8065  
    11require 'optparse' 
     2require 'action_controller/integration' 
    23 
    34module ActionController 
    45  class RequestProfiler 
    5     # CGI with stubbed environment and standard input
    6     class StubCGI < CGI 
    7       attr_accessor :env_table, :stdinput 
     6    # Wrap up the integration session runner
     7    class Sandbox 
     8      include Integration::Runner 
    89 
    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) 
    2212      end 
    2313 
    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! 
    2618      end 
    2719 
    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 
    3035 
    31         request = CgiRequest.new(cgi) 
    32         response = CgiResponse.new(cgi) 
     36      def say(message) 
     37        puts "  #{message}" unless @quiet 
     38      end 
    3339 
    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 
    3744    end 
    3845 
     
    5259 
    5360    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) 
    5670    end 
    5771 
    58     def profile 
     72    def profile(sandbox) 
    5973      load_ruby_prof 
    6074 
    61       results = RubyProf.profile { benchmark
     75      results = RubyProf.profile { benchmark(sandbox)
    6276 
    6377      show_profile_results results 
     
    6579    end 
    6680 
    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] 
    6986    end 
    7087 
    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 } 
    8090    end 
    8191 
    82  
    83     def uri 
    84       URI.parse(options[:uri]) 
    85     rescue URI::InvalidURIError 
    86       URI.parse(default_uri) 
    87     end 
    88  
    89     def default_uri 
    90       '/' 
    91     end 
    92  
    93     def env 
    94       @env ||= default_env 
    95     end 
    96  
    97     def default_env 
    98       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       end 
    107  
    108       defaults 
    109     end 
    110  
    111     def method 
    112       options[:method] || (options[:fixture] ? 'POST' : 'GET') 
    113     end 
    114  
    115     def body 
    116       options[:fixture] ? File.read(options[:fixture]) : '' 
    117     end 
    118  
    119  
    12092    def default_options 
    121       { :n => 1000, :open => 'open %s &' } 
     93      { :n => 100, :open => 'open %s &' } 
    12294    end 
    12395 
     
    12597    def parse_options(args) 
    12698      OptionParser.new do |opt| 
    127         opt.banner = "USAGE: #{$0} uri [options]" 
     99        opt.banner = "USAGE: #{$0} [options] [session script path]" 
    128100 
    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 } 
    131102        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 } 
    134103        opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v } 
    135104        opt.on('-h', '--help', 'Show this help') { puts opt; exit } 
    136105 
    137106        opt.parse args 
     107 
     108        if args.empty? 
     109          puts opt 
     110          exit 
     111        end 
     112        options[:script] = args.pop 
    138113      end 
    139114    end 
     
    147122          abort '`gem install ruby-prof` to use the profiler' 
    148123        end 
    149       end 
    150  
    151       def extract_multipart_boundary(path) 
    152         File.open(path) { |f| f.readline } 
    153124      end 
    154125