rack-perftools_profiler 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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 modes for the profiler
65
+ There are two ways to profile your app: with a single request or with multiple requests.
66
66
 
67
- First, you can run in 'simple' mode. Just visit the url you want to profile, but
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
- The other mode is start/stop mode.
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
- In both simple and start/stop modes, you can add additional params to change how the data
92
- is displayed. In simple mode, these params are just added to the URL being profiled. In
93
- start/stop mode, they are added to the \_\_data\_\_ URL
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
- * Wall time mode - Reports the amount of time (as in, wall clock time) spent in each section of code. Enable by setting ':mode => :walltime'
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.3.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
- ENV['CPUPROFILE_REALTIME'] = '1' if @mode == :walltime
110
- ENV['CPUPROFILE_OBJECTS'] = '1' if @mode == :objects
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'}, ['No profiling data available.']]
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,11 @@
1
+ module Rack::PerftoolsProfiler
2
+
3
+ module Utils
4
+
5
+ def let(x)
6
+ yield x
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -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