request_profiler.rb 3.6 KB
Newer Older
J
Jeremy Kemper 已提交
1
require 'optparse'
2
require 'action_controller/integration'
J
Jeremy Kemper 已提交
3 4 5

module ActionController
  class RequestProfiler
6
    # Wrap up the integration session runner.
J
Jeremy Kemper 已提交
7
    class Sandbox
8
      include Integration::Runner
J
Jeremy Kemper 已提交
9

10 11
      def self.benchmark(n, script)
        new(script).benchmark(n)
J
Jeremy Kemper 已提交
12 13
      end

14 15
      def initialize(script_path)
        @quiet = false
16
        define_run_method(script_path)
17
        reset!
J
Jeremy Kemper 已提交
18 19
      end

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
      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
J
Jeremy Kemper 已提交
35

36 37
      def say(message)
        puts "  #{message}" unless @quiet
J
Jeremy Kemper 已提交
38
      end
39 40

      private
41 42 43
        def define_run_method(script_path)
          script = File.read(script_path)
          instance_eval "def run; #{script}; end", script_path, 1
44
        end
J
Jeremy Kemper 已提交
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
    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
62
      sandbox = Sandbox.new(options[:script])
J
Jeremy Kemper 已提交
63

64
      puts 'Warming up once'
J
Jeremy Kemper 已提交
65

66 67 68
      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"
J
Jeremy Kemper 已提交
69

70
      options[:benchmark] ? benchmark(sandbox) : profile(sandbox)
J
Jeremy Kemper 已提交
71 72
    end

73 74
    def profile(sandbox)
      load_ruby_prof
J
Jeremy Kemper 已提交
75

76
      results = RubyProf.profile { benchmark(sandbox) }
J
Jeremy Kemper 已提交
77

78 79
      show_profile_results results
      results
J
Jeremy Kemper 已提交
80 81
    end

82 83 84 85 86
    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]
J
Jeremy Kemper 已提交
87 88
    end

89 90
    def warmup(sandbox)
      Benchmark.realtime { sandbox.run }
J
Jeremy Kemper 已提交
91 92 93
    end

    def default_options
94
      { :n => 100, :open => 'open %s &' }
J
Jeremy Kemper 已提交
95 96 97 98 99
    end

    # Parse command-line options
    def parse_options(args)
      OptionParser.new do |opt|
100
        opt.banner = "USAGE: #{$0} [options] [session script path]"
J
Jeremy Kemper 已提交
101

102
        opt.on('-n', '--times [0000]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i }
J
Jeremy Kemper 已提交
103
        opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
J
Jeremy Kemper 已提交
104 105 106 107
        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
108 109 110 111 112 113

        if args.empty?
          puts opt
          exit
        end
        options[:script] = args.pop
J
Jeremy Kemper 已提交
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
      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 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