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 ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
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&times=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&times=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,15 @@
1
+ module Rack::PerftoolsProfiler
2
+
3
+ class CallAppDirectly < Action
4
+
5
+ def act
6
+ @result = @middleware.call_app(@env)
7
+ end
8
+
9
+ def response
10
+ @result
11
+ end
12
+
13
+ end
14
+
15
+ 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&times=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&times=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&times=1&param=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
@@ -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