Skip to content

Commit

Permalink
Added password protection for single-request profiling
Browse files Browse the repository at this point in the history
  • Loading branch information
bhb committed Jul 25, 2011
1 parent 8334714 commit e610424
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 16 deletions.
17 changes: 14 additions & 3 deletions README.rdoc
Expand Up @@ -54,11 +54,12 @@ For Rack::Builder, call 'use' inside the Builder constructor block

== Options

* :default_printer - can be set to 'text', 'gif', or 'pdf'. Default is :text
* :default_printer - can be set to 'text', 'gif', or 'pdf'. Default is 'text'.
* :mode - can be set to 'cputime', 'methods', 'objects', 'walltime'. Default is :cputime. See the 'Profiling Modes' section below.
* :frequency - in :cputime mode, the number of times per second the app will be sampled. Default is 100 (times/sec)
* :bundler - run the profiler binary using 'bundle' if set to true. Default is false
* :frequency - in :cputime mode, the number of times per second the app will be sampled. Default is 100 (times/sec).
* :bundler - run the profiler binary using 'bundle' if set to true. Default is false.
* :gemfile_dir - directory with Gemfile. Default is the current directory.
* :password - password-protect profiling.

== Usage

Expand Down Expand Up @@ -139,6 +140,16 @@ When profiling using multiple requests, add the option when visiting \_\_start\_

If the 'mode' option is omitted, the middleware will default to the mode specified at configuration.

== Profiling in production

It is recommended that you always profile your application in the 'production' environment (using `rails server -e production` or an equivalent), since there can be important differences between 'development' and 'production' that may affect performance.

However, it is recommended that you profile your application on a development or staging machine rather than on a production machine. This is because profiling with multiple requests *will not* work if your app is running in multiple Ruby server processes.

Profiling a single request will work if there are multiple server processes. If your staging machine is publicly accessible, you can password-protect single-request profiling by using the `:password` option and then using the `profile` GET parameter to provide the password:

curl http://localhost:3000/foobar?profile=PASSWORD

== Changing behavior with environment variables

The mode and frequency settings are enabled by setting environment variables. Some of these environment variables must be set before 'perftools' is required. If you only require 'rack/perftools_profiler', it will do the right thing (require 'perftools' after setting the environment variables).
Expand Down
1 change: 1 addition & 0 deletions lib/rack/perftools_profiler.rb
Expand Up @@ -12,6 +12,7 @@
require 'rack/perftools_profiler/profile_once'
require 'rack/perftools_profiler/return_data'
require 'rack/perftools_profiler/call_app_directly'
require 'rack/perftools_profiler/return_password_error'

module Rack::PerftoolsProfiler

Expand Down
4 changes: 2 additions & 2 deletions lib/rack/perftools_profiler/action.rb
Expand Up @@ -17,8 +17,8 @@ def act
def self.for_env(env, profiler, middleware)
request = Rack::Request.new(env)
klass =
if ENV["PROFILE_PASSWORD"] && request.GET['profile'] != ENV["PROFILE_PASSWORD"]
CallAppDirectly
if !profiler.password_valid?(request.GET['profile'])
ReturnPasswordError
else
case request.path_info
when %r{/__start__$}
Expand Down
9 changes: 7 additions & 2 deletions lib/rack/perftools_profiler/profiler.rb
Expand Up @@ -32,8 +32,9 @@ def initialize(app, options)
@printer = (options.delete(:default_printer) { DEFAULT_PRINTER }).to_sym
@frequency = (options.delete(:frequency) { UNSET_FREQUENCY }).to_s
@mode = (options.delete(:mode) { DEFAULT_MODE }).to_sym
@bundler = (options.delete(:bundler) { false })
@gemfile_dir = (options.delete(:gemfile_dir) { DEFAULT_GEMFILE_DIR })
@bundler = options.delete(:bundler) { false }
@gemfile_dir = options.delete(:gemfile_dir) { DEFAULT_GEMFILE_DIR }
@password = options.delete(:password) { nil }
@mode_for_request = nil
ProfileDataAction.check_printer(@printer)
ensure_mode_is_valid(@mode)
Expand All @@ -53,6 +54,10 @@ def profile(mode = nil)
def self.clear_data
::File.delete(PROFILING_DATA_FILE) if ::File.exists?(PROFILING_DATA_FILE)
end

def password_valid?(password)
@password.nil? || password == @password
end

def start(mode = nil)
ensure_mode_is_changeable(mode) if mode
Expand Down
14 changes: 14 additions & 0 deletions lib/rack/perftools_profiler/return_password_error.rb
@@ -0,0 +1,14 @@
module Rack::PerftoolsProfiler

class ReturnPasswordError < Action
include Rack::PerftoolsProfiler::Utils

def response
[401,
{'Content-Type' => 'text/plain'},
["Profiling is password-protected. Password is incorrect.\nProvide a password using the 'profile' GET param:\nhttp://domain.com/foobar?profile=PASSWORD"]]
end

end

end
14 changes: 5 additions & 9 deletions test/single_request_profiling_test.rb
Expand Up @@ -325,23 +325,19 @@ def setup
end

context "when a profile password is required" do
should "not profile unless the parameter matches" do
ENV["PROFILE_PASSWORD"] = 'secret_password'
should "error if password does not match" do
app = @app.clone
env = Rack::MockRequest.env_for('/', :params => {'profile' => 'true'})
status, headers, body = Rack::PerftoolsProfiler.new(app, :default_printer => 'pdf').call(env)
assert_equal 200, status
status, headers, body = Rack::PerftoolsProfiler.new(app, :default_printer => 'pdf', :password => 'secret_password').call(env)
assert_equal 401, status
assert_equal 'text/plain', headers['Content-Type']
assert_equal 'Oh hai der', RackResponseBody.new(body).to_s
ENV.delete 'PROFILE_PASSWORD'
assert_match /Profiling is password-protected\. Password is incorrect\./, RackResponseBody.new(body).to_s
end

should "profile if the parameter matches" do
ENV["PROFILE_PASSWORD"] = 'secret_password'
env = Rack::MockRequest.env_for('/', :params => 'profile=secret_password&printer=gif')
_, headers, _ = Rack::PerftoolsProfiler.new(@app, :default_printer => 'pdf').call(env)
_, headers, _ = Rack::PerftoolsProfiler.new(@app, :default_printer => 'pdf', :password => 'secret_password').call(env)
assert_equal 'image/gif', headers['Content-Type']
ENV.delete 'PROFILE_PASSWORD'
end
end

Expand Down

0 comments on commit e610424

Please sign in to comment.