Permalink
Browse files

Request profiler

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8016 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
1 parent 96557eb commit d69be7df6dd1d358b019a2b8fe2c7cd10050063e @jeremy jeremy committed Oct 25, 2007
View
@@ -1,5 +1,7 @@
*SVN*
+* Request profiler. [Jeremy Kemper]
+
* Disabled checkboxes don't submit a form value. #9301 [vladr, robinjfisher]
* Added tests for options to ActiveRecordHelper#form. Closes #7213 [richcollins, mikong, mislav]
@@ -0,0 +1,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
+ '/benchmarks/hello'
+ 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/benchmarks/hello') { |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('--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('--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = 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
View
@@ -1,5 +1,7 @@
*SVN*
+* Request profiler. [Jeremy Kemper]
+
* config/boot.rb correctly detects RAILS_GEM_VERSION. #9834 [alexch, thewoolleyman]
* Fixed incorrect migration number if script/generate executed outside of Rails root #7080 [jeremymcanally]
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../config/boot'
+require 'commands/performance/request'
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+RAILS_DEFAULT_LOGGER = nil
+require 'config/environment'
+require 'application'
+require 'action_controller/request_profiler'
+
+ActionController::RequestProfiler.run(ARGV)
@@ -67,7 +67,7 @@ def manifest
m.file "environments/test.rb", "config/environments/test.rb"
# Scripts
- %w( about console destroy generate performance/benchmarker performance/profiler process/reaper process/spawner process/inspector runner server plugin ).each do |file|
+ %w( about console destroy generate performance/benchmarker performance/profiler performance/request process/reaper process/spawner process/inspector runner server plugin ).each do |file|
m.file "bin/#{file}", "script/#{file}", script_options
end

0 comments on commit d69be7d

Please sign in to comment.