aleksi-rack-perftools_profiler 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ .rvmrc
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
+ * :bundler - run profiler binary from bundle if set to true
50
+ * :default_printer - can be set to 'text', 'gif', or 'pdf'. Default is :text
51
+ * :mode - can be set to 'cputime' or 'walltime'. Default is :cputime
52
+ * :frequency - in :cputime mode, the number of times per second the app will be sampled. Default is 100 (times/sec)
53
+
54
+ == Usage
55
+
56
+ There are two modes for the profiler
57
+
58
+ First, you can run in 'simple' mode. Just visit the url you want to profile, but
59
+ add the 'profile' and (optionally) the 'times' GET params (which will rerun the action
60
+ the specified number of times).
61
+
62
+ Example:
63
+ curl http://localhost:8080/foobar?profile=true&times=3
64
+
65
+ Note that this will change the status, body, and headers of the response (you'll get
66
+ back the profiling data, NOT the original response).
67
+
68
+ The other mode is start/stop mode.
69
+
70
+ Example:
71
+ curl http://localhost:8080/__start__
72
+ curl http://localhost:8080/foobar
73
+ curl http://localhost:8080/foobaz
74
+ curl http://localhost:8080/__stop__
75
+ curl http://localhost:8080/__data__
76
+
77
+ In this mode, all responses are normal. You must visit \_\_stop\_\_ to complete profiling and
78
+ then you can view the profiling data by visiting \_\_data\_\_
79
+
80
+ == Profiling Data Options
81
+
82
+ In both simple and start/stop modes, you can add additional params to change how the data
83
+ is displayed. In simple mode, these params are just added to the URL being profiled. In
84
+ start/stop mode, they are added to the \_\_data\_\_ URL
85
+
86
+ * printer - overrides the default_printer option (see above)
87
+ * ignore - a regular expression of the area of code to ignore
88
+ * focus - a regular expression of the area of code to solely focus on.
89
+
90
+ (for 'ignore' and 'focus', please see http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html#pprof
91
+ for more details)
92
+
93
+ == Acknowledgments
94
+
95
+ A huge thanks to Aman Gupta for the awesome perftools.rb gem.
96
+
97
+ The basic idea and initial implementation of the middleware was heavily influenced by
98
+ Rack::Profiler from rack-contrib.
99
+
100
+ == Note on Patches/Pull Requests
101
+
102
+ * Fork the project.
103
+ * Make your feature addition or bug fix.
104
+ * Add tests for it. This is important so I don't break it in a
105
+ future version unintentionally.
106
+ * Commit, do not mess with rakefile, version, or history.
107
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
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 = 'aleksi-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'
14
+ gem.add_dependency 'rack', '~> 1.0'
15
+ gem.add_dependency('open4', '~> 1.0')
16
+ gem.add_development_dependency 'rack', '~> 1.1'
17
+ gem.add_development_dependency 'shoulda', '~> 2.10'
18
+ gem.add_development_dependency 'mocha', '~> 0.9'
19
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
+ end
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
24
+ end
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.3
@@ -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,125 @@
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
+ @bundler = (options.delete(:bundler) { false })
34
+ raise ProfilerArgumentError, "Invalid option(s): #{options.keys.join(' ')}" unless options.empty?
35
+ end
36
+
37
+ def profile
38
+ start
39
+ yield
40
+ ensure
41
+ stop
42
+ end
43
+
44
+ def self.clear_data
45
+ ::File.delete(PROFILING_DATA_FILE) if ::File.exists?(PROFILING_DATA_FILE)
46
+ end
47
+
48
+ def start
49
+ set_env_vars
50
+ PerfTools::CpuProfiler.start(PROFILING_DATA_FILE)
51
+ self.profiling = true
52
+ end
53
+
54
+ def stop
55
+ PerfTools::CpuProfiler.stop
56
+ self.profiling = false
57
+ unset_env_vars
58
+ end
59
+
60
+ def profiling?
61
+ pstore_transaction(true) do |store|
62
+ store[:profiling?]
63
+ end
64
+ end
65
+
66
+ def data(options = {})
67
+ printer = (options.fetch('printer') {@printer}).to_sym
68
+ ignore = options.fetch('ignore') { nil }
69
+ focus = options.fetch('focus') { nil }
70
+ if ::File.exists?(PROFILING_DATA_FILE)
71
+ args = "--#{printer}"
72
+ args += " --ignore=#{ignore}" if ignore
73
+ args += " --focus=#{focus}" if focus
74
+ cmd = "pprof.rb #{args} #{PROFILING_DATA_FILE}"
75
+ cmd = "bundle exec " + cmd if @bundler
76
+ stdout, stderr, status = run(cmd)
77
+ if(status == 0)
78
+ [printer, stdout]
79
+ else
80
+ raise ProfilingError.new("Running the command '#{cmd}' exited with status #{status}", stderr)
81
+ end
82
+ else
83
+ [:none, nil]
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def run(command)
90
+ out = err = pid = nil
91
+ status = Open4.popen4(command) do |pid, stdin, stdout, stderr|
92
+ stdin.close
93
+ pid = pid
94
+ out = stdout.read
95
+ err = stderr.read
96
+ end
97
+ [out,err,status.exitstatus]
98
+ end
99
+
100
+ def set_env_vars
101
+ ENV['CPUPROFILE_REALTIME'] = '1' if @mode == :walltime
102
+ ENV['CPUPROFILE_FREQUENCY'] = @frequency if @frequency != UNSET_FREQUENCY
103
+ end
104
+
105
+ def unset_env_vars
106
+ ENV.delete('CPUPROFILE_REALTIME')
107
+ ENV.delete('CPUPROFILE_FREQUENCY')
108
+ end
109
+
110
+ def profiling=(value)
111
+ pstore_transaction(false) do |store|
112
+ store[:profiling?] = value
113
+ end
114
+ end
115
+
116
+ def pstore_transaction(read_only)
117
+ pstore = PStore.new(PROFILING_SETTINGS_FILE)
118
+ pstore.transaction(read_only) do
119
+ yield pstore if block_given?
120
+ end
121
+ end
122
+
123
+ end
124
+
125
+ 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,121 @@
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
+ # :bundler - run profiler binary from bundle if set to true
41
+ # :default_printer - can be set to 'text', 'gif', or 'pdf'. Default is :text
42
+ # :mode - can be set to 'cputime' or 'walltime'. Default is :cputime
43
+ # :frequency - in :cputime mode, the number of times per second the app will be sampled.
44
+ # Default is 100 (times/sec)
45
+ #
46
+ # USAGE
47
+ #
48
+ # There are two modes for the profiler
49
+ #
50
+ # First, you can run in 'simple' mode. Just visit the url you want to profile, but
51
+ # add the 'profile' and (optionally) the 'times' GET params
52
+ #
53
+ # example:
54
+ # curl http://localhost:8080/foobar?profile=true&times=3
55
+ #
56
+ # Note that this will change the status, body, and headers of the response (you'll get
57
+ # back the profiling data, NOT the original response.
58
+ #
59
+ #
60
+ # The other mode is start/stop mode.
61
+ #
62
+ # example:
63
+ # curl http://localhost:8080/__start__
64
+ # curl http://localhost:8080/foobar
65
+ # curl http://localhost:8080/foobaz
66
+ # curl http://localhost:8080/__stop__
67
+ # curl http://localhost:8080/__data__
68
+ #
69
+ # In this mode, all responses are normal. You must visit __stop__ to complete profiling and
70
+ # then you can view the profiling data by visiting __data__
71
+
72
+ # PROFILING DATA OPTIONS
73
+ #
74
+ # In both simple and start/stop modes, you can add additional params to change how the data
75
+ # is displayed. In simple mode, these params are just added to the URL being profiled. In
76
+ # start/stop mode, they are added to the __data__ URL
77
+
78
+ # printer - overrides the default_printer option (see above)
79
+ # ignore - a regular expression of the area of code to ignore
80
+ # focus - a regular expression of the area of code to solely focus on.
81
+
82
+ # (for ignore and focus, please see http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html#pprof
83
+ # for more details)
84
+ #
85
+ # ACKNOWLEDGMENTS
86
+ #
87
+ # The basic idea and initial implementation was heavily influenced by Rack::Profiler from rack-contrib.
88
+
89
+ require 'rack'
90
+ require 'perftools'
91
+ require 'pstore'
92
+ require 'open4'
93
+
94
+ require 'rack/perftools_profiler/profiler_middleware'
95
+ require 'rack/perftools_profiler/action'
96
+ require 'rack/perftools_profiler/profiler'
97
+ require 'rack/perftools_profiler/start_profiling'
98
+ require 'rack/perftools_profiler/stop_profiling'
99
+ require 'rack/perftools_profiler/profile_data_action'
100
+ require 'rack/perftools_profiler/profile_once'
101
+ require 'rack/perftools_profiler/return_data'
102
+ require 'rack/perftools_profiler/call_app_directly'
103
+
104
+ module Rack::PerftoolsProfiler
105
+
106
+ def self.new(app, options={})
107
+ ProfilerMiddleware.new(app, options)
108
+ end
109
+
110
+ # helpers for testing
111
+ def self.clear_data
112
+ Profiler.clear_data
113
+ end
114
+
115
+ def self.with_profiling_off(app, options = {})
116
+ instance = ProfilerMiddleware.new(app, options)
117
+ instance.force_stop
118
+ instance
119
+ end
120
+
121
+ 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 gif 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,175 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aleksi-rack-perftools_profiler
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 3
10
+ version: 0.0.3
11
+ platform: ruby
12
+ authors:
13
+ - Ben Brinckerhoff
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-19 00:00:00 +04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: perftools.rb
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ - 4
33
+ version: "0.4"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rack
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 15
45
+ segments:
46
+ - 1
47
+ - 0
48
+ version: "1.0"
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: open4
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 15
60
+ segments:
61
+ - 1
62
+ - 0
63
+ version: "1.0"
64
+ type: :runtime
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: rack
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ~>
73
+ - !ruby/object:Gem::Version
74
+ hash: 13
75
+ segments:
76
+ - 1
77
+ - 1
78
+ version: "1.1"
79
+ type: :development
80
+ version_requirements: *id004
81
+ - !ruby/object:Gem::Dependency
82
+ name: shoulda
83
+ prerelease: false
84
+ requirement: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ hash: 23
90
+ segments:
91
+ - 2
92
+ - 10
93
+ version: "2.10"
94
+ type: :development
95
+ version_requirements: *id005
96
+ - !ruby/object:Gem::Dependency
97
+ name: mocha
98
+ prerelease: false
99
+ requirement: &id006 !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ~>
103
+ - !ruby/object:Gem::Version
104
+ hash: 25
105
+ segments:
106
+ - 0
107
+ - 9
108
+ version: "0.9"
109
+ type: :development
110
+ version_requirements: *id006
111
+ description: Middleware for profiling Rack-compatible apps using perftools.rb
112
+ email: ben@bbrinck.com
113
+ executables: []
114
+
115
+ extensions: []
116
+
117
+ extra_rdoc_files:
118
+ - LICENSE
119
+ - README.rdoc
120
+ files:
121
+ - .document
122
+ - .gitignore
123
+ - LICENSE
124
+ - README.rdoc
125
+ - Rakefile
126
+ - VERSION
127
+ - lib/rack/perftools_profiler.rb
128
+ - lib/rack/perftools_profiler/action.rb
129
+ - lib/rack/perftools_profiler/call_app_directly.rb
130
+ - lib/rack/perftools_profiler/profile_data_action.rb
131
+ - lib/rack/perftools_profiler/profile_once.rb
132
+ - lib/rack/perftools_profiler/profiler.rb
133
+ - lib/rack/perftools_profiler/profiler_middleware.rb
134
+ - lib/rack/perftools_profiler/return_data.rb
135
+ - lib/rack/perftools_profiler/start_profiling.rb
136
+ - lib/rack/perftools_profiler/stop_profiling.rb
137
+ - test/rack-perftools-profiler_test.rb
138
+ - test/test_helper.rb
139
+ has_rdoc: true
140
+ homepage: http://github.com/bhb/rack-perftools_profiler
141
+ licenses: []
142
+
143
+ post_install_message:
144
+ rdoc_options:
145
+ - --charset=UTF-8
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ hash: 3
154
+ segments:
155
+ - 0
156
+ version: "0"
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ none: false
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ hash: 3
163
+ segments:
164
+ - 0
165
+ version: "0"
166
+ requirements: []
167
+
168
+ rubyforge_project:
169
+ rubygems_version: 1.3.7
170
+ signing_key:
171
+ specification_version: 3
172
+ summary: Middleware for profiling Rack-compatible apps using perftools.rb
173
+ test_files:
174
+ - test/rack-perftools-profiler_test.rb
175
+ - test/test_helper.rb