rack-perftools_profiler 0.0.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/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +112 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/lib/rack/perftools_profiler.rb +120 -0
- data/lib/rack/perftools_profiler/action.rb +39 -0
- data/lib/rack/perftools_profiler/call_app_directly.rb +15 -0
- data/lib/rack/perftools_profiler/profile_data_action.rb +20 -0
- data/lib/rack/perftools_profiler/profile_once.rb +47 -0
- data/lib/rack/perftools_profiler/profiler.rb +123 -0
- data/lib/rack/perftools_profiler/profiler_middleware.rb +74 -0
- data/lib/rack/perftools_profiler/return_data.rb +20 -0
- data/lib/rack/perftools_profiler/start_profiling.rb +21 -0
- data/lib/rack/perftools_profiler/stop_profiling.rb +20 -0
- data/test/rack-perftools-profiler_test.rb +407 -0
- data/test/test_helper.rb +11 -0
- metadata +164 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Ben Brinckerhoff
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
= Rack::PerftoolsProfiler
|
2
|
+
|
3
|
+
Middleware for profiling Rack-compatible apps using perftools.rb (http://github.com/tmm1/perftools.rb)
|
4
|
+
|
5
|
+
== Requirements
|
6
|
+
|
7
|
+
You'll need graphviz to generate call graphs using dot (for the GIF printer):
|
8
|
+
|
9
|
+
sudo port install graphviz # OS X
|
10
|
+
brew install graphviz # Homebrew
|
11
|
+
sudo apt-get install graphviz # Debian/Ubuntu
|
12
|
+
|
13
|
+
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
|
+
|
19
|
+
== Configuration
|
20
|
+
|
21
|
+
Install the gem
|
22
|
+
|
23
|
+
gem install rack-perftools_profiler
|
24
|
+
|
25
|
+
Include the middleware
|
26
|
+
|
27
|
+
require 'rack/perftools_profiler'
|
28
|
+
|
29
|
+
For Rails, add the following to config/environment.rb
|
30
|
+
|
31
|
+
config.gem 'rack-perftools_profiler', :version => '~> 0.0.1', :lib => 'rack/perftools_profiler'
|
32
|
+
require 'rack/perftools_profiler'
|
33
|
+
config.middleware.use Rack::PerftoolsProfiler, :default_printer => 'gif'
|
34
|
+
|
35
|
+
For Sinatra, call 'use' inside a configure block, like so:
|
36
|
+
|
37
|
+
configure :profiling do
|
38
|
+
use Rack::PerftoolsProfiler, :default_printer => 'gif'
|
39
|
+
end
|
40
|
+
|
41
|
+
For Rack::Builder, call 'use' inside the Builder constructor block
|
42
|
+
|
43
|
+
Rack::Builder.new do
|
44
|
+
use Rack::PerftoolsProfiler, :default_printer => 'gif'
|
45
|
+
end
|
46
|
+
|
47
|
+
== Options
|
48
|
+
|
49
|
+
* :default_printer - can be set to 'text', 'gif', or 'pdf'. Default is :text
|
50
|
+
* :mode - can be set to 'cputime' or 'walltime'. Default is :cputime
|
51
|
+
* :frequency - in :cputime mode, the number of times per second the app will be sampled. Default is 100 (times/sec)
|
52
|
+
|
53
|
+
== Usage
|
54
|
+
|
55
|
+
There are two modes for the profiler
|
56
|
+
|
57
|
+
First, you can run in 'simple' mode. Just visit the url you want to profile, but
|
58
|
+
add the 'profile' and (optionally) the 'times' GET params (which will rerun the action
|
59
|
+
the specified number of times).
|
60
|
+
|
61
|
+
Example:
|
62
|
+
curl http://localhost:8080/foobar?profile=true×=3
|
63
|
+
|
64
|
+
Note that this will change the status, body, and headers of the response (you'll get
|
65
|
+
back the profiling data, NOT the original response).
|
66
|
+
|
67
|
+
The other mode is start/stop mode.
|
68
|
+
|
69
|
+
Example:
|
70
|
+
curl http://localhost:8080/__start__
|
71
|
+
curl http://localhost:8080/foobar
|
72
|
+
curl http://localhost:8080/foobaz
|
73
|
+
curl http://localhost:8080/__stop__
|
74
|
+
curl http://localhost:8080/__data__
|
75
|
+
|
76
|
+
In this mode, all responses are normal. You must visit `__stop__` to complete profiling and
|
77
|
+
then you can view the profiling data by visiting `__data__`
|
78
|
+
|
79
|
+
== Profiling Data Options
|
80
|
+
|
81
|
+
In both simple and start/stop modes, you can add additional params to change how the data
|
82
|
+
is displayed. In simple mode, these params are just added to the URL being profiled. In
|
83
|
+
start/stop mode, they are added to the `__data__` URL
|
84
|
+
|
85
|
+
* printer - overrides the default_printer option (see above)
|
86
|
+
* ignore - a regular expression of the area of code to ignore
|
87
|
+
* focus - a regular expression of the area of code to solely focus on.
|
88
|
+
|
89
|
+
(for 'ignore' and 'focus', please see http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html#pprof
|
90
|
+
for more details)
|
91
|
+
|
92
|
+
== Acknowledgments
|
93
|
+
|
94
|
+
A huge thanks to Aman Gupta for the awesome perftools.rb gem.
|
95
|
+
|
96
|
+
The basic idea and initial implementation of the middleware was heavily influenced by
|
97
|
+
Rack::Profiler from rack-contrib.
|
98
|
+
|
99
|
+
== Note on Patches/Pull Requests
|
100
|
+
|
101
|
+
* Fork the project.
|
102
|
+
* Make your feature addition or bug fix.
|
103
|
+
* Add tests for it. This is important so I don't break it in a
|
104
|
+
future version unintentionally.
|
105
|
+
* Commit, do not mess with rakefile, version, or history.
|
106
|
+
(if you want to have your own version, that is fine but
|
107
|
+
bump version in a commit by itself I can ignore when I pull)
|
108
|
+
* Send me a pull request. Bonus points for topic branches.
|
109
|
+
|
110
|
+
== Copyright
|
111
|
+
|
112
|
+
Copyright (c) 2010 Ben Brinckerhoff. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rubygems'
|
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.4.0'
|
14
|
+
gem.add_dependency 'rack', '~> 1.0.0'
|
15
|
+
gem.add_dependency('open4', '~> 1.0.1')
|
16
|
+
gem.add_development_dependency 'rack', '~> 1.1.0'
|
17
|
+
gem.add_development_dependency 'shoulda', '~> 2.10.2'
|
18
|
+
gem.add_development_dependency 'mocha', '~> 0.9.8'
|
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
|
25
|
+
|
26
|
+
require 'rake/testtask'
|
27
|
+
Rake::TestTask.new(:test) do |test|
|
28
|
+
test.libs << 'lib' << 'test'
|
29
|
+
test.pattern = 'test/**/*_test.rb'
|
30
|
+
test.verbose = true
|
31
|
+
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
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# REQUIREMENTS
|
2
|
+
#
|
3
|
+
# You'll need graphviz to generate call graphs using dot (for the GIF printer):
|
4
|
+
#
|
5
|
+
# sudo port install graphviz # osx
|
6
|
+
# sudo apt-get install graphviz # debian/ubuntu
|
7
|
+
|
8
|
+
# You'll need ps2pdf to generate PDFs
|
9
|
+
# On OS X, ps2pdf comes is installed as part of Ghostscript
|
10
|
+
#
|
11
|
+
# sudo port install ghostscript # osx
|
12
|
+
# brew install ghostscript # homebrew
|
13
|
+
# sudo apt-get install ps2pdf # debian/ubuntu
|
14
|
+
|
15
|
+
# CONFIGURATION
|
16
|
+
#
|
17
|
+
# Include the middleware
|
18
|
+
#
|
19
|
+
# require 'rack/perftools_profiler'
|
20
|
+
#
|
21
|
+
# For Rails, add the following to config/environment.rb
|
22
|
+
#
|
23
|
+
# config.middleware.use Rack::PerftoolsProfiler, :default_printer => 'gif'
|
24
|
+
#
|
25
|
+
# For Sinatra, call 'use' inside a configure block, like so:
|
26
|
+
#
|
27
|
+
# configure :profiling do
|
28
|
+
# use Rack::PerftoolsProfiler, :default_printer => 'gif'
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# For Rack::Builder, call 'use' inside the Builder constructor block
|
32
|
+
#
|
33
|
+
# Rack::Builder.new do
|
34
|
+
# use Rack::PerftoolsProfiler, :default_printer => 'gif'
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
#
|
38
|
+
# OPTIONS
|
39
|
+
#
|
40
|
+
# :default_printer - can be set to 'text', 'gif', or 'pdf'. Default is :text
|
41
|
+
# :mode - can be set to 'cputime' or 'walltime'. Default is :cputime
|
42
|
+
# :frequency - in :cputime mode, the number of times per second the app will be sampled.
|
43
|
+
# Default is 100 (times/sec)
|
44
|
+
#
|
45
|
+
# USAGE
|
46
|
+
#
|
47
|
+
# There are two modes for the profiler
|
48
|
+
#
|
49
|
+
# First, you can run in 'simple' mode. Just visit the url you want to profile, but
|
50
|
+
# add the 'profile' and (optionally) the 'times' GET params
|
51
|
+
#
|
52
|
+
# example:
|
53
|
+
# curl http://localhost:8080/foobar?profile=true×=3
|
54
|
+
#
|
55
|
+
# Note that this will change the status, body, and headers of the response (you'll get
|
56
|
+
# back the profiling data, NOT the original response.
|
57
|
+
#
|
58
|
+
#
|
59
|
+
# The other mode is start/stop mode.
|
60
|
+
#
|
61
|
+
# example:
|
62
|
+
# curl http://localhost:8080/__start__
|
63
|
+
# curl http://localhost:8080/foobar
|
64
|
+
# curl http://localhost:8080/foobaz
|
65
|
+
# curl http://localhost:8080/__stop__
|
66
|
+
# curl http://localhost:8080/__data__
|
67
|
+
#
|
68
|
+
# In this mode, all responses are normal. You must visit __stop__ to complete profiling and
|
69
|
+
# then you can view the profiling data by visiting __data__
|
70
|
+
|
71
|
+
# PROFILING DATA OPTIONS
|
72
|
+
#
|
73
|
+
# In both simple and start/stop modes, you can add additional params to change how the data
|
74
|
+
# is displayed. In simple mode, these params are just added to the URL being profiled. In
|
75
|
+
# start/stop mode, they are added to the __data__ URL
|
76
|
+
|
77
|
+
# printer - overrides the default_printer option (see above)
|
78
|
+
# ignore - a regular expression of the area of code to ignore
|
79
|
+
# focus - a regular expression of the area of code to solely focus on.
|
80
|
+
|
81
|
+
# (for ignore and focus, please see http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html#pprof
|
82
|
+
# for more details)
|
83
|
+
#
|
84
|
+
# ACKNOWLEDGMENTS
|
85
|
+
#
|
86
|
+
# The basic idea and initial implementation was heavily influenced by Rack::Profiler from rack-contrib.
|
87
|
+
|
88
|
+
require 'rack'
|
89
|
+
require 'perftools'
|
90
|
+
require 'pstore'
|
91
|
+
require 'open4'
|
92
|
+
|
93
|
+
require 'rack/perftools_profiler/profiler_middleware'
|
94
|
+
require 'rack/perftools_profiler/action'
|
95
|
+
require 'rack/perftools_profiler/profiler'
|
96
|
+
require 'rack/perftools_profiler/start_profiling'
|
97
|
+
require 'rack/perftools_profiler/stop_profiling'
|
98
|
+
require 'rack/perftools_profiler/profile_data_action'
|
99
|
+
require 'rack/perftools_profiler/profile_once'
|
100
|
+
require 'rack/perftools_profiler/return_data'
|
101
|
+
require 'rack/perftools_profiler/call_app_directly'
|
102
|
+
|
103
|
+
module Rack::PerftoolsProfiler
|
104
|
+
|
105
|
+
def self.new(app, options={})
|
106
|
+
ProfilerMiddleware.new(app, options)
|
107
|
+
end
|
108
|
+
|
109
|
+
# helpers for testing
|
110
|
+
def self.clear_data
|
111
|
+
Profiler.clear_data
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.with_profiling_off(app, options = {})
|
115
|
+
instance = ProfilerMiddleware.new(app, options)
|
116
|
+
instance.force_stop
|
117
|
+
instance
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Rack::PerftoolsProfiler
|
2
|
+
|
3
|
+
class Action
|
4
|
+
|
5
|
+
def initialize(env, profiler, middleware)
|
6
|
+
@env = env
|
7
|
+
@request = Rack::Request.new(env)
|
8
|
+
@data_params = @request.params.clone
|
9
|
+
@profiler = profiler
|
10
|
+
@middleware = middleware
|
11
|
+
end
|
12
|
+
|
13
|
+
def act
|
14
|
+
# do nothing
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.for_env(env, profiler, middleware)
|
18
|
+
request = Rack::Request.new(env)
|
19
|
+
klass =
|
20
|
+
case request.path
|
21
|
+
when '/__start__'
|
22
|
+
StartProfiling
|
23
|
+
when '/__stop__'
|
24
|
+
StopProfiling
|
25
|
+
when '/__data__'
|
26
|
+
ReturnData
|
27
|
+
else
|
28
|
+
if ProfileOnce.has_special_param?(request)
|
29
|
+
ProfileOnce
|
30
|
+
else
|
31
|
+
CallAppDirectly
|
32
|
+
end
|
33
|
+
end
|
34
|
+
klass.new(env, profiler, middleware)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rack::PerftoolsProfiler
|
2
|
+
|
3
|
+
class ProfileDataAction < Action
|
4
|
+
|
5
|
+
def check_printer_arg
|
6
|
+
request = Rack::Request.new(@env)
|
7
|
+
printer = request.params['printer']
|
8
|
+
self.class.check_printer(printer, @env)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.check_printer(printer, env=nil)
|
12
|
+
if printer != nil && !ProfilerMiddleware::PRINTERS.member?(printer.to_sym)
|
13
|
+
message = "Invalid printer type: #{printer}. Valid printer values are #{ProfilerMiddleware::PRINTERS.join(", ")}"
|
14
|
+
raise ProfilerArgumentError, message
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Rack::PerftoolsProfiler
|
2
|
+
|
3
|
+
class ProfileOnce < ProfileDataAction
|
4
|
+
include Rack::Utils
|
5
|
+
|
6
|
+
def self.has_special_param?(request)
|
7
|
+
request.params['profile'] != nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
super
|
12
|
+
request = Rack::Request.new(@env)
|
13
|
+
@times = (request.params.fetch('times') {1}).to_i
|
14
|
+
check_printer_arg
|
15
|
+
@new_env = delete_custom_params(@env)
|
16
|
+
end
|
17
|
+
|
18
|
+
def act
|
19
|
+
@profiler.profile do
|
20
|
+
@times.times { @middleware.call_app(@new_env) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def response
|
25
|
+
@middleware.profiler_data_response(@profiler.data(@data_params))
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete_custom_params(env)
|
29
|
+
new_env = env.clone
|
30
|
+
|
31
|
+
params = Rack::Request.new(new_env).params
|
32
|
+
params.delete('profile')
|
33
|
+
params.delete('times')
|
34
|
+
params.delete('printer')
|
35
|
+
params.delete('ignore')
|
36
|
+
params.delete('focus')
|
37
|
+
|
38
|
+
new_env.delete('rack.request.query_string')
|
39
|
+
new_env.delete('rack.request.query_hash')
|
40
|
+
|
41
|
+
new_env['QUERY_STRING'] = build_query(params)
|
42
|
+
new_env
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Rack::PerftoolsProfiler
|
2
|
+
|
3
|
+
class ProfilingError < RuntimeError
|
4
|
+
|
5
|
+
attr_reader :stderr
|
6
|
+
|
7
|
+
def initialize(message, stderr)
|
8
|
+
super(message)
|
9
|
+
@stderr = stderr
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
class Profiler
|
15
|
+
|
16
|
+
def self.tmpdir
|
17
|
+
dir = nil
|
18
|
+
Dir.chdir Dir.tmpdir do dir = Dir.pwd end # HACK FOR OSX
|
19
|
+
dir
|
20
|
+
end
|
21
|
+
|
22
|
+
PROFILING_DATA_FILE = ::File.join(self.tmpdir, 'rack_perftools_profiler.prof')
|
23
|
+
PROFILING_SETTINGS_FILE = ::File.join(self.tmpdir, 'rack_perftools_profiler.config')
|
24
|
+
DEFAULT_PRINTER = :text
|
25
|
+
DEFAULT_MODE = :cputime
|
26
|
+
UNSET_FREQUENCY = -1
|
27
|
+
|
28
|
+
def initialize(app, options)
|
29
|
+
@printer = (options.delete(:default_printer) { DEFAULT_PRINTER }).to_sym
|
30
|
+
ProfileDataAction.check_printer(@printer)
|
31
|
+
@frequency = (options.delete(:frequency) { UNSET_FREQUENCY }).to_s
|
32
|
+
@mode = (options.delete(:mode) { DEFAULT_MODE }).to_sym
|
33
|
+
raise ProfilerArgumentError, "Invalid option(s): #{options.keys.join(' ')}" unless options.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def profile
|
37
|
+
start
|
38
|
+
yield
|
39
|
+
ensure
|
40
|
+
stop
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.clear_data
|
44
|
+
::File.delete(PROFILING_DATA_FILE) if ::File.exists?(PROFILING_DATA_FILE)
|
45
|
+
end
|
46
|
+
|
47
|
+
def start
|
48
|
+
set_env_vars
|
49
|
+
PerfTools::CpuProfiler.start(PROFILING_DATA_FILE)
|
50
|
+
self.profiling = true
|
51
|
+
end
|
52
|
+
|
53
|
+
def stop
|
54
|
+
PerfTools::CpuProfiler.stop
|
55
|
+
self.profiling = false
|
56
|
+
unset_env_vars
|
57
|
+
end
|
58
|
+
|
59
|
+
def profiling?
|
60
|
+
pstore_transaction(true) do |store|
|
61
|
+
store[:profiling?]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def data(options = {})
|
66
|
+
printer = (options.fetch('printer') {@printer}).to_sym
|
67
|
+
ignore = options.fetch('ignore') { nil }
|
68
|
+
focus = options.fetch('focus') { nil }
|
69
|
+
if ::File.exists?(PROFILING_DATA_FILE)
|
70
|
+
args = "--#{printer}"
|
71
|
+
args += " --ignore=#{ignore}" if ignore
|
72
|
+
args += " --focus=#{focus}" if focus
|
73
|
+
cmd = "pprof.rb #{args} #{PROFILING_DATA_FILE}"
|
74
|
+
stdout, stderr, status = run(cmd)
|
75
|
+
if(status == 0)
|
76
|
+
[printer, stdout]
|
77
|
+
else
|
78
|
+
raise ProfilingError.new("Running the command '#{cmd}' exited with status #{status}", stderr)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
[:none, nil]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def run(command)
|
88
|
+
out = err = pid = nil
|
89
|
+
status = Open4.popen4(command) do |pid, stdin, stdout, stderr|
|
90
|
+
stdin.close
|
91
|
+
pid = pid
|
92
|
+
out = stdout.read
|
93
|
+
err = stderr.read
|
94
|
+
end
|
95
|
+
[out,err,status.exitstatus]
|
96
|
+
end
|
97
|
+
|
98
|
+
def set_env_vars
|
99
|
+
ENV['CPUPROFILE_REALTIME'] = '1' if @mode == :walltime
|
100
|
+
ENV['CPUPROFILE_FREQUENCY'] = @frequency if @frequency != UNSET_FREQUENCY
|
101
|
+
end
|
102
|
+
|
103
|
+
def unset_env_vars
|
104
|
+
ENV.delete('CPUPROFILE_REALTIME')
|
105
|
+
ENV.delete('CPUPROFILE_FREQUENCY')
|
106
|
+
end
|
107
|
+
|
108
|
+
def profiling=(value)
|
109
|
+
pstore_transaction(false) do |store|
|
110
|
+
store[:profiling?] = value
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def pstore_transaction(read_only)
|
115
|
+
pstore = PStore.new(PROFILING_SETTINGS_FILE)
|
116
|
+
pstore.transaction(read_only) do
|
117
|
+
yield pstore if block_given?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Rack::PerftoolsProfiler
|
2
|
+
|
3
|
+
class ProfilerArgumentError < RuntimeError; end;
|
4
|
+
|
5
|
+
class ProfilerMiddleware
|
6
|
+
include Rack::Utils
|
7
|
+
|
8
|
+
PRINTER_CONTENT_TYPE = {
|
9
|
+
:text => 'text/plain',
|
10
|
+
:gif => 'image/gif',
|
11
|
+
:pdf => 'application/pdf'
|
12
|
+
}
|
13
|
+
|
14
|
+
PRINTERS = PRINTER_CONTENT_TYPE.keys
|
15
|
+
|
16
|
+
def initialize(app, options = {})
|
17
|
+
@app = app
|
18
|
+
@profiler = Profiler.new(@app, options.clone)
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env)
|
22
|
+
@env = env.clone
|
23
|
+
action = Action.for_env(@env, @profiler, self)
|
24
|
+
action.act
|
25
|
+
action.response
|
26
|
+
rescue ProfilerArgumentError => err
|
27
|
+
@env['rack.errors'].write(err.message)
|
28
|
+
[400, {'Content-Type' => 'text/plain'}, [err.message]]
|
29
|
+
rescue ProfilingError => err
|
30
|
+
@env['rack.errors'].write(err.message + "\n" + err.stderr)
|
31
|
+
[500, {'Content-Type' => 'text/plain'}, [err.message+"\n\n", "Standard error:\n"+err.stderr+"\n"]]
|
32
|
+
end
|
33
|
+
|
34
|
+
def call_app(env)
|
35
|
+
@app.call(env)
|
36
|
+
end
|
37
|
+
|
38
|
+
def force_stop
|
39
|
+
@profiler.stop
|
40
|
+
end
|
41
|
+
|
42
|
+
def profiler_data_response(profiling_data)
|
43
|
+
format, body = profiling_data
|
44
|
+
body = Array(body)
|
45
|
+
if format==:none
|
46
|
+
message = 'No profiling data available. Visit /__stop__ and then visit /__data__'
|
47
|
+
[404, {'Content-Type' => 'text/plain'}, [message]]
|
48
|
+
else
|
49
|
+
[200, headers(format, body), Array(body)]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def headers(printer, body)
|
56
|
+
headers = {
|
57
|
+
'Content-Type' => PRINTER_CONTENT_TYPE[printer],
|
58
|
+
'Content-Length' => content_length(body)
|
59
|
+
}
|
60
|
+
if printer==:pdf
|
61
|
+
filetype = printer
|
62
|
+
filename='profile_data'
|
63
|
+
headers['Content-Disposition'] = %(attachment; filename="#{filename}.#{filetype}")
|
64
|
+
end
|
65
|
+
headers
|
66
|
+
end
|
67
|
+
|
68
|
+
def content_length(body)
|
69
|
+
body.inject(0) { |len, part| len + bytesize(part) }.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rack::PerftoolsProfiler
|
2
|
+
|
3
|
+
class ReturnData < ProfileDataAction
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
super
|
7
|
+
check_printer_arg
|
8
|
+
end
|
9
|
+
|
10
|
+
def response
|
11
|
+
if @profiler.profiling?
|
12
|
+
[400, {'Content-Type' => 'text/plain'}, ['No profiling data available.']]
|
13
|
+
else
|
14
|
+
@middleware.profiler_data_response(@profiler.data(@data_params))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rack::PerftoolsProfiler
|
2
|
+
|
3
|
+
class StartProfiling < Action
|
4
|
+
|
5
|
+
def act
|
6
|
+
@profiler.start
|
7
|
+
end
|
8
|
+
|
9
|
+
def response
|
10
|
+
[200, {'Content-Type' => 'text/plain'},
|
11
|
+
[<<-EOS
|
12
|
+
Profiling is now enabled.
|
13
|
+
Visit the URLS that should be profiled.
|
14
|
+
When you are finished, visit /__stop__, then visit /__data__ to view the results.
|
15
|
+
EOS
|
16
|
+
]]
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rack::PerftoolsProfiler
|
2
|
+
|
3
|
+
class StopProfiling < Action
|
4
|
+
|
5
|
+
def act
|
6
|
+
@profiler.stop
|
7
|
+
end
|
8
|
+
|
9
|
+
def response
|
10
|
+
[200, {'Content-Type' => 'text/plain'},
|
11
|
+
[<<-EOS
|
12
|
+
Profiling is now disabled.
|
13
|
+
Visit /__data__ to view the results.
|
14
|
+
EOS
|
15
|
+
]]
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,407 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
ITERATIONS = case RUBY_VERSION
|
4
|
+
when /1\.9\.1/
|
5
|
+
350_000 # Ruby 1.9.1 is that we need to add extra iterations to get profiling data
|
6
|
+
else
|
7
|
+
35_000
|
8
|
+
end
|
9
|
+
|
10
|
+
# From the Rack spec (http://rack.rubyforge.org/doc/files/SPEC.html) :
|
11
|
+
# The Body must respond to each and must only yield String values. The Body should not be an instance of String.
|
12
|
+
# ... The Body commonly is an Array of Strings, the application instance itself, or a File-like object.
|
13
|
+
|
14
|
+
class RackResponseBody
|
15
|
+
include Test::Unit::Assertions
|
16
|
+
|
17
|
+
def initialize(body)
|
18
|
+
assert !body.instance_of?(String)
|
19
|
+
@body = body
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
str = ""
|
24
|
+
@body.each do |part|
|
25
|
+
str << part
|
26
|
+
end
|
27
|
+
str
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class TestApp
|
33
|
+
|
34
|
+
def call(env)
|
35
|
+
case env['PATH_INFO']
|
36
|
+
when /method1/
|
37
|
+
ITERATIONS.times do
|
38
|
+
self.class.new.method1
|
39
|
+
end
|
40
|
+
GC.start
|
41
|
+
when /method2/
|
42
|
+
ITERATIONS.times do
|
43
|
+
self.class.new.method2
|
44
|
+
end
|
45
|
+
GC.start
|
46
|
+
end
|
47
|
+
[200, {}, ['Done']]
|
48
|
+
end
|
49
|
+
|
50
|
+
def method1
|
51
|
+
100.times do
|
52
|
+
1+2+3+4+5
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def method2
|
57
|
+
100.times do
|
58
|
+
1+2+3+4+5
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
class RackPerftoolsProfilerTest < Test::Unit::TestCase
|
65
|
+
include Rack::PerftoolsProfiler
|
66
|
+
|
67
|
+
context "testing Rack::PerftoolsProfiler" do
|
68
|
+
|
69
|
+
setup do
|
70
|
+
@app = lambda { |env| ITERATIONS.times {1+2+3+4+5}; [200, {'Content-Type' => 'text/plain'}, ['Oh hai der']] }
|
71
|
+
@slow_app = lambda { |env| ITERATIONS.times {1+2+3+4+5}; [200, {'Content-Type' => 'text/plain'}, ['slow app']] }
|
72
|
+
@start_env = Rack::MockRequest.env_for('/__start__')
|
73
|
+
@stop_env = Rack::MockRequest.env_for('/__stop__')
|
74
|
+
@data_env = Rack::MockRequest.env_for('/__data__')
|
75
|
+
@root_request_env = Rack::MockRequest.env_for("/")
|
76
|
+
@profiled_request_env = Rack::MockRequest.env_for("/", :params => "profile=true")
|
77
|
+
@profiled_request_env_with_times = Rack::MockRequest.env_for("/", :params => "profile=true×=2")
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'Rack::Lint checks' do
|
81
|
+
|
82
|
+
should 'pass all Lint checks with text printer' do
|
83
|
+
app = Rack::Lint.new(Rack::PerftoolsProfiler.with_profiling_off(@slow_app, :default_printer => 'text'))
|
84
|
+
app.call(@root_request_env)
|
85
|
+
app.call(@profiled_request_env)
|
86
|
+
app.call(@profiled_request_env_with_times)
|
87
|
+
app.call(@start_env)
|
88
|
+
app.call(@stop_env)
|
89
|
+
app.call(@data_env)
|
90
|
+
end
|
91
|
+
|
92
|
+
should 'pass all Lint checks with text printer' do
|
93
|
+
app = Rack::Lint.new(Rack::PerftoolsProfiler.with_profiling_off(@slow_app, :default_printer => 'gif'))
|
94
|
+
app.call(@root_request_env)
|
95
|
+
app.call(@profiled_request_env)
|
96
|
+
app.call(@profiled_request_env_with_times)
|
97
|
+
app.call(@start_env)
|
98
|
+
app.call(@stop_env)
|
99
|
+
app.call(@data_env)
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
should 'raise error if options contains invalid key' do
|
105
|
+
error = assert_raises ProfilerArgumentError do
|
106
|
+
Rack::PerftoolsProfiler.with_profiling_off(@app, :mode => 'walltime', :default_printer => 'gif', :foobar => 'baz')
|
107
|
+
end
|
108
|
+
assert_match(/Invalid option\(s\)\: foobar/, error.message)
|
109
|
+
end
|
110
|
+
|
111
|
+
should 'raise error if printer is invalid' do
|
112
|
+
error = assert_raises ProfilerArgumentError do
|
113
|
+
Rack::PerftoolsProfiler.with_profiling_off(@app, :mode => 'walltime', :default_printer => 'badprinter')
|
114
|
+
end
|
115
|
+
assert_match(/Invalid printer type\: badprinter/, error.message)
|
116
|
+
end
|
117
|
+
|
118
|
+
should 'not modify options hash' do
|
119
|
+
options = {:mode => 'walltime', :default_printer => 'gif'}
|
120
|
+
old_options = options.clone
|
121
|
+
Rack::PerftoolsProfiler.with_profiling_off(@app, options)
|
122
|
+
assert_equal old_options, options
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'without profiling' do
|
126
|
+
|
127
|
+
should 'call app directly' do
|
128
|
+
status, headers, body = Rack::PerftoolsProfiler.with_profiling_off(@app).call(@root_request_env)
|
129
|
+
assert_equal 200, status
|
130
|
+
assert_equal 'text/plain', headers['Content-Type']
|
131
|
+
assert_equal 'Oh hai der', RackResponseBody.new(body).to_s
|
132
|
+
end
|
133
|
+
|
134
|
+
should 'provide no data by default when __data__ is called' do
|
135
|
+
Rack::PerftoolsProfiler.clear_data
|
136
|
+
status, headers, body = Rack::PerftoolsProfiler.with_profiling_off(@app, :default_printer => 'text').call(@data_env)
|
137
|
+
assert_equal 404, status
|
138
|
+
assert_equal 'text/plain', headers['Content-Type']
|
139
|
+
assert_match(/No profiling data available./, RackResponseBody.new(body).to_s)
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'simple profiling mode' do
|
145
|
+
|
146
|
+
should 'default to text printer' do
|
147
|
+
_, headers, _ = Rack::PerftoolsProfiler.new(@app).call(@profiled_request_env)
|
148
|
+
assert_equal "text/plain", headers['Content-Type']
|
149
|
+
end
|
150
|
+
|
151
|
+
should "set CPUPROFILE_REALTIME to 1 if mode is 'walltime'" do
|
152
|
+
realtime = ENV['CPUPROFILE_REALTIME']
|
153
|
+
assert_nil realtime
|
154
|
+
app = lambda do |env|
|
155
|
+
realtime = ENV['CPUPROFILE_REALTIME']
|
156
|
+
[200, {}, ["hi"]]
|
157
|
+
end
|
158
|
+
Rack::PerftoolsProfiler.new(app, :mode => 'walltime').call(@profiled_request_env)
|
159
|
+
assert_equal '1', realtime
|
160
|
+
end
|
161
|
+
|
162
|
+
should 'alter CPUPROFILE_FREQUENCY if frequency is set' do
|
163
|
+
frequency = ENV['CPUPROFILE_FREQUENCY']
|
164
|
+
assert_nil frequency
|
165
|
+
app = lambda do |env|
|
166
|
+
frequency = ENV['CPUPROFILE_FREQUENCY']
|
167
|
+
[200, {}, ["hi"]]
|
168
|
+
end
|
169
|
+
Rack::PerftoolsProfiler.new(app, :frequency => 500).call(@profiled_request_env)
|
170
|
+
assert_equal '500', frequency
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'text printer' do
|
174
|
+
|
175
|
+
should 'return profiling data' do
|
176
|
+
_, _, body = Rack::PerftoolsProfiler.new(@slow_app, :default_printer => 'text').call(@profiled_request_env)
|
177
|
+
assert_match(/Total: \d+ samples/, RackResponseBody.new(body).to_s)
|
178
|
+
end
|
179
|
+
|
180
|
+
should 'have Content-Type text/plain' do
|
181
|
+
_, headers, _ = Rack::PerftoolsProfiler.new(@app, :default_printer => 'text').call(@profiled_request_env)
|
182
|
+
assert_equal "text/plain", headers['Content-Type']
|
183
|
+
end
|
184
|
+
|
185
|
+
should 'have Content-Length' do
|
186
|
+
_, headers, _ = Rack::PerftoolsProfiler.new(@slow_app, :default_printer => 'text').call(@profiled_request_env)
|
187
|
+
assert (headers.fetch('Content-Length').to_i > 500)
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'gif printer' do
|
193
|
+
|
194
|
+
should 'gif printer has Content-Type image/gif' do
|
195
|
+
_, headers, _ = Rack::PerftoolsProfiler.new(@app, :default_printer => 'gif').call(@profiled_request_env)
|
196
|
+
assert_equal "image/gif", headers['Content-Type']
|
197
|
+
end
|
198
|
+
|
199
|
+
should 'gif printer has Content-Length' do
|
200
|
+
_, headers, _ = Rack::PerftoolsProfiler.new(@slow_app, :default_printer => 'gif').call(@profiled_request_env)
|
201
|
+
assert headers.fetch('Content-Length').to_i > 25_000
|
202
|
+
end
|
203
|
+
|
204
|
+
should 'pdf printer has Content-Type application/pdf' do
|
205
|
+
_, headers, _ = Rack::PerftoolsProfiler.new(@app, :default_printer => 'pdf').call(@profiled_request_env)
|
206
|
+
assert_equal "application/pdf", headers['Content-Type']
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
context 'pdf printer' do
|
212
|
+
|
213
|
+
should 'have default filename' do
|
214
|
+
_, headers, _ = Rack::PerftoolsProfiler.new(@app, :default_printer => 'pdf').call(@profiled_request_env)
|
215
|
+
assert_equal %q{attachment; filename="profile_data.pdf"}, headers['Content-Disposition']
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
should 'be able to call app multiple times' do
|
221
|
+
env = Rack::MockRequest.env_for('/', :params => 'profile=true×=3')
|
222
|
+
app = @app.clone
|
223
|
+
app.expects(:call).times(3)
|
224
|
+
Rack::PerftoolsProfiler.new(app, :default_printer => 'text').call(env)
|
225
|
+
end
|
226
|
+
|
227
|
+
should "allow 'printer' param override :default_printer option'" do
|
228
|
+
env = Rack::MockRequest.env_for('/', :params => 'profile=true&printer=gif')
|
229
|
+
_, headers, _ = Rack::PerftoolsProfiler.new(@app, :default_printer => 'pdf').call(env)
|
230
|
+
assert_equal 'image/gif', headers['Content-Type']
|
231
|
+
end
|
232
|
+
|
233
|
+
should 'give 400 if printer is invalid' do
|
234
|
+
env = Rack::MockRequest.env_for('/', :params => 'profile=true&printer=badprinter')
|
235
|
+
status, _, _ = Rack::PerftoolsProfiler.new(@app).call(env)
|
236
|
+
assert_equal 400, status
|
237
|
+
end
|
238
|
+
|
239
|
+
should 'send Rack environment to underlying application (minus special profiling GET params)' do
|
240
|
+
env = Rack::MockRequest.env_for('/', :params => 'profile=true×=1¶m=value&printer=gif&focus=foo&ignore=bar')
|
241
|
+
old_env = env.clone
|
242
|
+
expected_env = env.clone
|
243
|
+
expected_env["QUERY_STRING"] = 'param=value'
|
244
|
+
app = @app.clone
|
245
|
+
app.expects(:call).with(expected_env)
|
246
|
+
Rack::PerftoolsProfiler.new(app, :default_printer => 'gif').call(env)
|
247
|
+
assert_equal env, old_env
|
248
|
+
end
|
249
|
+
|
250
|
+
should "accept 'focus' param" do
|
251
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(TestApp.new, :default_printer => 'text', :mode => 'walltime')
|
252
|
+
custom_env = Rack::MockRequest.env_for('/method1', :params => 'profile=true&focus=method1')
|
253
|
+
status, headers, body = profiled_app.call(custom_env)
|
254
|
+
assert_no_match(/garbage/, RackResponseBody.new(body).to_s)
|
255
|
+
end
|
256
|
+
|
257
|
+
should "accept 'ignore' param" do
|
258
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(TestApp.new, :default_printer => 'text', :mode => 'walltime')
|
259
|
+
custom_env = Rack::MockRequest.env_for('/method1', :params => 'profile=true&ignore=method1')
|
260
|
+
status, headers, body = profiled_app.call(custom_env)
|
261
|
+
assert_match(/garbage/, RackResponseBody.new(body).to_s)
|
262
|
+
assert_no_match(/method1/, RackResponseBody.new(body).to_s)
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'start/stop profiling' do
|
268
|
+
|
269
|
+
should "set CPUPROFILE_REALTIME to 1 if mode is 'walltime' " do
|
270
|
+
realtime = ENV['CPUPROFILE_REALTIME']
|
271
|
+
assert_nil realtime
|
272
|
+
app = lambda do |env|
|
273
|
+
realtime = ENV['CPUPROFILE_REALTIME']
|
274
|
+
[200, {}, ["hi"]]
|
275
|
+
end
|
276
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :mode => 'walltime')
|
277
|
+
profiled_app.call(@start_env)
|
278
|
+
profiled_app.call(@root_request_env)
|
279
|
+
profiled_app.call(@stop_env)
|
280
|
+
assert_equal '1', realtime
|
281
|
+
end
|
282
|
+
|
283
|
+
should 'alter CPUPROFILE_FREQUENCY if frequency is set' do
|
284
|
+
frequency = ENV['CPUPROFILE_FREQUENCY']
|
285
|
+
assert_nil frequency
|
286
|
+
app = lambda do |env|
|
287
|
+
frequency = ENV['CPUPROFILE_FREQUENCY']
|
288
|
+
[200, {}, ["hi"]]
|
289
|
+
end
|
290
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :frequency => 250)
|
291
|
+
profiled_app.call(@start_env)
|
292
|
+
profiled_app.call(@root_request_env)
|
293
|
+
assert_equal '250', frequency
|
294
|
+
end
|
295
|
+
|
296
|
+
context 'when profiling is on' do
|
297
|
+
|
298
|
+
should 'not provide profiling data when __data__ is called' do
|
299
|
+
Rack::PerftoolsProfiler.clear_data
|
300
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(@app, :default_printer => 'text')
|
301
|
+
profiled_app.call(@start_env)
|
302
|
+
profiled_app.call(@root_request_env)
|
303
|
+
status, _, body = profiled_app.call(@data_env)
|
304
|
+
assert_equal 400, status
|
305
|
+
assert_match(/No profiling data available./, RackResponseBody.new(body).to_s)
|
306
|
+
end
|
307
|
+
|
308
|
+
should 'pass on profiling params in environment' do
|
309
|
+
env = Rack::MockRequest.env_for('/', :params => 'times=2')
|
310
|
+
old_env = env.clone
|
311
|
+
app = @app.clone
|
312
|
+
expected_env = env.clone
|
313
|
+
expected_env['rack.request.query_string'] = 'times=2'
|
314
|
+
expected_env['rack.request.query_hash'] = {'times' => '2'}
|
315
|
+
app.expects(:call).with(expected_env)
|
316
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :default_printer => 'text')
|
317
|
+
profiled_app.call(@start_env)
|
318
|
+
profiled_app.call(env)
|
319
|
+
assert_equal env, old_env
|
320
|
+
end
|
321
|
+
|
322
|
+
should 'pass on non-profiling params in environment' do
|
323
|
+
env = Rack::MockRequest.env_for('/', :params => 'param=value')
|
324
|
+
old_env = env.clone
|
325
|
+
app = @app.clone
|
326
|
+
expected_env = env.clone
|
327
|
+
expected_env['rack.request.query_string'] = 'param=value'
|
328
|
+
expected_env['rack.request.query_hash'] = {'param' => 'value'}
|
329
|
+
app.expects(:call).with(expected_env)
|
330
|
+
profiled_app = Rack::PerftoolsProfiler.new(app, :default_printer => 'text')
|
331
|
+
profiled_app.call(@start_env)
|
332
|
+
profiled_app.call(env)
|
333
|
+
assert_equal env, old_env
|
334
|
+
end
|
335
|
+
|
336
|
+
should 'not alter regular calls' do
|
337
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(@app, :default_printer => 'gif')
|
338
|
+
profiled_app.call(@start_env)
|
339
|
+
status, headers, body = profiled_app.call(@root_request_env)
|
340
|
+
assert_equal 200, status
|
341
|
+
assert_equal 'text/plain', headers['Content-Type']
|
342
|
+
assert_equal 'Oh hai der', RackResponseBody.new(body).to_s
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
347
|
+
context 'after profiling is finished' do
|
348
|
+
|
349
|
+
should 'return profiling data when __data__ is called' do
|
350
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(@app, :default_printer => 'gif')
|
351
|
+
profiled_app.call(@start_env)
|
352
|
+
profiled_app.call(@root_request_env)
|
353
|
+
profiled_app.call(@stop_env)
|
354
|
+
status, headers, body = profiled_app.call(@data_env)
|
355
|
+
assert_equal 200, status
|
356
|
+
assert_equal "image/gif", headers['Content-Type']
|
357
|
+
end
|
358
|
+
|
359
|
+
end
|
360
|
+
|
361
|
+
should 'keeps data from multiple calls' do
|
362
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(TestApp.new, :default_printer => 'text', :mode => 'walltime')
|
363
|
+
profiled_app.call(@start_env)
|
364
|
+
profiled_app.call(Rack::MockRequest.env_for('/method1'))
|
365
|
+
profiled_app.call(Rack::MockRequest.env_for('/method2'))
|
366
|
+
profiled_app.call(@stop_env)
|
367
|
+
status, headers, body = profiled_app.call(@data_env)
|
368
|
+
assert_match(/method1/, RackResponseBody.new(body).to_s)
|
369
|
+
assert_match(/method2/, RackResponseBody.new(body).to_s)
|
370
|
+
end
|
371
|
+
|
372
|
+
should "allow 'printer' param to override :default_printer option'" do
|
373
|
+
profiled_app = Rack::PerftoolsProfiler.new(@app, :default_printer => 'pdf')
|
374
|
+
profiled_app.call(@start_env)
|
375
|
+
profiled_app.call(@root_request_env)
|
376
|
+
profiled_app.call(@stop_env)
|
377
|
+
custom_data_env = Rack::MockRequest.env_for('__data__', :params => 'printer=gif')
|
378
|
+
_, headers, _ = profiled_app.call(custom_data_env)
|
379
|
+
assert_equal 'image/gif', headers['Content-Type']
|
380
|
+
end
|
381
|
+
|
382
|
+
should "accept 'focus' param" do
|
383
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(TestApp.new, :default_printer => 'text', :mode => 'walltime')
|
384
|
+
profiled_app.call(@start_env)
|
385
|
+
profiled_app.call(Rack::MockRequest.env_for('/method1'))
|
386
|
+
profiled_app.call(Rack::MockRequest.env_for('/method2'))
|
387
|
+
profiled_app.call(@stop_env)
|
388
|
+
custom_data_env = Rack::MockRequest.env_for('__data__', :params => 'focus=method1')
|
389
|
+
status, headers, body = profiled_app.call(custom_data_env)
|
390
|
+
assert_no_match(/method2/, RackResponseBody.new(body).to_s)
|
391
|
+
end
|
392
|
+
|
393
|
+
should "accept 'ignore' param" do
|
394
|
+
profiled_app = Rack::PerftoolsProfiler.with_profiling_off(TestApp.new, :default_printer => 'text', :mode => 'walltime')
|
395
|
+
profiled_app.call(@start_env)
|
396
|
+
profiled_app.call(Rack::MockRequest.env_for('/method1'))
|
397
|
+
profiled_app.call(Rack::MockRequest.env_for('/method2'))
|
398
|
+
profiled_app.call(@stop_env)
|
399
|
+
custom_data_env = Rack::MockRequest.env_for('__data__', :params => 'ignore=method1')
|
400
|
+
status, headers, body = profiled_app.call(custom_data_env)
|
401
|
+
assert_no_match(/method1/, RackResponseBody.new(body).to_s)
|
402
|
+
end
|
403
|
+
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
require 'mocha'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
require 'rack/perftools_profiler'
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-perftools_profiler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Ben Brinckerhoff
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-04-19 00:00:00 -06:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: perftools.rb
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 4
|
30
|
+
- 0
|
31
|
+
version: 0.4.0
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rack
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 1
|
43
|
+
- 0
|
44
|
+
- 0
|
45
|
+
version: 1.0.0
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: open4
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 1
|
57
|
+
- 0
|
58
|
+
- 1
|
59
|
+
version: 1.0.1
|
60
|
+
type: :runtime
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rack
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 1
|
71
|
+
- 1
|
72
|
+
- 0
|
73
|
+
version: 1.1.0
|
74
|
+
type: :development
|
75
|
+
version_requirements: *id004
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: shoulda
|
78
|
+
prerelease: false
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ~>
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
segments:
|
84
|
+
- 2
|
85
|
+
- 10
|
86
|
+
- 2
|
87
|
+
version: 2.10.2
|
88
|
+
type: :development
|
89
|
+
version_requirements: *id005
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: mocha
|
92
|
+
prerelease: false
|
93
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ~>
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
- 9
|
100
|
+
- 8
|
101
|
+
version: 0.9.8
|
102
|
+
type: :development
|
103
|
+
version_requirements: *id006
|
104
|
+
description: Middleware for profiling Rack-compatible apps using perftools.rb
|
105
|
+
email: ben@bbrinck.com
|
106
|
+
executables: []
|
107
|
+
|
108
|
+
extensions: []
|
109
|
+
|
110
|
+
extra_rdoc_files:
|
111
|
+
- LICENSE
|
112
|
+
- README.rdoc
|
113
|
+
files:
|
114
|
+
- .document
|
115
|
+
- .gitignore
|
116
|
+
- LICENSE
|
117
|
+
- README.rdoc
|
118
|
+
- Rakefile
|
119
|
+
- VERSION
|
120
|
+
- lib/rack/perftools_profiler.rb
|
121
|
+
- lib/rack/perftools_profiler/action.rb
|
122
|
+
- lib/rack/perftools_profiler/call_app_directly.rb
|
123
|
+
- lib/rack/perftools_profiler/profile_data_action.rb
|
124
|
+
- lib/rack/perftools_profiler/profile_once.rb
|
125
|
+
- lib/rack/perftools_profiler/profiler.rb
|
126
|
+
- lib/rack/perftools_profiler/profiler_middleware.rb
|
127
|
+
- lib/rack/perftools_profiler/return_data.rb
|
128
|
+
- lib/rack/perftools_profiler/start_profiling.rb
|
129
|
+
- lib/rack/perftools_profiler/stop_profiling.rb
|
130
|
+
- test/rack-perftools-profiler_test.rb
|
131
|
+
- test/test_helper.rb
|
132
|
+
has_rdoc: true
|
133
|
+
homepage: http://github.com/bhb/rack-perftools_profiler
|
134
|
+
licenses: []
|
135
|
+
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options:
|
138
|
+
- --charset=UTF-8
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
segments:
|
146
|
+
- 0
|
147
|
+
version: "0"
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
segments:
|
153
|
+
- 0
|
154
|
+
version: "0"
|
155
|
+
requirements: []
|
156
|
+
|
157
|
+
rubyforge_project:
|
158
|
+
rubygems_version: 1.3.6
|
159
|
+
signing_key:
|
160
|
+
specification_version: 3
|
161
|
+
summary: Middleware for profiling Rack-compatible apps using perftools.rb
|
162
|
+
test_files:
|
163
|
+
- test/rack-perftools-profiler_test.rb
|
164
|
+
- test/test_helper.rb
|