rack-perftools_profiler 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +10 -0
- data/Gemfile +3 -0
- data/{README.rdoc → README.md} +100 -83
- data/Rakefile +1 -55
- data/lib/rack/perftools_profiler/action.rb +21 -17
- data/lib/rack/perftools_profiler/profiler.rb +17 -5
- data/lib/rack/perftools_profiler/version.rb +5 -0
- data/rack-perftools_profiler.gemspec +25 -68
- data/test/multiple_request_profiling_test.rb +116 -16
- data/test/single_request_profiling_test.rb +42 -24
- data/test/test_helper.rb +12 -6
- metadata +83 -111
data/.gitignore
ADDED
data/Gemfile
ADDED
data/{README.rdoc → README.md}
RENAMED
@@ -1,146 +1,160 @@
|
|
1
|
-
|
1
|
+
# Rack::PerftoolsProfiler
|
2
2
|
|
3
|
-
Middleware for profiling Rack-compatible apps using perftools.rb
|
3
|
+
Middleware for profiling Rack-compatible apps using [perftools.rb](http://github.com/tmm1/perftools.rb)
|
4
4
|
|
5
|
-
|
5
|
+
## Quick start
|
6
|
+
|
7
|
+
Assuming your application is using Rails 3 (and you have installed the requirements in the next section), add the following code:
|
8
|
+
|
9
|
+
Gemfile:
|
10
|
+
|
11
|
+
gem 'rack-perftools_profiler', :require => 'rack/perftools_profiler'
|
12
|
+
|
13
|
+
config/environment.rb:
|
14
|
+
|
15
|
+
config.middleware.use ::Rack::PerftoolsProfiler, :default_printer => 'gif', :bundler => true
|
16
|
+
|
17
|
+
The visit the page you want to profile:
|
18
|
+
|
19
|
+
http://localhost:3000/some_action?profile=true
|
20
|
+
|
21
|
+
## Requirements
|
6
22
|
|
7
23
|
You'll need graphviz to generate call graphs using dot (for the GIF printer):
|
8
24
|
|
9
|
-
|
10
|
-
|
11
|
-
|
25
|
+
sudo port install graphviz # OS X
|
26
|
+
brew install graphviz # Homebrew
|
27
|
+
sudo apt-get install graphviz # Debian/Ubuntu
|
12
28
|
|
13
29
|
You'll need ps2pdf to generate PDFs (On OS X, ps2pdf comes is installed as part of Ghostscript)
|
14
|
-
|
15
|
-
sudo port install ghostscript # OSX
|
16
|
-
brew install ghostscript # Homebrew
|
17
|
-
sudo apt-get install ps2pdf # Debian/Ubuntu
|
18
30
|
|
19
|
-
|
31
|
+
sudo port install ghostscript # OSX
|
32
|
+
brew install ghostscript # Homebrew
|
33
|
+
sudo apt-get install ps2pdf # Debian/Ubuntu
|
34
|
+
|
35
|
+
## Configuration
|
20
36
|
|
21
37
|
Install the gem
|
22
38
|
|
23
|
-
|
39
|
+
gem install rack-perftools_profiler
|
24
40
|
|
25
41
|
Include the middleware
|
26
42
|
|
27
|
-
|
43
|
+
require 'rack/perftools_profiler'
|
28
44
|
|
29
|
-
For Rails 2, add the following to config/environment.rb
|
45
|
+
For Rails 2, add the following to `config/environment.rb`
|
30
46
|
|
31
|
-
|
32
|
-
|
33
|
-
|
47
|
+
config.gem 'rack-perftools_profiler', :lib => 'rack/perftools_profiler'
|
48
|
+
require 'rack/perftools_profiler'
|
49
|
+
config.middleware.use ::Rack::PerftoolsProfiler, :default_printer => 'gif'
|
34
50
|
|
35
51
|
For Rails 3, add the following to your Gemfile
|
36
52
|
|
37
|
-
|
53
|
+
gem 'rack-perftools_profiler', :require => 'rack/perftools_profiler'
|
38
54
|
|
39
55
|
and add the following to config/application.rb
|
40
56
|
|
41
|
-
|
57
|
+
config.middleware.use ::Rack::PerftoolsProfiler, :default_printer => 'gif', :bundler => true
|
42
58
|
|
43
|
-
For Sinatra, call
|
59
|
+
For Sinatra, call `use` inside a configure block, like so:
|
44
60
|
|
45
|
-
|
46
|
-
|
47
|
-
|
61
|
+
configure do
|
62
|
+
use ::Rack::PerftoolsProfiler, :default_printer => 'gif'
|
63
|
+
end
|
48
64
|
|
49
|
-
For Rack::Builder, call
|
65
|
+
For Rack::Builder, call `use` inside the Builder constructor block
|
50
66
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
== Options
|
67
|
+
Rack::Builder.new do
|
68
|
+
use ::Rack::PerftoolsProfiler, :default_printer => 'gif'
|
69
|
+
end
|
56
70
|
|
57
|
-
|
58
|
-
* :mode - can be set to 'cputime', 'methods', 'objects', 'walltime'. Default is :cputime. See the 'Profiling Modes' section below.
|
59
|
-
* :frequency - in :cputime mode, the number of times per second the app will be sampled. Default is 100 (times/sec).
|
60
|
-
* :bundler - run the profiler binary using 'bundle' if set to true. Default is false.
|
61
|
-
* :gemfile_dir - directory with Gemfile. Default is the current directory.
|
62
|
-
* :password - password-protect profiling.
|
71
|
+
## Options
|
63
72
|
|
64
|
-
|
73
|
+
* `:default_printer` - can be set to 'text', 'gif', or 'pdf'. Default is 'text'.
|
74
|
+
* `:mode` - can be set to 'cputime', 'methods', 'objects', 'walltime'. Default is :cputime. See the 'Profiling Modes' section below.
|
75
|
+
* `:frequency` - in :cputime mode, the number of times per second the app will be sampled. Default is 100 (times/sec).
|
76
|
+
* `:bundler` - run the profiler binary using 'bundle' if set to true. Default is false.
|
77
|
+
* `:gemfile_dir` - directory with Gemfile. Default is the current directory.
|
78
|
+
* `:password` - password-protect profiling.
|
79
|
+
|
80
|
+
## Usage
|
65
81
|
|
66
82
|
There are two ways to profile your app: with a single request or with multiple requests.
|
67
83
|
|
68
|
-
To profile with a single request, visit the URL you want to profile, but
|
69
|
-
|
70
|
-
|
84
|
+
To profile with a single request, visit the URL you want to profile, but add the `profile` and (optionally) the `times` GET params (which will rerun the action the specified number of times).
|
85
|
+
|
86
|
+
Example:
|
71
87
|
|
72
|
-
|
73
|
-
curl http://localhost:3000/foobar?profile=true×=3
|
88
|
+
curl http://localhost:3000/foobar?profile=true×=3
|
74
89
|
|
75
90
|
Note that this will change the status, body, and headers of the response (you'll get
|
76
91
|
back the profiling data, NOT the original response).
|
77
92
|
|
78
|
-
You can also profile your application using multiple requests. When you profile using this method,
|
79
|
-
all responses are normal. You must visit \_\_stop\_\_ to complete profiling and then you can view
|
80
|
-
the profiling data by visiting \_\_data\_\_.
|
93
|
+
You can also profile your application using multiple requests. When you profile using this method, all responses are normal. You must visit `__stop__` to complete profiling and then you can view the profiling data by visiting `__data__`.
|
81
94
|
|
82
95
|
Example:
|
83
|
-
curl http://localhost:3000/__start__
|
84
|
-
curl http://localhost:3000/foobar
|
85
|
-
curl http://localhost:3000/foobaz
|
86
|
-
curl http://localhost:3000/__stop__
|
87
|
-
curl http://localhost:3000/__data__
|
88
96
|
|
89
|
-
|
97
|
+
curl http://localhost:3000/__start__
|
98
|
+
curl http://localhost:3000/foobar
|
99
|
+
curl http://localhost:3000/foobaz
|
100
|
+
curl http://localhost:3000/__stop__
|
101
|
+
curl http://localhost:3000/__data__
|
102
|
+
|
103
|
+
## Profiling Data Options
|
90
104
|
|
91
|
-
Regardless of how you profile your application, you can add additional params to change how the
|
92
|
-
data is displayed. When using a single request, these params are just added to the URL being profiled.
|
93
|
-
When using multiple requests, they are added to the
|
105
|
+
Regardless of how you profile your application, you can add additional params to change how the
|
106
|
+
data is displayed. When using a single request, these params are just added to the URL being profiled.
|
107
|
+
When using multiple requests, they are added to the `__data__` URL.
|
94
108
|
|
95
109
|
* printer - overrides the default_printer option (see above)
|
96
|
-
* ignore - a regular expression of the area of code to ignore
|
110
|
+
* ignore - a regular expression of the area of code to ignore
|
97
111
|
* focus - a regular expression of the area of code to solely focus on.
|
98
112
|
|
99
113
|
(for 'ignore' and 'focus', please see http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html#pprof
|
100
114
|
for more details)
|
101
115
|
|
102
|
-
|
116
|
+
## Profiling Modes
|
103
117
|
|
104
118
|
perftools.rb (and therefore, the Rack middleware) can be put into three different profiling modes.
|
105
119
|
|
106
|
-
* CPU time mode - Reports how many CPU cycles are spent in each section of code. This is the default and can be enabled by setting
|
107
|
-
* Method call mode - Report how many method calls are made inside each method. Enable by setting
|
108
|
-
* Object allocation mode - Reports the percentage of object allocations performed in each section of code. Enable by setting
|
109
|
-
* Wall time mode - Reports the amount of time (as in, wall clock time) spent in each section of code. Enable by setting
|
120
|
+
* CPU time mode - Reports how many CPU cycles are spent in each section of code. This is the default and can be enabled by setting `:mode => :cputime`
|
121
|
+
* Method call mode - Report how many method calls are made inside each method. Enable by setting `:mode => :methods`
|
122
|
+
* Object allocation mode - Reports the percentage of object allocations performed in each section of code. Enable by setting `:mode => :objects`
|
123
|
+
* Wall time mode - Reports the amount of time (as in, wall clock time) spent in each section of code. Enable by setting `:mode => :walltime`
|
110
124
|
|
111
125
|
For example, consider the following Sinatra application:
|
112
126
|
|
113
|
-
|
114
|
-
|
127
|
+
require 'sinatra'
|
128
|
+
require 'rack/perftools_profiler'
|
115
129
|
|
116
|
-
|
117
|
-
|
118
|
-
|
130
|
+
configure do
|
131
|
+
use ::Rack::PerftoolsProfiler, :default_printer => 'gif', :mode => :cputime
|
132
|
+
end
|
119
133
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
134
|
+
get "/slow" do
|
135
|
+
sleep(3)
|
136
|
+
"hello"
|
137
|
+
end
|
124
138
|
|
125
139
|
In the default mode, there will be no profiling data for the 'slow' route, because it uses few CPU cycles (You'll see the message 'No nodes to print').
|
126
140
|
|
127
|
-
If you change the mode to
|
141
|
+
If you change the mode to `:walltime`, you'll get profiling data, since the call to `sleep` causes the code to spend several seconds of wall time in the block.
|
128
142
|
|
129
|
-
|
143
|
+
## Overriding the Profiling mode
|
130
144
|
|
131
145
|
You can also switch the profiling mode on a per-request basis, but ONLY if you are switching to 'methods' or 'objects' mode. Due to the implementation of perftools.rb, it is NOT possible to switch to 'walltime' or 'cputime' modes.
|
132
146
|
|
133
147
|
To switch to another mode, provide the 'mode' option. When profiling with a single request, add the option to the URL profiled:
|
134
148
|
|
135
|
-
|
149
|
+
curl http://localhost:3000/foobar?profile=true&mode=objects
|
136
150
|
|
137
|
-
When profiling using multiple requests, add the option when visiting
|
151
|
+
When profiling using multiple requests, add the option when visiting `__start__` :
|
138
152
|
|
139
|
-
|
153
|
+
curl http://localhost:3000/__start__?mode=objects
|
140
154
|
|
141
155
|
If the 'mode' option is omitted, the middleware will default to the mode specified at configuration.
|
142
156
|
|
143
|
-
|
157
|
+
## Profiling in production
|
144
158
|
|
145
159
|
It is recommended that you always profile your application in the 'production' environment (using `rails server -e production` or an equivalent), since there can be important differences between 'development' and 'production' that may affect performance.
|
146
160
|
|
@@ -148,31 +162,34 @@ However, it is recommended that you profile your application on a development or
|
|
148
162
|
|
149
163
|
Profiling a single request will work if there are multiple server processes. If your staging machine is publicly accessible, you can password-protect single-request profiling by using the `:password` option and then using the `profile` GET parameter to provide the password:
|
150
164
|
|
151
|
-
|
165
|
+
curl http://localhost:3000/foobar?profile=PASSWORD
|
152
166
|
|
153
|
-
|
167
|
+
## Changing behavior with environment variables
|
154
168
|
|
155
169
|
The mode and frequency settings are enabled by setting environment variables. Some of these environment variables must be set before 'perftools' is required. If you only require 'rack/perftools_profiler', it will do the right thing (require 'perftools' after setting the environment variables).
|
156
170
|
|
157
171
|
If you need to require 'perftools' before 'rack/perftools_profiler' (or you have other problems changing the mode or frequency), try using these environment variables yourself.
|
158
172
|
|
159
173
|
Setting the frequency:
|
160
|
-
|
174
|
+
|
175
|
+
CPUPROFILE_FREQUENCY=500 ruby your_app.rb
|
161
176
|
|
162
177
|
Setting the mode to 'wall time'
|
163
|
-
|
178
|
+
|
179
|
+
CPUPROFILE_REALTIME=1 ruby your_app.rb
|
164
180
|
|
165
181
|
Setting the mode to 'object allocation'
|
166
|
-
CPUPROFILE_OBJECTS=1 ruby your_app.rb
|
167
182
|
|
168
|
-
|
183
|
+
CPUPROFILE_OBJECTS=1 ruby your_app.rb
|
184
|
+
|
185
|
+
## Acknowledgments
|
169
186
|
|
170
187
|
A huge thanks to Aman Gupta for the awesome perftools.rb gem.
|
171
188
|
|
172
189
|
The basic idea and initial implementation of the middleware was heavily influenced by Rack::Profiler from rack-contrib.
|
173
190
|
|
174
|
-
|
175
|
-
|
191
|
+
## Note on Patches/Pull Requests
|
192
|
+
|
176
193
|
* Fork the project.
|
177
194
|
* Make your feature addition or bug fix.
|
178
195
|
* Add tests for it. This is important so I don't break it in a
|
@@ -181,6 +198,6 @@ The basic idea and initial implementation of the middleware was heavily influenc
|
|
181
198
|
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
182
199
|
* Send me a pull request. Bonus points for topic branches.
|
183
200
|
|
184
|
-
|
201
|
+
## Copyright
|
185
202
|
|
186
203
|
Copyright (c) 2010-2011 Ben Brinckerhoff. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -1,27 +1,4 @@
|
|
1
|
-
require
|
2
|
-
require 'rake'
|
3
|
-
|
4
|
-
begin
|
5
|
-
require 'jeweler'
|
6
|
-
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name = 'rack-perftools_profiler'
|
8
|
-
gem.summary = %Q{Middleware for profiling Rack-compatible apps using perftools.rb}
|
9
|
-
gem.description = %Q{Middleware for profiling Rack-compatible apps using perftools.rb}
|
10
|
-
gem.email = 'ben@bbrinck.com'
|
11
|
-
gem.homepage = 'http://github.com/bhb/rack-perftools_profiler'
|
12
|
-
gem.authors = ['Ben Brinckerhoff']
|
13
|
-
gem.add_dependency 'perftools.rb', '~> 0.5.6'
|
14
|
-
gem.add_dependency 'rack', '~> 1.0'
|
15
|
-
gem.add_dependency('open4', '~> 1.0')
|
16
|
-
gem.add_development_dependency 'rack', '~> 1.1'
|
17
|
-
gem.add_development_dependency 'shoulda', '~> 2.10'
|
18
|
-
gem.add_development_dependency 'mocha', '~> 0.9'
|
19
|
-
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
20
|
-
end
|
21
|
-
Jeweler::GemcutterTasks.new
|
22
|
-
rescue LoadError
|
23
|
-
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
24
|
-
end
|
1
|
+
require "bundler/gem_tasks"
|
25
2
|
|
26
3
|
require 'rake/testtask'
|
27
4
|
Rake::TestTask.new(:test) do |test|
|
@@ -29,34 +6,3 @@ Rake::TestTask.new(:test) do |test|
|
|
29
6
|
test.pattern = 'test/**/*_test.rb'
|
30
7
|
test.verbose = true
|
31
8
|
end
|
32
|
-
|
33
|
-
begin
|
34
|
-
require 'rcov/rcovtask'
|
35
|
-
Rcov::RcovTask.new do |test|
|
36
|
-
test.libs << 'test'
|
37
|
-
test.pattern = 'test/**/*_test.rb'
|
38
|
-
test.verbose = true
|
39
|
-
end
|
40
|
-
rescue LoadError
|
41
|
-
task :rcov do
|
42
|
-
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
task :test => :check_dependencies
|
47
|
-
|
48
|
-
task :default => :test
|
49
|
-
|
50
|
-
require 'rake/rdoctask'
|
51
|
-
Rake::RDocTask.new do |rdoc|
|
52
|
-
if File.exist?('VERSION')
|
53
|
-
version = File.read('VERSION')
|
54
|
-
else
|
55
|
-
version = ""
|
56
|
-
end
|
57
|
-
|
58
|
-
rdoc.rdoc_dir = 'rdoc'
|
59
|
-
rdoc.title = "rack-perftools_profiler #{version}"
|
60
|
-
rdoc.rdoc_files.include('README*')
|
61
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
62
|
-
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Rack::PerftoolsProfiler
|
2
|
-
|
2
|
+
|
3
3
|
class Action
|
4
4
|
|
5
5
|
def initialize(env, profiler, middleware)
|
@@ -9,35 +9,39 @@ module Rack::PerftoolsProfiler
|
|
9
9
|
@profiler = profiler
|
10
10
|
@middleware = middleware
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def act
|
14
14
|
# do nothing
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.for_env(env, profiler, middleware)
|
18
18
|
request = Rack::Request.new(env)
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
password = request.GET['profile']
|
20
|
+
accepted = profiler.accepts?(password)
|
21
|
+
klass =
|
22
|
+
case request.path_info
|
23
|
+
when %r{/__start__$}
|
24
|
+
password_protect(StartProfiling, accepted)
|
25
|
+
when %r{/__stop__$}
|
26
|
+
password_protect(StopProfiling, accepted)
|
27
|
+
when %r{/__data__$}
|
28
|
+
password_protect(ReturnData, accepted)
|
22
29
|
else
|
23
|
-
|
24
|
-
|
25
|
-
StartProfiling
|
26
|
-
when %r{/__stop__$}
|
27
|
-
StopProfiling
|
28
|
-
when %r{/__data__$}
|
29
|
-
ReturnData
|
30
|
+
if ProfileOnce.has_special_param?(request)
|
31
|
+
password_protect(ProfileOnce, accepted)
|
30
32
|
else
|
31
|
-
|
32
|
-
ProfileOnce
|
33
|
-
else
|
34
|
-
CallAppDirectly
|
35
|
-
end
|
33
|
+
CallAppDirectly
|
36
34
|
end
|
37
35
|
end
|
38
36
|
klass.new(env, profiler, middleware)
|
39
37
|
end
|
40
38
|
|
39
|
+
private
|
40
|
+
|
41
|
+
def self.password_protect(klass, accepted)
|
42
|
+
accepted ? klass : ReturnPasswordError
|
43
|
+
end
|
44
|
+
|
41
45
|
end
|
42
46
|
|
43
47
|
end
|
@@ -34,7 +34,7 @@ module Rack::PerftoolsProfiler
|
|
34
34
|
@mode = (options.delete(:mode) { DEFAULT_MODE }).to_sym
|
35
35
|
@bundler = options.delete(:bundler) { false }
|
36
36
|
@gemfile_dir = options.delete(:gemfile_dir) { DEFAULT_GEMFILE_DIR }
|
37
|
-
@password = options.delete(:password) {
|
37
|
+
@password = options.delete(:password) { :not_set }
|
38
38
|
@mode_for_request = nil
|
39
39
|
ProfileDataAction.check_printer(@printer)
|
40
40
|
ensure_mode_is_valid(@mode)
|
@@ -43,7 +43,7 @@ module Rack::PerftoolsProfiler
|
|
43
43
|
require 'perftools'
|
44
44
|
raise ProfilerArgumentError, "Invalid option(s): #{options.keys.join(' ')}" unless options.empty?
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
def profile(mode = nil)
|
48
48
|
start(mode)
|
49
49
|
yield
|
@@ -55,16 +55,28 @@ module Rack::PerftoolsProfiler
|
|
55
55
|
::File.delete(PROFILING_DATA_FILE) if ::File.exists?(PROFILING_DATA_FILE)
|
56
56
|
end
|
57
57
|
|
58
|
+
def accepts?(password)
|
59
|
+
if password_protected?
|
60
|
+
password_valid?(password)
|
61
|
+
else
|
62
|
+
true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
58
66
|
def password_valid?(password)
|
59
67
|
@password.nil? || password == @password
|
60
68
|
end
|
61
|
-
|
69
|
+
|
70
|
+
def password_protected?
|
71
|
+
@password != :not_set
|
72
|
+
end
|
73
|
+
|
62
74
|
def start(mode = nil)
|
63
75
|
ensure_mode_is_changeable(mode) if mode
|
64
76
|
PerfTools::CpuProfiler.stop
|
65
77
|
if (mode)
|
66
78
|
@mode_for_request = mode
|
67
|
-
end
|
79
|
+
end
|
68
80
|
unset_env_vars
|
69
81
|
set_env_vars
|
70
82
|
PerfTools::CpuProfiler.start(PROFILING_DATA_FILE)
|
@@ -146,7 +158,7 @@ module Rack::PerftoolsProfiler
|
|
146
158
|
ENV.delete('CPUPROFILE_OBJECTS')
|
147
159
|
ENV.delete('CPUPROFILE_METHODS')
|
148
160
|
end
|
149
|
-
|
161
|
+
|
150
162
|
def profiling=(value)
|
151
163
|
pstore_transaction(false) do |store|
|
152
164
|
store[:profiling?] = value
|