rack-perftools_profiler 0.3.0 → 0.4.0
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/README.rdoc +26 -12
- data/VERSION +1 -1
- data/lib/rack/perftools_profiler.rb +2 -0
- data/lib/rack/perftools_profiler/profile_once.rb +9 -1
- data/lib/rack/perftools_profiler/profiler.rb +42 -6
- data/lib/rack/perftools_profiler/return_data.rb +1 -1
- data/lib/rack/perftools_profiler/start_profiling.rb +14 -1
- data/lib/rack/perftools_profiler/utils.rb +11 -0
- data/rack-perftools_profiler.gemspec +81 -0
- data/test/multiple_request_profiling_test.rb +352 -0
- data/test/rack-perftools-profiler_test.rb +14 -369
- data/test/single_request_profiling_test.rb +278 -0
- data/test/test_helper.rb +64 -0
- metadata +29 -7
- data/.gitignore +0 -6
data/README.rdoc
CHANGED
@@ -55,16 +55,16 @@ For Rack::Builder, call 'use' inside the Builder constructor block
|
|
55
55
|
== Options
|
56
56
|
|
57
57
|
* :default_printer - can be set to 'text', 'gif', or 'pdf'. Default is :text
|
58
|
-
* :mode - can be set to 'cputime', 'objects', 'walltime'. Default is :cputime. See the 'Profiling Modes' section below.
|
58
|
+
* :mode - can be set to 'cputime', 'methods', 'objects', 'walltime'. Default is :cputime. See the 'Profiling Modes' section below.
|
59
59
|
* :frequency - in :cputime mode, the number of times per second the app will be sampled. Default is 100 (times/sec)
|
60
60
|
* :bundler - run the profiler binary using 'bundle' if set to true. Default is false
|
61
61
|
* :gemfile_dir - directory with Gemfile. Default is the current directory.
|
62
62
|
|
63
63
|
== Usage
|
64
64
|
|
65
|
-
There are two
|
65
|
+
There are two ways to profile your app: with a single request or with multiple requests.
|
66
66
|
|
67
|
-
|
67
|
+
To profile with a single request, visit the URL you want to profile, but
|
68
68
|
add the 'profile' and (optionally) the 'times' GET params (which will rerun the action
|
69
69
|
the specified number of times).
|
70
70
|
|
@@ -74,7 +74,9 @@ Example:
|
|
74
74
|
Note that this will change the status, body, and headers of the response (you'll get
|
75
75
|
back the profiling data, NOT the original response).
|
76
76
|
|
77
|
-
|
77
|
+
You can also profile your application using multiple requests. When you profile using this method,
|
78
|
+
all responses are normal. You must visit \_\_stop\_\_ to complete profiling and then you can view
|
79
|
+
the profiling data by visiting \_\_data\_\_.
|
78
80
|
|
79
81
|
Example:
|
80
82
|
curl http://localhost:3000/__start__
|
@@ -83,14 +85,11 @@ Example:
|
|
83
85
|
curl http://localhost:3000/__stop__
|
84
86
|
curl http://localhost:3000/__data__
|
85
87
|
|
86
|
-
In this mode, all responses are normal. You must visit \_\_stop\_\_ to complete profiling and
|
87
|
-
then you can view the profiling data by visiting \_\_data\_\_
|
88
|
-
|
89
88
|
== Profiling Data Options
|
90
89
|
|
91
|
-
|
92
|
-
is displayed.
|
93
|
-
|
90
|
+
Regardless of how you profile your application, you can add additional params to change how the
|
91
|
+
data is displayed. When using a single request, these params are just added to the URL being profiled.
|
92
|
+
When using multiple requests, they are added to the \_\_data\_\_ URL.
|
94
93
|
|
95
94
|
* printer - overrides the default_printer option (see above)
|
96
95
|
* ignore - a regular expression of the area of code to ignore
|
@@ -101,11 +100,12 @@ for more details)
|
|
101
100
|
|
102
101
|
== Profiling Modes
|
103
102
|
|
104
|
-
perftools.rb can be put into three different profiling modes.
|
103
|
+
perftools.rb (and therefore, the Rack middleware) can be put into three different profiling modes.
|
105
104
|
|
106
105
|
* 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'
|
107
|
-
*
|
106
|
+
* Method call mode - Report how many method calls are made inside each method. Enable by setting ':mode => :methods'
|
108
107
|
* Object allocation mode - Reports the percentage of object allocations performed in each section of code. Enable by setting ':mode => :objects'
|
108
|
+
* Wall time mode - Reports the amount of time (as in, wall clock time) spent in each section of code. Enable by setting ':mode => :walltime'
|
109
109
|
|
110
110
|
For example, consider the following Sinatra application:
|
111
111
|
|
@@ -125,6 +125,20 @@ In the default mode, there will be no profiling data for the 'slow' route, becau
|
|
125
125
|
|
126
126
|
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.
|
127
127
|
|
128
|
+
== Overriding the Profiling mode
|
129
|
+
|
130
|
+
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.
|
131
|
+
|
132
|
+
To switch to another mode, provide the 'mode' option. When profiling with a single request, add the option to the URL profiled:
|
133
|
+
|
134
|
+
curl http://localhost:3000/foobar?profile=true&mode=objects
|
135
|
+
|
136
|
+
When profiling using multiple requests, add the option when visiting \_\_start\_\_ :
|
137
|
+
|
138
|
+
curl http://localhost:3000/__start__?mode=objects
|
139
|
+
|
140
|
+
If the 'mode' option is omitted, the middleware will default to the mode specified at configuration.
|
141
|
+
|
128
142
|
== Changing behavior with environment variables
|
129
143
|
|
130
144
|
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).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
@@ -2,6 +2,7 @@ require 'rack'
|
|
2
2
|
require 'pstore'
|
3
3
|
require 'open4'
|
4
4
|
|
5
|
+
require 'rack/perftools_profiler/utils'
|
5
6
|
require 'rack/perftools_profiler/profiler_middleware'
|
6
7
|
require 'rack/perftools_profiler/action'
|
7
8
|
require 'rack/perftools_profiler/profiler'
|
@@ -24,6 +25,7 @@ module Rack::PerftoolsProfiler
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def self.with_profiling_off(app, options = {})
|
28
|
+
clear_data
|
27
29
|
instance = ProfilerMiddleware.new(app, options)
|
28
30
|
instance.force_stop
|
29
31
|
instance
|
@@ -2,6 +2,7 @@ module Rack::PerftoolsProfiler
|
|
2
2
|
|
3
3
|
class ProfileOnce < ProfileDataAction
|
4
4
|
include Rack::Utils
|
5
|
+
include Rack::PerftoolsProfiler::Utils
|
5
6
|
|
6
7
|
def self.has_special_param?(request)
|
7
8
|
request.params['profile'] != nil
|
@@ -11,12 +12,19 @@ module Rack::PerftoolsProfiler
|
|
11
12
|
super
|
12
13
|
request = Rack::Request.new(@env)
|
13
14
|
@times = (request.params.fetch('times') {1}).to_i
|
15
|
+
@mode = let(request.params['mode']) do |m|
|
16
|
+
if m.nil? || m.empty?
|
17
|
+
nil
|
18
|
+
else
|
19
|
+
m.to_sym
|
20
|
+
end
|
21
|
+
end
|
14
22
|
check_printer_arg
|
15
23
|
@new_env = delete_custom_params(@env)
|
16
24
|
end
|
17
25
|
|
18
26
|
def act
|
19
|
-
@profiler.profile do
|
27
|
+
@profiler.profile(@mode) do
|
20
28
|
@times.times { @middleware.call_app(@new_env) }
|
21
29
|
end
|
22
30
|
end
|
@@ -22,7 +22,9 @@ module Rack::PerftoolsProfiler
|
|
22
22
|
PROFILING_DATA_FILE = ::File.join(self.tmpdir, 'rack_perftools_profiler.prof')
|
23
23
|
PROFILING_SETTINGS_FILE = ::File.join(self.tmpdir, 'rack_perftools_profiler.config')
|
24
24
|
DEFAULT_PRINTER = :text
|
25
|
+
MODES = [:cputime, :methods, :objects, :walltime]
|
25
26
|
DEFAULT_MODE = :cputime
|
27
|
+
CHANGEABLE_MODES = [:methods, :objects]
|
26
28
|
UNSET_FREQUENCY = "-1"
|
27
29
|
DEFAULT_GEMFILE_DIR = '.'
|
28
30
|
|
@@ -32,15 +34,17 @@ module Rack::PerftoolsProfiler
|
|
32
34
|
@mode = (options.delete(:mode) { DEFAULT_MODE }).to_sym
|
33
35
|
@bundler = (options.delete(:bundler) { false })
|
34
36
|
@gemfile_dir = (options.delete(:gemfile_dir) { DEFAULT_GEMFILE_DIR })
|
37
|
+
@mode_for_request = nil
|
35
38
|
ProfileDataAction.check_printer(@printer)
|
39
|
+
ensure_mode_is_valid(@mode)
|
36
40
|
# We need to set the enviroment variables before loading perftools
|
37
41
|
set_env_vars
|
38
42
|
require 'perftools'
|
39
43
|
raise ProfilerArgumentError, "Invalid option(s): #{options.keys.join(' ')}" unless options.empty?
|
40
44
|
end
|
41
45
|
|
42
|
-
def profile
|
43
|
-
start
|
46
|
+
def profile(mode = nil)
|
47
|
+
start(mode)
|
44
48
|
yield
|
45
49
|
ensure
|
46
50
|
stop
|
@@ -50,10 +54,18 @@ module Rack::PerftoolsProfiler
|
|
50
54
|
::File.delete(PROFILING_DATA_FILE) if ::File.exists?(PROFILING_DATA_FILE)
|
51
55
|
end
|
52
56
|
|
53
|
-
def start
|
57
|
+
def start(mode = nil)
|
58
|
+
ensure_mode_is_changeable(mode) if mode
|
54
59
|
PerfTools::CpuProfiler.stop
|
60
|
+
if (mode)
|
61
|
+
@mode_for_request = mode
|
62
|
+
end
|
63
|
+
unset_env_vars
|
64
|
+
set_env_vars
|
55
65
|
PerfTools::CpuProfiler.start(PROFILING_DATA_FILE)
|
56
66
|
self.profiling = true
|
67
|
+
ensure
|
68
|
+
@mode_for_request = nil
|
57
69
|
end
|
58
70
|
|
59
71
|
def stop
|
@@ -106,16 +118,22 @@ module Rack::PerftoolsProfiler
|
|
106
118
|
end
|
107
119
|
|
108
120
|
def set_env_vars
|
109
|
-
|
110
|
-
|
121
|
+
if @mode_for_request
|
122
|
+
mode_to_use = @mode_for_request
|
123
|
+
else
|
124
|
+
mode_to_use = @mode
|
125
|
+
end
|
126
|
+
ENV['CPUPROFILE_REALTIME'] = '1' if mode_to_use == :walltime
|
127
|
+
ENV['CPUPROFILE_OBJECTS'] = '1' if mode_to_use == :objects
|
128
|
+
ENV['CPUPROFILE_METHODS'] = '1' if mode_to_use == :methods
|
111
129
|
ENV['CPUPROFILE_FREQUENCY'] = @frequency if @frequency != UNSET_FREQUENCY
|
112
130
|
end
|
113
131
|
|
114
|
-
# Useful for testing
|
115
132
|
def unset_env_vars
|
116
133
|
ENV.delete('CPUPROFILE_REALTIME')
|
117
134
|
ENV.delete('CPUPROFILE_FREQUENCY')
|
118
135
|
ENV.delete('CPUPROFILE_OBJECTS')
|
136
|
+
ENV.delete('CPUPROFILE_METHODS')
|
119
137
|
end
|
120
138
|
|
121
139
|
def profiling=(value)
|
@@ -131,6 +149,24 @@ module Rack::PerftoolsProfiler
|
|
131
149
|
end
|
132
150
|
end
|
133
151
|
|
152
|
+
def ensure_mode_is_changeable(mode)
|
153
|
+
if !CHANGEABLE_MODES.include?(mode)
|
154
|
+
message = "Cannot change mode to '#{mode}'.\n"
|
155
|
+
mode_string = CHANGEABLE_MODES.map{|m| "'#{m}'"}.join(", ")
|
156
|
+
message += "Per-request mode changes are only available for the following modes: #{mode_string}.\n"
|
157
|
+
message += "See the README for more details."
|
158
|
+
raise ProfilerArgumentError, message
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def ensure_mode_is_valid(mode)
|
163
|
+
if !MODES.include?(mode)
|
164
|
+
message = "Invalid mode: #{mode}.\n"
|
165
|
+
message += "Valid modes are: #{MODES.join(', ')}"
|
166
|
+
raise ProfilerArgumentError, message
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
134
170
|
end
|
135
171
|
|
136
172
|
end
|
@@ -9,7 +9,7 @@ module Rack::PerftoolsProfiler
|
|
9
9
|
|
10
10
|
def response
|
11
11
|
if @profiler.profiling?
|
12
|
-
[400, {'Content-Type' => 'text/plain'}, [
|
12
|
+
[400, {'Content-Type' => 'text/plain'}, ["No profiling data available yet.\nVisit to /__stop__ to finish profiling."]]
|
13
13
|
else
|
14
14
|
@middleware.profiler_data_response(@profiler.data(@data_params))
|
15
15
|
end
|
@@ -1,9 +1,22 @@
|
|
1
1
|
module Rack::PerftoolsProfiler
|
2
2
|
|
3
3
|
class StartProfiling < Action
|
4
|
+
include Rack::PerftoolsProfiler::Utils
|
4
5
|
|
6
|
+
def initialize(*args)
|
7
|
+
super
|
8
|
+
request = Rack::Request.new(@env)
|
9
|
+
@mode = let(request.params['mode']) do |m|
|
10
|
+
if m.nil? || m.empty?
|
11
|
+
nil
|
12
|
+
else
|
13
|
+
m.to_sym
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
5
18
|
def act
|
6
|
-
@profiler.start
|
19
|
+
@profiler.start(@mode)
|
7
20
|
end
|
8
21
|
|
9
22
|
def response
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rack-perftools_profiler}
|
8
|
+
s.version = "0.4.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Ben Brinckerhoff"]
|
12
|
+
s.date = %q{2011-01-16}
|
13
|
+
s.description = %q{Middleware for profiling Rack-compatible apps using perftools.rb}
|
14
|
+
s.email = %q{ben@bbrinck.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"lib/rack/perftools_profiler.rb",
|
26
|
+
"lib/rack/perftools_profiler/action.rb",
|
27
|
+
"lib/rack/perftools_profiler/call_app_directly.rb",
|
28
|
+
"lib/rack/perftools_profiler/profile_data_action.rb",
|
29
|
+
"lib/rack/perftools_profiler/profile_once.rb",
|
30
|
+
"lib/rack/perftools_profiler/profiler.rb",
|
31
|
+
"lib/rack/perftools_profiler/profiler_middleware.rb",
|
32
|
+
"lib/rack/perftools_profiler/return_data.rb",
|
33
|
+
"lib/rack/perftools_profiler/start_profiling.rb",
|
34
|
+
"lib/rack/perftools_profiler/stop_profiling.rb",
|
35
|
+
"lib/rack/perftools_profiler/utils.rb",
|
36
|
+
"rack-perftools_profiler.gemspec",
|
37
|
+
"test/multiple_request_profiling_test.rb",
|
38
|
+
"test/rack-perftools-profiler_test.rb",
|
39
|
+
"test/single_request_profiling_test.rb",
|
40
|
+
"test/test_helper.rb"
|
41
|
+
]
|
42
|
+
s.homepage = %q{http://github.com/bhb/rack-perftools_profiler}
|
43
|
+
s.require_paths = ["lib"]
|
44
|
+
s.rubygems_version = %q{1.3.7}
|
45
|
+
s.summary = %q{Middleware for profiling Rack-compatible apps using perftools.rb}
|
46
|
+
s.test_files = [
|
47
|
+
"test/multiple_request_profiling_test.rb",
|
48
|
+
"test/rack-perftools-profiler_test.rb",
|
49
|
+
"test/single_request_profiling_test.rb",
|
50
|
+
"test/test_helper.rb"
|
51
|
+
]
|
52
|
+
|
53
|
+
if s.respond_to? :specification_version then
|
54
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
55
|
+
s.specification_version = 3
|
56
|
+
|
57
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
58
|
+
s.add_runtime_dependency(%q<perftools.rb>, ["~> 0.5"])
|
59
|
+
s.add_runtime_dependency(%q<rack>, ["~> 1.0"])
|
60
|
+
s.add_runtime_dependency(%q<open4>, ["~> 1.0"])
|
61
|
+
s.add_development_dependency(%q<rack>, ["~> 1.1"])
|
62
|
+
s.add_development_dependency(%q<shoulda>, ["~> 2.10"])
|
63
|
+
s.add_development_dependency(%q<mocha>, ["~> 0.9"])
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<perftools.rb>, ["~> 0.5"])
|
66
|
+
s.add_dependency(%q<rack>, ["~> 1.0"])
|
67
|
+
s.add_dependency(%q<open4>, ["~> 1.0"])
|
68
|
+
s.add_dependency(%q<rack>, ["~> 1.1"])
|
69
|
+
s.add_dependency(%q<shoulda>, ["~> 2.10"])
|
70
|
+
s.add_dependency(%q<mocha>, ["~> 0.9"])
|
71
|
+
end
|
72
|
+
else
|
73
|
+
s.add_dependency(%q<perftools.rb>, ["~> 0.5"])
|
74
|
+
s.add_dependency(%q<rack>, ["~> 1.0"])
|
75
|
+
s.add_dependency(%q<open4>, ["~> 1.0"])
|
76
|
+
s.add_dependency(%q<rack>, ["~> 1.1"])
|
77
|
+
s.add_dependency(%q<shoulda>, ["~> 2.10"])
|
78
|
+
s.add_dependency(%q<mocha>, ["~> 0.9"])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,352 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MultipleRequestProfilingTest < Test::Unit::TestCase
|
4
|
+
include Rack::PerftoolsProfiler
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@app = lambda { |env| ITERATIONS.times {1+2+3+4+5}; [200, {'Content-Type' => 'text/plain'}, ['Oh hai der']] }
|
8
|
+
@start_env = Rack::MockRequest.env_for('/__start__')
|
9
|
+
@stop_env = Rack::MockRequest.env_for('/__stop__')
|
10
|
+
@data_env = Rack::MockRequest.env_for('/__data__')
|
11
|
+
@root_request_env = Rack::MockRequest.env_for("/")
|
12
|
+
end
|
13
|
+
|
14
|
+
def profile(profiled_app, options = {})
|
15
|
+
start = options.fetch(:start) { @start_env }
|
16
|
+
stop = options.fetch(:stop) { @stop_env }
|
17
|
+
data = options.fetch(:data) { @data_env }
|
18
|
+
|
19
|
+
profiled_app.call(start) if start != :none
|
20
|
+
if block_given?
|
21
|
+
yield profiled_app
|
22
|
+
else
|
23
|
+
profiled_app.call(@root_request_env)
|
24
|
+
end
|
25
|
+
last_response = profiled_app.call(stop) if stop != :none
|
26
|
+
if data!=nil && data!=:none
|
27
|
+
last_response = profiled_app.call(data) if data
|
28
|
+
end
|
29
|
+
last_response
|
30
|
+
end
|
31
|
+
|
32
|
+
def profile_requests(profiled_app, requests, options = {})
|
33
|
+
get_data = options.fetch(:get_data) { true }
|
34
|
+
if requests == :default
|
35
|
+
requests = [@root_request_env]
|
36
|
+
else
|
37
|
+
requests = Array(requests)
|
38
|
+
end
|
39
|
+
profiled_app.call(@start_env)
|
40
|
+
requests.each do |request|
|
41
|
+
profiled_app.call(request)
|
42
|
+
end
|
43
|
+
profiled_app.call(@stop_env)
|
44
|
+
profiled_app.call(@data_env) if get_data
|
45
|
+
end
|
46
|
+
|
47
|
+
context "(common behavior)" do
|
48
|
+
|
49
|
+
should 'default to text printer' do
|
50
|
+
# TODO - It's weird that this passes if you pass in :data => :none to #profile
|
51
|
+
_, headers, _ = profile(Rack::PerftoolsProfiler.new(@app))
|
52
|
+
assert_equal "text/plain", headers['Content-Type']
|
53
|
+
end
|
54
|
+
|
55
|
+
should "set CPUPROFILE_REALTIME to 1 if mode is 'walltime' " do
|
56
|
+
realtime = ENV['CPUPROFILE_REALTIME']
|
57
|
+
assert_nil realtime
|
58
|
+
app = lambda do |env|
|
59
|
+
realtime = ENV['CPUPROFILE_REALTIME']
|
60
|
+
[200, {}, ["hi"]]
|
61
|
+
end
|
62
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :mode => 'walltime')
|
63
|
+
profile(profiled_app, :data => :none)
|
64
|
+
assert_equal '1', realtime
|
65
|
+
end
|
66
|
+
|
67
|
+
should "set CPUPROFILE_OBJECTS to 1 if mode is 'objects'" do
|
68
|
+
objects = ENV['CPUPROFILE_OBJECTS']
|
69
|
+
assert_nil objects
|
70
|
+
app = lambda do |env|
|
71
|
+
objects = ENV['CPUPROFILE_OBJECTS']
|
72
|
+
[200, {}, ["hi"]]
|
73
|
+
end
|
74
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :mode => 'objects')
|
75
|
+
profile(profiled_app, :data => :none)
|
76
|
+
assert_equal '1', objects
|
77
|
+
end
|
78
|
+
|
79
|
+
should "set CPUPROFILE_METHODS to 1 if mode is 'methods'" do
|
80
|
+
methods = ENV['CPUPROFILE_METHODS']
|
81
|
+
assert_nil methods
|
82
|
+
app = lambda do |env|
|
83
|
+
methods = ENV['CPUPROFILE_METHODS']
|
84
|
+
[200, {}, ["hi"]]
|
85
|
+
end
|
86
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :mode => 'methods')
|
87
|
+
profile(profiled_app, :data => :none)
|
88
|
+
assert_equal '1', methods
|
89
|
+
end
|
90
|
+
|
91
|
+
should "not set CPUPROFILE_FREQUENCY by default" do
|
92
|
+
frequency = ENV['CPUPROFILE_FREQUENCY']
|
93
|
+
assert_nil frequency
|
94
|
+
frequency = '1'
|
95
|
+
app = lambda do |env|
|
96
|
+
frequency = ENV['CPUPROFILE_FREQUENCY']
|
97
|
+
[200, {}, ["hi"]]
|
98
|
+
end
|
99
|
+
profiled_app = Rack::PerftoolsProfiler.new(app)
|
100
|
+
profile(profiled_app, :data => :none)
|
101
|
+
assert_nil frequency
|
102
|
+
end
|
103
|
+
|
104
|
+
should 'alter CPUPROFILE_FREQUENCY if frequency is set' do
|
105
|
+
frequency = ENV['CPUPROFILE_FREQUENCY']
|
106
|
+
assert_nil frequency
|
107
|
+
app = lambda do |env|
|
108
|
+
frequency = ENV['CPUPROFILE_FREQUENCY']
|
109
|
+
[200, {}, ["hi"]]
|
110
|
+
end
|
111
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :frequency => 250)
|
112
|
+
profile(profiled_app, :data => :none)
|
113
|
+
assert_equal '250', frequency
|
114
|
+
end
|
115
|
+
|
116
|
+
should "allow 'printer' param to override :default_printer option'" do
|
117
|
+
profiled_app = Rack::PerftoolsProfiler.new(@app, :default_printer => 'pdf')
|
118
|
+
custom_data_env = Rack::MockRequest.env_for('__data__', :params => 'printer=gif')
|
119
|
+
_, headers, _ = profile(profiled_app, :data => custom_data_env)
|
120
|
+
assert_equal 'image/gif', headers['Content-Type']
|
121
|
+
end
|
122
|
+
|
123
|
+
should 'give 400 if printer is invalid' do
|
124
|
+
profiled_app = Rack::PerftoolsProfiler.new(@app, :default_printer => 'pdf')
|
125
|
+
custom_data_env = Rack::MockRequest.env_for('__data__', :params => 'printer=badprinter')
|
126
|
+
status, _, body = profile(profiled_app, :data => custom_data_env)
|
127
|
+
assert_equal 400, status
|
128
|
+
assert_match /Invalid printer type/, body.join
|
129
|
+
end
|
130
|
+
|
131
|
+
should "accept 'focus' param" do
|
132
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(TestApp.new, :default_printer => 'text', :mode => 'walltime')
|
133
|
+
custom_data_env = Rack::MockRequest.env_for('__data__', :params => 'focus=method1')
|
134
|
+
|
135
|
+
status, headers, body = profile(profiled_app, :data => custom_data_env) do |app|
|
136
|
+
app.call(Rack::MockRequest.env_for('/method1'))
|
137
|
+
app.call(Rack::MockRequest.env_for('/method2'))
|
138
|
+
end
|
139
|
+
assert_match(/method1/, RackResponseBody.new(body).to_s)
|
140
|
+
assert_no_match(/method2/, RackResponseBody.new(body).to_s)
|
141
|
+
end
|
142
|
+
|
143
|
+
should "accept 'ignore' param" do
|
144
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(TestApp.new, :default_printer => 'text', :mode => 'walltime')
|
145
|
+
custom_data_env = Rack::MockRequest.env_for('__data__', :params => 'ignore=method1')
|
146
|
+
|
147
|
+
status, headers, body = profile(profiled_app, :data => custom_data_env) do |app|
|
148
|
+
app.call(Rack::MockRequest.env_for('/method1'))
|
149
|
+
app.call(Rack::MockRequest.env_for('/method2'))
|
150
|
+
end
|
151
|
+
|
152
|
+
assert_match(/method2/, RackResponseBody.new(body).to_s)
|
153
|
+
assert_no_match(/method1/, RackResponseBody.new(body).to_s)
|
154
|
+
end
|
155
|
+
|
156
|
+
context "when in bundler mode" do
|
157
|
+
|
158
|
+
should "call pprof.rb using 'bundle' command if bundler is set" do
|
159
|
+
status = stub_everything(:exitstatus => 0)
|
160
|
+
profiled_app = Rack::PerftoolsProfiler.new(@app, :bundler => true)
|
161
|
+
Open4.expects(:popen4).with(regexp_matches(/^bundle exec pprof\.rb/)).returns(status)
|
162
|
+
profile(profiled_app)
|
163
|
+
end
|
164
|
+
|
165
|
+
should "change directory into the current directory if custom Gemfile dir is not provided" do
|
166
|
+
profiled_app = Rack::PerftoolsProfiler.new(@app, :bundler => true, :gemfile_dir => 'bundler')
|
167
|
+
Dir.expects(:chdir).with('bundler').returns(["","",0])
|
168
|
+
profile(profiled_app)
|
169
|
+
end
|
170
|
+
|
171
|
+
should "change directory into custom Gemfile dir if provided" do
|
172
|
+
profiled_app = Rack::PerftoolsProfiler.new(@app, :bundler => true)
|
173
|
+
Dir.expects(:chdir).with('.').returns(["","",0])
|
174
|
+
profile(profiled_app)
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
context "when overriding profiling mode" do
|
180
|
+
|
181
|
+
should "default to configured mode if mode is empty string" do
|
182
|
+
realtime = ENV['CPUPROFILE_REALTIME']
|
183
|
+
assert_nil realtime
|
184
|
+
app = lambda do |env|
|
185
|
+
realtime = ENV['CPUPROFILE_REALTIME']
|
186
|
+
[200, {}, ["hi"]]
|
187
|
+
end
|
188
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :mode => :walltime)
|
189
|
+
modified_start_env = Rack::MockRequest.env_for('/__start__', :params => 'mode=')
|
190
|
+
profile(profiled_app, :start => modified_start_env, :data => :none)
|
191
|
+
assert_equal '1', realtime
|
192
|
+
end
|
193
|
+
|
194
|
+
should "set CPUPROFILE_OBJECTS to 1 if mode is 'objects'" do
|
195
|
+
objects = ENV['CPUPROFILE_OBJECTS']
|
196
|
+
assert_nil objects
|
197
|
+
app = lambda do |env|
|
198
|
+
objects = ENV['CPUPROFILE_OBJECTS']
|
199
|
+
[200, {}, ["hi"]]
|
200
|
+
end
|
201
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :mode => :cputime)
|
202
|
+
modified_start_env = Rack::MockRequest.env_for('/__start__', :params => 'mode=objects')
|
203
|
+
profile(profiled_app, :start => modified_start_env, :data => :none)
|
204
|
+
assert_equal '1', objects
|
205
|
+
end
|
206
|
+
|
207
|
+
should "set CPUPROFILE_METHODS to 1 if mode is 'methods'" do
|
208
|
+
methods = ENV['CPUPROFILE_METHODS']
|
209
|
+
assert_nil methods
|
210
|
+
app = lambda do |env|
|
211
|
+
methods = ENV['CPUPROFILE_METHODS']
|
212
|
+
[200, {}, ["hi"]]
|
213
|
+
end
|
214
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :mode => :cputime)
|
215
|
+
modified_start_env = Rack::MockRequest.env_for('/__start__', :params => 'mode=methods')
|
216
|
+
profile(profiled_app, :start => modified_start_env, :data => :none)
|
217
|
+
assert_equal '1', methods
|
218
|
+
end
|
219
|
+
|
220
|
+
should "return to default mode if no mode is specified" do
|
221
|
+
objects = ENV['CPUPROFILE_OBJECTS']
|
222
|
+
assert_nil objects
|
223
|
+
app = lambda do |env|
|
224
|
+
objects = ENV['CPUPROFILE_OBJECTS']
|
225
|
+
[200, {}, ["hi"]]
|
226
|
+
end
|
227
|
+
|
228
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :mode => :cputime)
|
229
|
+
modified_start_env = Rack::MockRequest.env_for('/__start__', :params => 'mode=objects')
|
230
|
+
profile(profiled_app, :start => modified_start_env, :data => :none)
|
231
|
+
|
232
|
+
assert_equal '1', objects
|
233
|
+
|
234
|
+
profile(profiled_app, :data => :none)
|
235
|
+
|
236
|
+
assert_nil objects
|
237
|
+
end
|
238
|
+
|
239
|
+
should "return error message if mode is unrecognized" do
|
240
|
+
profiled_app = Rack::PerftoolsProfiler.new(@app)
|
241
|
+
mode = "foobar"
|
242
|
+
|
243
|
+
modified_start_env = Rack::MockRequest.env_for('/__start__', :params => "mode=#{mode}")
|
244
|
+
|
245
|
+
status, _, body = profiled_app.call(modified_start_env)
|
246
|
+
|
247
|
+
assert_equal 400, status
|
248
|
+
assert_match(/Cannot change mode to '#{mode}'.\nPer-request mode changes are only available for the following modes: 'methods', 'objects'/,
|
249
|
+
RackResponseBody.new(body).to_s)
|
250
|
+
end
|
251
|
+
|
252
|
+
should "return error message if mode is 'walltime'" do
|
253
|
+
profiled_app = Rack::PerftoolsProfiler.new(@app)
|
254
|
+
mode = "walltime"
|
255
|
+
|
256
|
+
modified_start_env = Rack::MockRequest.env_for('/__start__', :params => "mode=#{mode}")
|
257
|
+
|
258
|
+
status, _, body = profiled_app.call(modified_start_env)
|
259
|
+
|
260
|
+
assert_equal 400, status
|
261
|
+
assert_match(/Cannot change mode to '#{mode}'.\nPer-request mode changes are only available for the following modes: 'methods', 'objects'/,
|
262
|
+
RackResponseBody.new(body).to_s)
|
263
|
+
end
|
264
|
+
|
265
|
+
should "return error message if mode is 'cputime'" do
|
266
|
+
profiled_app = Rack::PerftoolsProfiler.new(@app)
|
267
|
+
mode = "cputime"
|
268
|
+
|
269
|
+
modified_start_env = Rack::MockRequest.env_for('/__start__', :params => "mode=#{mode}")
|
270
|
+
|
271
|
+
status, _, body = profiled_app.call(modified_start_env)
|
272
|
+
|
273
|
+
assert_equal 400, status
|
274
|
+
assert_match(/Cannot change mode to '#{mode}'.\nPer-request mode changes are only available for the following modes: 'methods', 'objects'/,
|
275
|
+
RackResponseBody.new(body).to_s)
|
276
|
+
end
|
277
|
+
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
281
|
+
|
282
|
+
context 'when profiling is enabled' do
|
283
|
+
|
284
|
+
should 'not provide profiling data when __data__ is called' do
|
285
|
+
Rack::PerftoolsProfiler.clear_data
|
286
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(@app, :default_printer => 'text')
|
287
|
+
status, _ , body = profile(profiled_app, :stop => :none)
|
288
|
+
assert_equal 400, status
|
289
|
+
assert_match(/No profiling data available./, RackResponseBody.new(body).to_s)
|
290
|
+
end
|
291
|
+
|
292
|
+
should 'pass on profiling params in environment' do
|
293
|
+
env = Rack::MockRequest.env_for('/', :params => 'times=2')
|
294
|
+
old_env = env.clone
|
295
|
+
app = @app.clone
|
296
|
+
expected_env = env.clone
|
297
|
+
expected_env['rack.request.query_string'] = 'times=2'
|
298
|
+
expected_env['rack.request.query_hash'] = {'times' => '2'}
|
299
|
+
app.expects(:call).with(expected_env)
|
300
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :default_printer => 'text')
|
301
|
+
profiled_app.call(@start_env)
|
302
|
+
profiled_app.call(env)
|
303
|
+
assert_equal env, old_env
|
304
|
+
end
|
305
|
+
|
306
|
+
should 'pass on non-profiling params in environment' do
|
307
|
+
env = Rack::MockRequest.env_for('/', :params => 'param=value')
|
308
|
+
old_env = env.clone
|
309
|
+
app = @app.clone
|
310
|
+
expected_env = env.clone
|
311
|
+
expected_env['rack.request.query_string'] = 'param=value'
|
312
|
+
expected_env['rack.request.query_hash'] = {'param' => 'value'}
|
313
|
+
app.expects(:call).with(expected_env)
|
314
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :default_printer => 'text')
|
315
|
+
profiled_app.call(@start_env)
|
316
|
+
profiled_app.call(env)
|
317
|
+
assert_equal env, old_env
|
318
|
+
end
|
319
|
+
|
320
|
+
should 'not alter regular calls' do
|
321
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(@app, :default_printer => 'gif')
|
322
|
+
profiled_app.call(@start_env)
|
323
|
+
status, headers, body = profiled_app.call(@root_request_env)
|
324
|
+
assert_equal 200, status
|
325
|
+
assert_equal 'text/plain', headers['Content-Type']
|
326
|
+
assert_equal 'Oh hai der', RackResponseBody.new(body).to_s
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
|
331
|
+
context 'after profiling is finished' do
|
332
|
+
|
333
|
+
should 'return profiling data when __data__ is called' do
|
334
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(@app, :default_printer => 'gif')
|
335
|
+
status, headers, body = profile(profiled_app)
|
336
|
+
assert_equal 200, status
|
337
|
+
assert_equal "image/gif", headers['Content-Type']
|
338
|
+
end
|
339
|
+
|
340
|
+
end
|
341
|
+
|
342
|
+
should 'keeps data from multiple calls' do
|
343
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(TestApp.new, :default_printer => 'text', :mode => 'walltime')
|
344
|
+
status, headers, body = profile(profiled_app) do |app|
|
345
|
+
app.call(Rack::MockRequest.env_for('/method1'))
|
346
|
+
app.call(Rack::MockRequest.env_for('/method2'))
|
347
|
+
end
|
348
|
+
assert_match(/method1/, RackResponseBody.new(body).to_s)
|
349
|
+
assert_match(/method2/, RackResponseBody.new(body).to_s)
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|