action_profiler 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,32 @@
1
+ Portions copyright 2004 David Heinemeier Hansson.
2
+
3
+ All original code copyright 2005 Eric Hodel, The Robot Co-op. All rights
4
+ reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions
8
+ are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright
11
+ notice, this list of conditions and the following disclaimer.
12
+ 2. Redistributions in binary form must reproduce the above copyright
13
+ notice, this list of conditions and the following disclaimer in the
14
+ documentation and/or other materials provided with the distribution.
15
+ 3. Neither the names of the authors nor the names of their contributors
16
+ may be used to endorse or promote products derived from this software
17
+ without specific prior written permission.
18
+ 4. Redistribution in Rails or any sub-projects of Rails is not allowed
19
+ until Rails runs without warnings with the ``-W2'' flag enabled.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
22
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
25
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
26
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
27
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
30
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
31
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ Manifest.txt
3
+ README
4
+ Rakefile
5
+ bin/action_profiler
6
+ lib/action_profiler.rb
7
+ lib/action_profiler/path2class.rb
8
+ lib/action_profiler/prof_processor.rb
9
+ lib/action_profiler/profiled_processor.rb
10
+ lib/action_profiler/profiler_processor.rb
11
+ lib/action_profiler/test_processor.rb
12
+ lib/action_profiler/zenprofiler_processor.rb
data/README ADDED
@@ -0,0 +1,64 @@
1
+ = Action Profiler
2
+
3
+ Full Documentation:
4
+
5
+ http://rails-analyzer.rubyforge.org/action_profiler
6
+
7
+ Rubyforge Project:
8
+
9
+ http://rubyforge.org/projects/rails-analyzer
10
+
11
+ == About
12
+
13
+ Action Profiler allows you to profile a single Rails action to determine what
14
+ to optimize. You can use the Production Log Analyzer and action_grep to
15
+ determine which actions you should profile and what arguments to use.
16
+
17
+ Information on the Production Log Analyzer can be found at:
18
+
19
+ http://rails-analyzer.rubyforge.org/pl_analyze
20
+
21
+ === Profilers
22
+
23
+ action_profiler can use three profilers, Ruby's builtin profiler class,
24
+ Shugo Maeda's Prof or Ryan Davis' ZenProfile. For the last two profilers you
25
+ will need Ruby 1.8.3 or better.
26
+
27
+ === Running Action Profiler
28
+
29
+ Typically, action_profiler will be run from the root of your Rails
30
+ application:
31
+
32
+ $ action_profiler GamesController#index
33
+ Warmup...
34
+ Profiling...
35
+ [ profile output ]
36
+ $
37
+
38
+ If you need to run action_profiler from some other path, the -p command line
39
+ option can be used to specify the location of your Rails application.
40
+
41
+ action_profiler -p ~/Worx/X/CCR GamesController#index
42
+
43
+ Paramaters can be specified after the controller and action:
44
+
45
+ action_profiler GamesController#index ":id => 1"
46
+
47
+ If you need to make sure a page is working correctly you can specify -o and no
48
+ profiling will occur and the generated page will be printed out instead:
49
+
50
+ $ action_profiler -o GamesController#show ":id => 1"
51
+ <html>
52
+ [ lots of HTML output ]
53
+ $
54
+
55
+ == Gem Installation
56
+
57
+ gem install action_profiler
58
+
59
+ == Download
60
+
61
+ http://rubyforge.org/frs/?group_id=586
62
+
63
+ (Sorry, no manual installation script is available for the .tgz)
64
+
@@ -0,0 +1,61 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+ require 'rake/contrib/sshpublisher'
7
+
8
+ $VERBOSE = nil
9
+
10
+ spec = Gem::Specification.new do |s|
11
+ s.name = 'action_profiler'
12
+ s.version = '1.0.0'
13
+ s.summary = 'A profiler for Rails controllers'
14
+ s.author = 'Eric Hodel'
15
+ s.email = 'eric@robotcoop.com'
16
+
17
+ s.has_rdoc = true
18
+ s.files = File.read('Manifest.txt').split($/)
19
+ s.require_path = 'lib'
20
+ s.executables = ['action_profiler']
21
+ s.default_executable = 'action_profiler'
22
+ end
23
+
24
+ desc 'Run tests'
25
+ task :default => [ :test ]
26
+
27
+ Rake::TestTask.new('test') do |t|
28
+ t.libs << 'test'
29
+ t.pattern = 'test/test_*.rb'
30
+ t.verbose = true
31
+ end
32
+
33
+ desc 'Generate RDoc'
34
+ Rake::RDocTask.new :rdoc do |rd|
35
+ rd.rdoc_dir = 'doc'
36
+ rd.rdoc_files.add 'lib', 'README', 'LICENSE'
37
+ rd.main = 'README'
38
+ rd.options << '-d' if `which dot` =~ /\/dot/
39
+ end
40
+
41
+ desc 'Build Gem'
42
+ Rake::GemPackageTask.new spec do |pkg|
43
+ pkg.need_tar = true
44
+ end
45
+
46
+ desc 'Sends RDoc to RubyForge'
47
+ task :send_rdoc => [ :rerdoc ] do
48
+ publisher = Rake::SshDirPublisher.new('drbrain@rubyforge.org',
49
+ '/var/www/gforge-projects/rails-analyzer/action_profiler',
50
+ 'doc')
51
+ publisher.upload
52
+ end
53
+
54
+ desc 'Clean up'
55
+ task :clean => [ :clobber_rdoc, :clobber_package ]
56
+
57
+ desc 'Clean up'
58
+ task :clobber => [ :clean ]
59
+
60
+ # vim: syntax=Ruby
61
+
@@ -0,0 +1,6 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ require 'action_profiler'
4
+
5
+ ProfiledProcessor.process_args ARGV
6
+
@@ -0,0 +1,3 @@
1
+ # This project has lame names.
2
+ require 'action_profiler/profiled_processor'
3
+
@@ -0,0 +1,12 @@
1
+ class Object
2
+
3
+ ##
4
+ # Retrieves the class or module for the path +klassname+ (such as
5
+ # "Test::Unit::TestCase").
6
+
7
+ def path2class(klassname)
8
+ klassname.split('::').inject(Object) { |k,n| k.const_get n }
9
+ end
10
+
11
+ end
12
+
@@ -0,0 +1,39 @@
1
+ require 'prof'
2
+ require 'rubyprof_ext' # From Rails
3
+
4
+ Prof.clock_mode = Prof::GETTIMEOFDAY
5
+
6
+ ##
7
+ # A ProfiledProcessor that uses Shugo Maeda's Prof profiler.
8
+ #
9
+ # The Prof profiler requires Ruby 1.8.3 or better and can be found at
10
+ # http://shugo.net/archive/ruby-prof/
11
+ #
12
+ # The Prof profiler is configured to use gettimeofday(2). There is no way to
13
+ # change this setting.
14
+
15
+ class ProfProcessor < ProfiledProcessor
16
+
17
+ def initialize(*args) # :nodoc:
18
+ super
19
+ @profile_data = nil
20
+ end
21
+
22
+ def start_profile # :nodoc:
23
+ Prof.start
24
+ end
25
+
26
+ ##
27
+ # Prof returns profile data on Prof.stop, so save it for printing.
28
+
29
+ def stop_profile # :nodoc:
30
+ @profile_data = Prof.stop
31
+ end
32
+
33
+ def print_profile(io = STDERR) # :nodoc:
34
+ return unless @profile_data
35
+ Prof.print_profile @profile_data, io
36
+ end
37
+
38
+ end
39
+
@@ -0,0 +1,208 @@
1
+ require 'optparse'
2
+
3
+ require 'action_profiler/test_processor'
4
+
5
+ def debug(msg)
6
+ STDERR.puts msg if $AP_DEBUG
7
+ end
8
+
9
+ ##
10
+ # A Rails action processor that profiles an entire action.
11
+ #
12
+ # ProfiledProcessor is an abstract class. A subclasses must implement
13
+ # #start_profile, #stop_profile and #print_profile.
14
+
15
+ class ProfiledProcessor < TestProcessor
16
+
17
+ PROFILERS = ['ZenProfiler', 'Prof', 'Profiler']
18
+
19
+ ##
20
+ # Processes +args+ then runs a profile based on the arguments given.
21
+
22
+ def self.process_args(args = ARGV)
23
+ app_path = Dir.pwd
24
+ method = 'GET'
25
+ only_html = false
26
+ processor_klass = nil
27
+ times = 1
28
+
29
+ opts = OptionParser.new do |opts|
30
+ opts.banner = "Usage: #{File.basename $0} [options] method [params [session [flash]]]"
31
+
32
+ opts.separator ''
33
+ opts.separator 'method: controller and action to run "GamesController#index"'
34
+ opts.separator 'params, session, flash: Hash-style arguments ":id => 5"'
35
+ opts.separator ''
36
+
37
+ opts.on("-m", "--method=HTTP_METHOD",
38
+ "HTTP request method for this action",
39
+ "Default: #{method}") do |val|
40
+ method = val
41
+ end
42
+
43
+ opts.on("-o", "--[no-]only-html",
44
+ "Only output rendered page",
45
+ "Default: #{only_html}") do |val|
46
+ only_html = val
47
+ end
48
+
49
+ opts.on("-p", "--app-path=PATH",
50
+ "Path to Rails application root",
51
+ "Default: current directory") do |val|
52
+ unless File.directory? val then
53
+ raise OptionParser::InvalidArgument, "bad path: #{val}"
54
+ end
55
+
56
+ app_path = val
57
+ end
58
+
59
+ opts.on("-P", "--profiler=PROFILER",
60
+ "Profiler to use",
61
+ "Default: ZenProfiler, Prof then Profiler") do |val|
62
+ begin
63
+ processor_klass = load_processor val
64
+ rescue LoadError
65
+ raise OptionParser::InvalidArgument, "can't load #{val}_processor"
66
+ end
67
+ end
68
+
69
+ opts.on("-t", "--times=TIMES", Integer,
70
+ "Times to run the action under the profiler",
71
+ "Default: #{times}") do |val|
72
+ times = val
73
+ end
74
+
75
+ opts.separator ''
76
+ opts.on("-h", "--help", "Display this help") { STDERR.puts opts; exit 1 }
77
+ opts.on("-d", "--debug", "Enable debugging output") do |val|
78
+ $AP_DEBUG = val
79
+ end
80
+ opts.separator ''
81
+
82
+ opts.parse! args
83
+ end
84
+
85
+ processor_klass = load_default_processor if processor_klass.nil?
86
+
87
+ begin
88
+ Dir.chdir app_path
89
+ require 'config/environment'
90
+ require 'application' # HACK Rails can't find this by itself
91
+ rescue LoadError => e
92
+ debug "Application load error \"#{e.message}\""
93
+ raise OptionParser::InvalidArgument, "could not load application, check your path"
94
+ end
95
+
96
+ raise OptionParser::ParseError, "action not specified" if args.empty?
97
+ action = args.shift
98
+
99
+ raise OptionParser::ParseError, "too many arguments" if args.length > 3
100
+
101
+ begin
102
+ params, session, flash = args.map { |arg| eval "{#{arg}}" }
103
+ rescue Exception
104
+ raise OptionParser::ParseError, "invalid param/session/flash argument"
105
+ end
106
+
107
+ params ||= {}
108
+ session ||= {}
109
+ flash ||= {}
110
+
111
+ debug "Using #{processor_klass.inspect} processor"
112
+
113
+ pp = processor_klass.new action, method, params, session, flash, only_html
114
+ pp.profile times
115
+
116
+ rescue ArgumentError, OptionParser::ParseError => e
117
+ STDERR.puts e.message
118
+ debug "\t#{$!.backtrace.join("\n\t")}"
119
+ STDERR.puts
120
+ STDERR.puts opts.to_s
121
+ exit 1
122
+ end
123
+
124
+ ##
125
+ # Attempts to load the default profilers in order. Returns the first
126
+ # successfully found profiler class.
127
+
128
+ def self.load_default_processor
129
+ PROFILERS.each do |profiler|
130
+ begin
131
+ return load_processor(profiler)
132
+ rescue LoadError => e
133
+ end
134
+ end
135
+ raise "couldn't load any profilers, how strange, sorry about that"
136
+ end
137
+
138
+ ##
139
+ # Attempts to load a processor starting with +name+. Returns the loaded
140
+ # class if successful.
141
+
142
+ def self.load_processor(name)
143
+ debug "Loading #{name}Processor"
144
+ # HACK I have no fucking clue how or why Rails' require mucks shit up,
145
+ # nor can I reproduce it in a small testcase.
146
+ require__ "action_profiler/#{name.downcase}_processor"
147
+ return Object.path2class("#{name}Processor")
148
+ rescue LoadError => e
149
+ debug "Failed to load #{name}Processor: #{e.message}"
150
+ raise
151
+ end
152
+
153
+ ##
154
+ # If +only_html+ is true then only the rendered page will be displayed and
155
+ # no profiling will be performed. See TestProcessor#new for the rest.
156
+
157
+ def initialize(action, method, params, session, flash, only_html)
158
+ super action, method, params, session, flash
159
+ @only_html = only_html
160
+ end
161
+
162
+ ##
163
+ # Profiles the action, running it under the profiler +times+ times after
164
+ # three warmup actions.
165
+
166
+ def profile(times = 1)
167
+ if @only_html then
168
+ process
169
+ puts @response.body
170
+ return
171
+ end
172
+
173
+ STDERR.puts "Warmup..."
174
+ 3.times { process }
175
+
176
+ begin
177
+ STDERR.puts "Profiling..."
178
+ start_profile
179
+ times.times { process }
180
+ stop_profile
181
+ ensure
182
+ print_profile
183
+ end
184
+ end
185
+
186
+ ##
187
+ # Implemented by a subclass to start the profiler it uses.
188
+
189
+ def start_profile
190
+ raise NotImplementedError
191
+ end
192
+
193
+ ##
194
+ # Implemented by a subclass to stop the profiler it uses.
195
+
196
+ def stop_profile
197
+ raise NotImplementedError
198
+ end
199
+
200
+ ##
201
+ # Implemented by a subclass to print out the profile data to +io+.
202
+
203
+ def print_profile(io)
204
+ raise NotImplementedError
205
+ end
206
+
207
+ end
208
+
@@ -0,0 +1,24 @@
1
+ require 'profiler'
2
+
3
+ ##
4
+ # A ProfiledProcessor that uses Ruby's built-in Profiler__ class.
5
+ #
6
+ # ProfilerProcessor is very slow. You really want to upgrade to Ruby 1.8.3 or
7
+ # better and use ZenProfilerProcessor or ProfProcessor.
8
+
9
+ class ProfilerProcessor < ProfiledProcessor
10
+
11
+ def start_profile # :nodoc:
12
+ Profiler__.start_profile
13
+ end
14
+
15
+ def stop_profile # :nodoc:
16
+ Profiler__.stop_profile
17
+ end
18
+
19
+ def print_profile(io = STDERR) # :nodoc:
20
+ Profiler__.print_profile io
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1,130 @@
1
+ require 'rubygems'
2
+ require 'action_controller'
3
+
4
+ require 'action_profiler/path2class'
5
+
6
+ # :stopdoc:
7
+
8
+ # This exists because Rails coupled test processing to unit testing.
9
+
10
+ # Don't load assertions or deprecated assertions by faking entries in $".
11
+ gs = Gem::GemPathSearcher.new
12
+ path = gs.find('action_controller/test_process').full_gem_path
13
+ $" << File.join(path, 'lib', 'action_controller', 'assertions.rb')
14
+ $" << File.join(path, 'lib', 'action_controller', 'deprecated_assertions.rb')
15
+
16
+ # This lameness exists because Rails injects into Test::Unit::TestCase when
17
+ # it should use subclasses.
18
+
19
+ module Test; end
20
+ module Test::Unit; end
21
+ class Test::Unit::TestCase; end
22
+
23
+ # :startdoc:
24
+
25
+ require 'action_controller/test_process'
26
+
27
+ ##
28
+ # TestProcessor is a class that exercises a Rails controller action.
29
+ #
30
+ # TestProcessor is heavily based on ActionPack's
31
+ # lib/action_controller/test_process.rb
32
+ #
33
+ # The original can be found at: http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/test_process.rb
34
+ #
35
+ # The methods #process and #build_request_uri are copyright (c) 2004 David
36
+ # Heinemeier Hansson and are used under the MIT License. All original code is
37
+ # subject to the LICENSE file included with Action Profiler.
38
+ #--
39
+ # Per the MIT license:
40
+ #
41
+ # Permission is hereby granted, free of charge, to any person obtaining
42
+ # a copy of this software and associated documentation files (the
43
+ # "Software"), to deal in the Software without restriction, including
44
+ # without limitation the rights to use, copy, modify, merge, publish,
45
+ # distribute, sublicense, and/or sell copies of the Software, and to
46
+ # permit persons to whom the Software is furnished to do so, subject to
47
+ # the following conditions:
48
+ #
49
+ # The above copyright notice and this permission notice shall be
50
+ # included in all copies or substantial portions of the Software.
51
+ #
52
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
53
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
54
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
55
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
56
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
57
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
58
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
59
+
60
+ class TestProcessor
61
+
62
+ ##
63
+ # Creates a new TestProcessor that will profile +action+ with +method+.
64
+ # +params+, +session+ and +flash+ are hashes for use in processing the
65
+ # request.
66
+ #
67
+ # +action+ is a String with the format "GamesController#index".
68
+ #
69
+ # +method+ is one of the HTTP request methods, get, post, etc.
70
+
71
+ def initialize(action, method, params = nil, session = nil, flash = nil)
72
+ unless action =~ /^([:\w]+Controller)#(\w+)$/ then
73
+ raise ArgumentError, "invalid action name"
74
+ end
75
+
76
+ @controller_name = $1
77
+ @action_name = $2
78
+ @method = method.downcase
79
+
80
+ @params = params
81
+ @session = session
82
+ @flash = flash
83
+
84
+ begin
85
+ controller_klass = Object.path2class @controller_name
86
+ rescue NameError
87
+ raise ArgumentError, "can't find controller #{@controller_name}"
88
+ end
89
+
90
+ @controller = controller_klass.new
91
+ @request = ActionController::TestRequest.new
92
+ @response = ActionController::TestResponse.new
93
+ ActionMailer::Base.delivery_method = :test
94
+ ActionMailer::Base.deliveries = []
95
+ end
96
+
97
+ ##
98
+ # Runs this action.
99
+
100
+ def process
101
+ @request.recycle!
102
+
103
+ @html_document = nil # Why? Who knows!
104
+ @request.env['REQUEST_METHOD'] = @method
105
+ @request.action = @action_name
106
+
107
+ @request.assign_parameters(@controller.class.controller_path, @action_name,
108
+ @params)
109
+
110
+ @request.session = ActionController::TestSession.new @session
111
+ @request.session['flash'] = ActionController::Flash::FlashHash.new
112
+ @request.session['flash'].update @flash
113
+
114
+ build_request_uri
115
+ @controller.process @request, @response
116
+ end
117
+
118
+ private
119
+
120
+ def build_request_uri
121
+ return if @request.env['REQUEST_URI']
122
+ options = @controller.send :rewrite_options, @params
123
+ options.update :only_path => true, :action => @action_name
124
+
125
+ url = ActionController::UrlRewriter.new @request, @params
126
+ @request.set_REQUEST_URI url.rewrite(options)
127
+ end
128
+
129
+ end
130
+
@@ -0,0 +1,24 @@
1
+ require 'zenprofile'
2
+
3
+ ##
4
+ # A ProfiledProcessor that uses Ryan Davis' ZenProfiler.
5
+ #
6
+ # The ZenProfiler profiler requires Ruby 1.8.3 or better and RubyInline.
7
+ # ZenProfiler can be found at http://rubyforge.org/frs/?group_id=712
8
+
9
+ class ZenProfilerProcessor < ProfiledProcessor
10
+
11
+ def start_profile # :nodoc:
12
+ ZenProfiler.start
13
+ end
14
+
15
+ def stop_profile # :nodoc:
16
+ ZenProfiler.stop
17
+ end
18
+
19
+ def print_profile(io = STDERR) # :nodoc:
20
+ ZenProfiler.print_profile io
21
+ end
22
+
23
+ end
24
+
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: action_profiler
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2005-11-15 00:00:00 -08:00
8
+ summary: A profiler for Rails controllers
9
+ require_paths:
10
+ - lib
11
+ email: eric@robotcoop.com
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable: action_profiler
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ authors:
30
+ - Eric Hodel
31
+ files:
32
+ - LICENSE
33
+ - Manifest.txt
34
+ - README
35
+ - Rakefile
36
+ - bin/action_profiler
37
+ - lib/action_profiler.rb
38
+ - lib/action_profiler/path2class.rb
39
+ - lib/action_profiler/prof_processor.rb
40
+ - lib/action_profiler/profiled_processor.rb
41
+ - lib/action_profiler/profiler_processor.rb
42
+ - lib/action_profiler/test_processor.rb
43
+ - lib/action_profiler/zenprofiler_processor.rb
44
+ test_files: []
45
+ rdoc_options: []
46
+ extra_rdoc_files: []
47
+ executables:
48
+ - action_profiler
49
+ extensions: []
50
+ requirements: []
51
+ dependencies: []