Permalink
Browse files

Added password protection for single-request profiling

  • Loading branch information...
bhb committed Jul 25, 2011
1 parent 8334714 commit e61042432f54987473a971348f886e3d708e7306
View
@@ -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
@@ -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).
@@ -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
@@ -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__$}
@@ -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)
@@ -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
@@ -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
@@ -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

0 comments on commit e610424

Please sign in to comment.