Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Pull working action_profiler from Robot Co-op's repository.

[git-p4: depot-paths = "//src/action_profiler/dev/": change = 3134]
  • Loading branch information...
commit cf85c5f34d15ee5065132771e174a90026170438 0 parents
@drbrain drbrain authored
30 LICENSE.txt
@@ -0,0 +1,30 @@
+Portions copyright 2004 David Heinemeier Hansson.
+
+All original code copyright 2005, 2007 Eric Hodel, The Robot Co-op.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. Neither the names of the authors nor the names of their contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
12 Manifest.txt
@@ -0,0 +1,12 @@
+LICENSE.txt
+Manifest.txt
+README.txt
+Rakefile
+bin/action_profiler
+lib/action_profiler.rb
+lib/action_profiler/path2class.rb
+lib/action_profiler/prof_processor.rb
+lib/action_profiler/profiled_processor.rb
+lib/action_profiler/profiler_processor.rb
+lib/action_profiler/test_processor.rb
+lib/action_profiler/zenprofiler_processor.rb
48 README.txt
@@ -0,0 +1,48 @@
+= Action Profiler
+
+A profiler for Rails controllers.
+
+Full Documentation:
+
+http://seattlerb.rubyforge.org/action_profiler
+
+Rubyforge Project:
+
+http://rubyforge.org/projects/rails-analyzer
+
+== About
+
+action_profiler allows you to profile a single Rails action to determine what
+to optimize. You can use the Production Log Analyzer and action_grep to
+determine which actions you should profile and what arguments to use.
+
+Information on the Production Log Analyzer can be found at:
+
+http://rails-analyzer.rubyforge.org/pl_analyze
+
+=== Profilers
+
+Action Profiler REQUIRES Ruby 1.8.3 or newer, even if you just use
+Ruby's builtin profiler.
+
+Action Profiler can use three profilers, ZenProfile, Ruby's builtin
+profiler class or Shugo Maeda's Prof.
+
+Shugo Maeda's Prof: http://raa.ruby-lang.org/project/ruby-prof
+
+ZenProfile: http://rubyforge.org/frs/?group_id=712&release_id=2476
+
+=== Running Action Profiler
+
+See ActionProfiler
+
+== Gem Installation
+
+ gem install action_profiler
+
+== Download
+
+http://rubyforge.org/frs/?group_id=1513
+
+(Sorry, no manual installation script is available for the .tgz)
+
13 Rakefile
@@ -0,0 +1,13 @@
+require 'hoe'
+$: << './lib'
+require 'action_profiler'
+
+Hoe.new 'action_profiler', ActionProfiler::VERSION do |p|
+ p.summary = p.paragraphs_of('README.txt', 1).first
+ p.description = p.paragraphs_of('README.txt', 7).join ' '
+ p.author = 'Eric Hodel'
+ p.email = 'drbrain@segment7.net'
+ p.rubyforge_name = 'seattlerb'
+ p.url = p.paragraphs_of('README.txt', 3).first
+end
+
6 bin/action_profiler
@@ -0,0 +1,6 @@
+#!/usr/local/bin/ruby
+
+require 'action_profiler'
+
+ActionProfiler::ProfiledProcessor.process_args ARGV
+
39 lib/action_profiler.rb
@@ -0,0 +1,39 @@
+##
+# Typically, action_profiler will be run from the root of your Rails
+# application:
+#
+# $ action_profiler GamesController#index
+# Warmup...
+# Profiling...
+# [ profile output ]
+# $
+#
+# If you need to run action_profiler from some other path, the -p command line
+# option can be used to specify the location of your Rails application.
+#
+# action_profiler -p ~/Worx/X/CCR GamesController#index
+#
+# Parameters can be specified after the controller and action:
+#
+# action_profiler GamesController#index ":id => 1"
+#
+# If you need to make sure a page is working correctly you can specify -o. No
+# profiling will occur and the generated page will be printed out instead:
+#
+# $ action_profiler -o GamesController#show ":id => 1"
+# <html>
+# [ lots of HTML output ]
+# $
+
+module ActionProfiler
+
+ ##
+ # The version of ActionProfiler you are using.
+
+ VERSION = '1.1.0'
+
+end
+
+# This project has lame names.
+require 'action_profiler/profiled_processor'
+
12 lib/action_profiler/path2class.rb
@@ -0,0 +1,12 @@
+class Object
+
+ ##
+ # Retrieves the class or module for the path +klassname+ (such as
+ # "Test::Unit::TestCase").
+
+ def path2class(klassname)
+ klassname.split('::').inject(self) { |k,n| k.const_get n }
+ end
+
+end unless Object.respond_to? :path2class
+
39 lib/action_profiler/prof_processor.rb
@@ -0,0 +1,39 @@
+require 'prof'
+require 'rubyprof_ext' # From Rails
+
+Prof.clock_mode = Prof::GETTIMEOFDAY
+
+##
+# A ProfiledProcessor that uses Shugo Maeda's Prof profiler.
+#
+# The Prof profiler requires Ruby 1.8.3 or better and can be found at
+# http://shugo.net/archive/ruby-prof/
+#
+# The Prof profiler is configured to use gettimeofday(2). There is no way to
+# change this setting.
+
+class ActionProfiler::ProfProcessor < ActionProfiler::ProfiledProcessor
+
+ def initialize(*args) # :nodoc:
+ super
+ @profile_data = nil
+ end
+
+ def start_profile # :nodoc:
+ Prof.start
+ end
+
+ ##
+ # Prof returns profile data on Prof.stop, so save it for printing.
+
+ def stop_profile # :nodoc:
+ @profile_data = Prof.stop
+ end
+
+ def print_profile(io = STDERR) # :nodoc:
+ return unless @profile_data
+ Prof.print_profile @profile_data, io
+ end
+
+end
+
206 lib/action_profiler/profiled_processor.rb
@@ -0,0 +1,206 @@
+require 'optparse'
+
+require 'action_profiler/test_processor'
+
+def debug(msg)
+ $stderr.puts msg if $AP_DEBUG
+end
+
+##
+# A Rails action processor that profiles an entire action.
+#
+# ProfiledProcessor is an abstract class. A subclasses must implement
+# #start_profile, #stop_profile and #print_profile.
+
+class ActionProfiler::ProfiledProcessor < ActionProfiler::TestProcessor
+
+ PROFILERS = ['ZenProfiler', 'Prof', 'Profiler']
+
+ ##
+ # Processes +args+ then runs a profile based on the arguments given.
+
+ def self.process_args(args = ARGV)
+ app_path = Dir.pwd
+ method = 'GET'
+ only_html = false
+ processor_klass = nil
+ times = 1
+
+ opts = OptionParser.new do |opts|
+ opts.banner = "Usage: #{File.basename $0} [options] method [params [session [flash]]]"
+
+ opts.separator ''
+ opts.separator 'method: controller and action to run "GamesController#index"'
+ opts.separator 'params, session, flash: Hash-style arguments ":id => 5"'
+ opts.separator ''
+
+ opts.on("-m", "--method=HTTP_METHOD",
+ "HTTP request method for this action",
+ "Default: #{method}") do |val|
+ method = val
+ end
+
+ opts.on("-o", "--[no-]only-html",
+ "Only output rendered page",
+ "Default: #{only_html}") do |val|
+ only_html = val
+ end
+
+ opts.on("-p", "--app-path=PATH",
+ "Path to Rails application root",
+ "Default: current directory") do |val|
+ unless File.directory? val then
+ raise OptionParser::InvalidArgument, "bad path: #{val}"
+ end
+
+ app_path = val
+ end
+
+ opts.on("-P", "--profiler=PROFILER",
+ "Profiler to use",
+ "Default: ZenProfiler, Prof then Profiler") do |val|
+ begin
+ processor_klass = load_processor val
+ rescue LoadError
+ raise OptionParser::InvalidArgument, "can't load #{val}Processor"
+ end
+ end
+
+ opts.on("-t", "--times=TIMES", Integer,
+ "Times to run the action under the profiler",
+ "Default: #{times}") do |val|
+ times = val
+ end
+
+ opts.separator ''
+ opts.on("-h", "--help", "Display this help") { STDERR.puts opts; exit 1 }
+ opts.on("-d", "--debug", "Enable debugging output") do |val|
+ $AP_DEBUG = val
+ end
+ opts.separator ''
+
+ opts.parse! args
+ end
+
+ processor_klass = load_default_processor if processor_klass.nil?
+
+ begin
+ Dir.chdir app_path
+ require 'config/environment'
+ require 'application' # HACK Rails can't find this by itself
+ rescue LoadError => e
+ debug "Application load error \"#{e.message}\""
+ raise OptionParser::InvalidArgument, "could not load application, check your path"
+ end
+
+ raise OptionParser::ParseError, "action not specified" if args.empty?
+ action = args.shift
+
+ raise OptionParser::ParseError, "too many arguments" if args.length > 3
+
+ begin
+ params, session, flash = args.map { |arg| eval "{#{arg}}" }
+ rescue Exception
+ raise OptionParser::ParseError, "invalid param/session/flash argument"
+ end
+
+ params ||= {}
+ session ||= {}
+ flash ||= {}
+
+ debug "Using #{processor_klass.inspect} processor"
+
+ pp = processor_klass.new action, method, params, session, flash, only_html
+ pp.profile times
+
+ rescue ArgumentError, OptionParser::ParseError => e
+ STDERR.puts e.message
+ debug "\t#{$!.backtrace.join("\n\t")}"
+ STDERR.puts
+ STDERR.puts opts.to_s
+ exit 1
+ end
+
+ ##
+ # Attempts to load the default profilers in order. Returns the first
+ # successfully found profiler class.
+
+ def self.load_default_processor
+ PROFILERS.each do |profiler|
+ begin
+ return load_processor(profiler)
+ rescue LoadError => e
+ end
+ end
+ raise "couldn't load any profilers, how strange, sorry about that"
+ end
+
+ ##
+ # Attempts to load a processor starting with +name+. Returns the loaded
+ # class if successful.
+
+ def self.load_processor(name)
+ debug "Loading #{name}Processor"
+ require "action_profiler/#{name.downcase}_processor"
+ return ActionProfiler.path2class("#{name}Processor")
+ rescue LoadError => e
+ debug "Failed to load #{name}Processor: #{e.message}"
+ raise
+ end
+
+ ##
+ # If +only_html+ is true then only the rendered page will be displayed and
+ # no profiling will be performed. See TestProcessor#new for the rest.
+
+ def initialize(action, method, params, session, flash, only_html)
+ super action, method, params, session, flash
+ @only_html = only_html
+ end
+
+ ##
+ # Profiles the action, running it under the profiler +times+ times after
+ # three warmup actions.
+
+ def profile(times = 1)
+ if @only_html then
+ process
+ puts @response.body
+ return
+ end
+
+ STDERR.puts "Warmup..."
+ 3.times { process }
+
+ begin
+ STDERR.puts "Profiling..."
+ start_profile
+ times.times { process }
+ stop_profile
+ ensure
+ print_profile
+ end
+ end
+
+ ##
+ # Implemented by a subclass to start the profiler it uses.
+
+ def start_profile
+ raise NotImplementedError
+ end
+
+ ##
+ # Implemented by a subclass to stop the profiler it uses.
+
+ def stop_profile
+ raise NotImplementedError
+ end
+
+ ##
+ # Implemented by a subclass to print out the profile data to +io+.
+
+ def print_profile(io)
+ raise NotImplementedError
+ end
+
+end
+
24 lib/action_profiler/profiler_processor.rb
@@ -0,0 +1,24 @@
+require 'profiler'
+
+##
+# A ProfiledProcessor that uses Ruby's built-in Profiler__ class.
+#
+# ProfilerProcessor is very slow. You really want to upgrade to Ruby 1.8.3 or
+# better and use ZenProfilerProcessor or ProfProcessor.
+
+class ActionProfiler::ProfilerProcessor < ActionProfiler::ProfiledProcessor
+
+ def start_profile # :nodoc:
+ Profiler__.start_profile
+ end
+
+ def stop_profile # :nodoc:
+ Profiler__.stop_profile
+ end
+
+ def print_profile(io = STDERR) # :nodoc:
+ Profiler__.print_profile io
+ end
+
+end
+
130 lib/action_profiler/test_processor.rb
@@ -0,0 +1,130 @@
+require 'rubygems'
+require 'action_controller'
+
+require 'action_profiler/path2class'
+
+# :stopdoc:
+
+# This exists because Rails coupled test processing to unit testing.
+
+# Don't load assertions or deprecated assertions by faking entries in $".
+gs = Gem::GemPathSearcher.new
+path = gs.find('action_controller/test_process').full_gem_path
+$" << File.join(path, 'lib', 'action_controller', 'assertions.rb')
+$" << File.join(path, 'lib', 'action_controller', 'deprecated_assertions.rb')
+
+# This lameness exists because Rails injects into Test::Unit::TestCase when
+# it should use subclasses.
+
+module Test; end
+module Test::Unit; end
+class Test::Unit::TestCase; end
+
+# :startdoc:
+
+require 'action_controller/test_process'
+
+##
+# TestProcessor is a class that exercises a Rails controller action.
+#
+# TestProcessor is heavily based on ActionPack's
+# lib/action_controller/test_process.rb
+#
+# The original can be found at: http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/test_process.rb
+#
+# The methods #process and #build_request_uri are copyright (c) 2004 David
+# Heinemeier Hansson and are used under the MIT License. All original code is
+# subject to the LICENSE file included with Action Profiler.
+#--
+# Per the MIT license:
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+class ActionProfiler::TestProcessor
+
+ ##
+ # Creates a new TestProcessor that will profile +action+ with +method+.
+ # +params+, +session+ and +flash+ are hashes for use in processing the
+ # request.
+ #
+ # +action+ is a String with the format "GamesController#index".
+ #
+ # +method+ is one of the HTTP request methods, get, post, etc.
+
+ def initialize(action, method, params = nil, session = nil, flash = nil)
+ unless action =~ /^([:\w]+Controller)#(\w+)$/ then
+ raise ArgumentError, "invalid action name"
+ end
+
+ @controller_name = $1
+ @action_name = $2
+ @method = method.downcase
+
+ @params = params
+ @session = session
+ @flash = flash
+
+ begin
+ controller_klass = Object.path2class @controller_name
+ rescue NameError
+ raise ArgumentError, "can't find controller #{@controller_name}"
+ end
+
+ @controller = controller_klass.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ ActionMailer::Base.delivery_method = :test
+ ActionMailer::Base.deliveries = []
+ end
+
+ ##
+ # Runs this action.
+
+ def process
+ @request.recycle!
+
+ @html_document = nil # Why? Who knows!
+ @request.env['REQUEST_METHOD'] = @method
+ @request.action = @action_name
+
+ @request.assign_parameters(@controller.class.controller_path, @action_name,
+ @params)
+
+ @request.session = ActionController::TestSession.new @session
+ @request.session['flash'] = ActionController::Flash::FlashHash.new
+ @request.session['flash'].update @flash
+
+ build_request_uri
+ @controller.process @request, @response
+ end
+
+ private
+
+ def build_request_uri
+ return if @request.env['REQUEST_URI']
+ options = @controller.send :rewrite_options, @params
+ options.update :only_path => true, :action => @action_name
+
+ url = ActionController::UrlRewriter.new @request, @params
+ @request.set_REQUEST_URI url.rewrite(options)
+ end
+
+end
+
24 lib/action_profiler/zenprofiler_processor.rb
@@ -0,0 +1,24 @@
+require 'zenprofile'
+
+##
+# A ProfiledProcessor that uses Ryan Davis' ZenProfiler.
+#
+# The ZenProfiler profiler requires Ruby 1.8.3 or better and RubyInline.
+# ZenProfiler can be found at http://rubyforge.org/frs/?group_id=712
+
+class ActionProfiler::ZenProfilerProcessor < ActionProfiler::ProfiledProcessor
+
+ def start_profile # :nodoc:
+ ZenProfiler.start_profile
+ end
+
+ def stop_profile # :nodoc:
+ ZenProfiler.stop_profile
+ end
+
+ def print_profile(io = STDERR) # :nodoc:
+ ZenProfiler.print_profile io
+ end
+
+end
+
Please sign in to comment.
Something went wrong with that request. Please try again.