From 2debd935db099a76cfe6334151fed19f3abb0c94 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 18 Oct 2023 10:57:03 -0400 Subject: [PATCH 01/17] extract skip logic to their own methods --- lib/mini_profiler.rb | 46 ++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index cccbce90..0f1e6fb9 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -165,26 +165,11 @@ def call(env) # Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME'] = ENV['PASSENGER_BASE_URI'] || env['SCRIPT_NAME'] - skip_it = matches_action?('skip', env) || ( - @config.skip_paths && - @config.skip_paths.any? do |p| - if p.instance_of?(String) - path.start_with?(p) - elsif p.instance_of?(Regexp) - p.match?(path) - end - end - ) - if skip_it + if matches_action?('skip', env) || skip_via_parameter?(query_string) || skip_via_path?(path) return client_settings.handle_cookie(@app.call(env)) end - skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) - - if skip_it || ( - @config.authorization_mode == :allow_authorized && - !client_settings.has_valid_cookie? - ) + if skip_via_preauthorize?(env) || unauthorized?(client_settings) if take_snapshot?(path) return client_settings.handle_cookie(take_snapshot(env, start)) else @@ -666,5 +651,32 @@ def take_snapshot(env, start) self.current = nil results end + + def public_base_path(env) + "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}" + end + + def skip_via_parameter?(query_string) + /#{@config.profile_parameter}=skip/.match?(query_string) + end + + def skip_via_path?(path) + @config.skip_paths && + @config.skip_paths.any? do |p| + if p.instance_of?(String) + path.start_with?(p) + elsif p.instance_of?(Regexp) + p.match?(path) + end + end + end + + def skip_via_preauthorize?(env) + @config.pre_authorize_cb && !@config.pre_authorize_cb.call(env) + end + + def unauthorized?(client_settings) + @config.authorization_mode == :allow_authorized && !client_settings.has_valid_cookie? + end end end From 78892374bfd15e33f115e62bb98cfe8818dec128 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 18 Oct 2023 11:01:04 -0400 Subject: [PATCH 02/17] Add manual disable too --- lib/mini_profiler.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index 0f1e6fb9..4cc29ab3 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -194,9 +194,7 @@ def call(env) return client_settings.handle_cookie(serve_file(env, file_name: file_name)) end - has_disable_cookie = client_settings.disable_profiling? - # manual session disable / enable - if matches_action?('disable', env) || has_disable_cookie + if matches_action?('disable', env) || manual_disable?(query_string: query_string, client_settings: client_settings) skip_it = true end @@ -675,6 +673,10 @@ def skip_via_preauthorize?(env) @config.pre_authorize_cb && !@config.pre_authorize_cb.call(env) end + def manual_disable?(query_string:, client_settings:) + query_string.match?(/#{@config.profile_parameter}=disable/) || client_settings.disable_profiling? + end + def unauthorized?(client_settings) @config.authorization_mode == :allow_authorized && !client_settings.has_valid_cookie? end From 6897644e712de9502b628b7cac5bdb4c182a5a3b Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 18 Oct 2023 11:02:31 -0400 Subject: [PATCH 03/17] add manual_enable? --- lib/mini_profiler.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index 4cc29ab3..4047e329 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -198,7 +198,7 @@ def call(env) skip_it = true end - if matches_action?('enable', env) + if matches_action?('enable', env) || manual_enable?(query_string: query_string) skip_it = false config.enabled = true end @@ -677,6 +677,10 @@ def manual_disable?(query_string:, client_settings:) query_string.match?(/#{@config.profile_parameter}=disable/) || client_settings.disable_profiling? end + def manual_enable?(query_string:) + query_string.match?(/#{@config.profile_parameter}=enable/) + end + def unauthorized?(client_settings) @config.authorization_mode == :allow_authorized && !client_settings.has_valid_cookie? end From 64fda08bf12fd758e6d48ffa9b0122298970263d Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 18 Oct 2023 11:12:47 -0400 Subject: [PATCH 04/17] extract QuerySettings class to contain logic to parse query string / path --- lib/mini_profiler.rb | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index 4047e329..712c7bc8 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -20,6 +20,17 @@ class MiniProfiler include Actions include Views + class QuerySettings + def initialize(query_string, profile_parameter) + @query_string = query_string + @profile_parameter = profile_parameter + end + + def skip? + @query_string.match?(/#{@profile_parameter}=skip/) + end + end + class << self include Rack::MiniProfiler::ProfilingMethods attr_accessor :subscribe_sql_active_record @@ -160,12 +171,15 @@ def call(env) MiniProfiler.deauthorize_request if @config.authorization_mode == :allow_authorized status = headers = body = nil + query_string = env['QUERY_STRING'] + query_settings = QuerySettings.new(query_string, @config.profile_parameter) path = env['PATH_INFO'].sub('//', '/') + # Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME'] = ENV['PASSENGER_BASE_URI'] || env['SCRIPT_NAME'] - if matches_action?('skip', env) || skip_via_parameter?(query_string) || skip_via_path?(path) + if matches_action?('skip', env) || query_settings.skip? || skip_via_path?(path) return client_settings.handle_cookie(@app.call(env)) end @@ -654,10 +668,6 @@ def public_base_path(env) "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}" end - def skip_via_parameter?(query_string) - /#{@config.profile_parameter}=skip/.match?(query_string) - end - def skip_via_path?(path) @config.skip_paths && @config.skip_paths.any? do |p| From 6447f353f14baa1e45d8c749033a796ccbd4c491 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 18 Oct 2023 11:20:47 -0400 Subject: [PATCH 05/17] move skip paths into query_settings. move preauthorized logic into config --- lib/mini_profiler.rb | 38 ++++++++++++++++++------------------- lib/mini_profiler/config.rb | 3 +++ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index 712c7bc8..0087a8c7 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -21,14 +21,28 @@ class MiniProfiler include Views class QuerySettings - def initialize(query_string, profile_parameter) + def initialize(query_string, profile_parameter, skip_paths, path) @query_string = query_string + @profile_parameter = profile_parameter + @skip_paths = skip_paths + + @path = path end def skip? @query_string.match?(/#{@profile_parameter}=skip/) end + + def skip_path? + @skip_paths && @skip_paths.any? do |p| + if p.instance_of?(String) + @path.start_with?(p) + elsif p.instance_of?(Regexp) + p.match?(@path) + end + end + end end class << self @@ -172,18 +186,17 @@ def call(env) status = headers = body = nil query_string = env['QUERY_STRING'] - query_settings = QuerySettings.new(query_string, @config.profile_parameter) path = env['PATH_INFO'].sub('//', '/') - + query_settings = QuerySettings.new(query_string, @config.profile_parameter, @config.skip_paths, path) # Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME'] = ENV['PASSENGER_BASE_URI'] || env['SCRIPT_NAME'] - if matches_action?('skip', env) || query_settings.skip? || skip_via_path?(path) + if matches_action?('skip', env) || query_settings.skip? || query_settings.skip_path? return client_settings.handle_cookie(@app.call(env)) end - if skip_via_preauthorize?(env) || unauthorized?(client_settings) + if @config.pre_authorized?(env) || unauthorized?(client_settings) if take_snapshot?(path) return client_settings.handle_cookie(take_snapshot(env, start)) else @@ -668,21 +681,6 @@ def public_base_path(env) "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}" end - def skip_via_path?(path) - @config.skip_paths && - @config.skip_paths.any? do |p| - if p.instance_of?(String) - path.start_with?(p) - elsif p.instance_of?(Regexp) - p.match?(path) - end - end - end - - def skip_via_preauthorize?(env) - @config.pre_authorize_cb && !@config.pre_authorize_cb.call(env) - end - def manual_disable?(query_string:, client_settings:) query_string.match?(/#{@config.profile_parameter}=disable/) || client_settings.disable_profiling? end diff --git a/lib/mini_profiler/config.rb b/lib/mini_profiler/config.rb index 737399a0..7eb37138 100644 --- a/lib/mini_profiler/config.rb +++ b/lib/mini_profiler/config.rb @@ -141,6 +141,9 @@ def merge!(config) end end + def pre_authorized?(env) + @pre_authorize_cb && !@pre_authorize_cb.call(env) + end end end end From b293550222e51fd6633fca6c3a77617a80ecdc3f Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 18 Oct 2023 11:23:58 -0400 Subject: [PATCH 06/17] move manual enable/disable --- lib/mini_profiler.rb | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index 0087a8c7..ed988b8c 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -43,6 +43,14 @@ def skip_path? end end end + + def manual_enable? + query_string.match?(/#{@config.profile_parameter}=enable/) + end + + def manual_disable? + query_string.match?(/#{@config.profile_parameter}=disable/) + end end class << self @@ -208,24 +216,11 @@ def call(env) if path.start_with? @config.base_url_path file_name = path.sub(@config.base_url_path, '') - case file_name - when 'results' - return serve_results(env) - when 'snapshots' - self.current = nil - return serve_snapshot(env) - when 'flamegraph' - return serve_flamegraph(env) - end - - return client_settings.handle_cookie(serve_file(env, file_name: file_name)) - end - - if matches_action?('disable', env) || manual_disable?(query_string: query_string, client_settings: client_settings) + if query_settings.manual_disable? || client_settings.disable_profiling? skip_it = true end - if matches_action?('enable', env) || manual_enable?(query_string: query_string) + if matches_action?('enable', env) || query_settings.manual_enable? skip_it = false config.enabled = true end @@ -681,14 +676,6 @@ def public_base_path(env) "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}" end - def manual_disable?(query_string:, client_settings:) - query_string.match?(/#{@config.profile_parameter}=disable/) || client_settings.disable_profiling? - end - - def manual_enable?(query_string:) - query_string.match?(/#{@config.profile_parameter}=enable/) - end - def unauthorized?(client_settings) @config.authorization_mode == :allow_authorized && !client_settings.has_valid_cookie? end From 0128c81f224a07dc521d38e1eb03d18288c906ce Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 18 Oct 2023 11:29:56 -0400 Subject: [PATCH 07/17] move more methods to query_settings --- lib/mini_profiler.rb | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index ed988b8c..785a2224 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -45,11 +45,27 @@ def skip_path? end def manual_enable? - query_string.match?(/#{@config.profile_parameter}=enable/) + @query_string.match?(/#{@profile_parameter}=enable/) end def manual_disable? - query_string.match?(/#{@config.profile_parameter}=disable/) + @query_string.match?(/#{@profile_parameter}=disable/) + end + + def normal_backtrace? + @query_string.match?(/#{@profile_parameter}=normal-backtrace/) + end + + def no_backtrace? + @query_string.match?(/#{@profile_parameter}=no-backtrace/) + end + + def full_backtrace? + @query_string.match?(/#{@profile_parameter}=full-backtrace/) + end + + def trace_exceptions? + @query_string.match?(/#{@profile_parameter}=trace-exceptions/) end end @@ -249,12 +265,13 @@ def call(env) MiniProfiler.create_current(env, @config) - if matches_action?('normal-backtrace', env) + if query_settings.normal_backtrace? client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT elsif matches_action?('no-backtrace', env) + elsif query_settings.no_backtrace? current.skip_backtrace = true client_settings.backtrace_level = ClientSettings::BACKTRACE_NONE - elsif matches_action?('full-backtrace', env) || client_settings.backtrace_full? + elsif query_settings.full_backtrace? || client_settings.backtrace_full? current.full_backtrace = true client_settings.backtrace_level = ClientSettings::BACKTRACE_FULL elsif client_settings.backtrace_none? @@ -263,7 +280,7 @@ def call(env) flamegraph = nil - trace_exceptions = matches_action?('trace-exceptions', env) && defined? TracePoint + trace_exceptions = query_settings.trace_exceptions? && defined? TracePoint status, headers, body, exceptions, trace = nil if trace_exceptions From 430788e9436da98b97e8d11d95d469f0e7d6e974 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 18 Oct 2023 12:26:53 -0400 Subject: [PATCH 08/17] push flamegraph settings to querysetting --- lib/mini_profiler.rb | 45 ++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index 785a2224..5744f62f 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -23,6 +23,7 @@ class MiniProfiler class QuerySettings def initialize(query_string, profile_parameter, skip_paths, path) @query_string = query_string + @query_params = Rack::Utils.parse_nested_query(query_string) @profile_parameter = profile_parameter @skip_paths = skip_paths @@ -67,6 +68,30 @@ def full_backtrace? def trace_exceptions? @query_string.match?(/#{@profile_parameter}=trace-exceptions/) end + + def flamegraph? + @query_string.match?(/pp=(async-)?flamegraph/ ) + end + + def flamegraph_sample_rate + match_data = @query_string.match(/flamegraph_sample_rate=([\d\.]+)/) + if match_data && !match_data[1].to_f.zero? + match_data[1].to_f + end + end + + VALID_MODES = [:cpu, :wall, :object, :custom].freeze + def flamegraph_mode + mode_match_data = @query_string.match(/flamegraph_mode=([a-zA-Z]+)/) + + if mode_match_data && VALID_MODES.include?(mode_match_data[1].to_sym) + mode_match_data[1].to_sym + end + end + + def trace_exceptions_filter + @query_params['trace_exceptions_filter'] + end end class << self @@ -304,25 +329,14 @@ def call(env) # Prevent response body from being compressed env['HTTP_ACCEPT_ENCODING'] = 'identity' if config.suppress_encoding - if matches_action?('flamegraph', env) || matches_action?('async-flamegraph', env) || env['HTTP_REFERER'] =~ /pp=async-flamegraph/ + if query_settings.flamegraph? || env['HTTP_REFERER'] =~ /pp=async-flamegraph/ if defined?(StackProf) && StackProf.respond_to?(:run) # do not sully our profile with mini profiler timings current.measure = false match_data = action_parameters(env)['flamegraph_sample_rate'] - if match_data && !match_data[1].to_f.zero? - sample_rate = match_data[1].to_f - else - sample_rate = config.flamegraph_sample_rate - end - - mode_match_data = action_parameters(env)['flamegraph_mode'] - - if mode_match_data && [:cpu, :wall, :object, :custom].include?(mode_match_data[1].to_sym) - mode = mode_match_data[1].to_sym - else - mode = config.flamegraph_mode - end + sample_rate = query_settings.flamegraph_sample_rate || config.flamegraph_sample_rate + mode = query_settings.flamegraph_mode || config.flamegraph_mode ignore_gc_match_data = action_parameters(env)['flamegraph_ignore_gc'] @@ -372,8 +386,7 @@ def call(env) if trace_exceptions body.close if body.respond_to? :close - query_params = action_parameters(env) - trace_exceptions_filter = query_params['trace_exceptions_filter'] + trace_exceptions_filter = query_settings.trace_exceptions_filter if trace_exceptions_filter trace_exceptions_regex = Regexp.new(trace_exceptions_filter) exceptions.reject! { |ex| ex.class.name =~ trace_exceptions_regex } From b1b13be94ce344057d53ddd0a63d33e5f0251cca Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 18 Oct 2023 12:32:26 -0400 Subject: [PATCH 09/17] use query_params since we have already parsed it once instead of regexing tons of times --- lib/mini_profiler.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index 5744f62f..b8e11176 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -45,28 +45,32 @@ def skip_path? end end + def profile_value + @query_params[@profile_parameter] + end + def manual_enable? - @query_string.match?(/#{@profile_parameter}=enable/) + profile_value == 'enable' end def manual_disable? - @query_string.match?(/#{@profile_parameter}=disable/) + profile_value == 'disable' end def normal_backtrace? - @query_string.match?(/#{@profile_parameter}=normal-backtrace/) + profile_value == 'normal-backtrace' end def no_backtrace? - @query_string.match?(/#{@profile_parameter}=no-backtrace/) + profile_value == 'no-backtrace' end def full_backtrace? - @query_string.match?(/#{@profile_parameter}=full-backtrace/) + profile_value == 'full-backtrace' end def trace_exceptions? - @query_string.match?(/#{@profile_parameter}=trace-exceptions/) + profile_value == 'trace-exceptions' end def flamegraph? From aedd2af2dfd678fd744aad942b9e1c0ac8ba2b04 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 18 Oct 2023 12:37:53 -0400 Subject: [PATCH 10/17] more query settings --- lib/mini_profiler.rb | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index b8e11176..3f170e09 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -73,7 +73,8 @@ def trace_exceptions? profile_value == 'trace-exceptions' end - def flamegraph? + # FIXME this should use profile_parameter and be the same as flamegraph? + def pp_flamegraph? @query_string.match?(/pp=(async-)?flamegraph/ ) end @@ -96,6 +97,22 @@ def flamegraph_mode def trace_exceptions_filter @query_params['trace_exceptions_filter'] end + + def env? + profile_value == 'env' + end + + def analyze_memory? + profile_value == 'analyze-memory' + end + + def help? + profile_value == 'help' + end + + def flamegraph? + profile_value == 'flamegraph' + end end class << self @@ -333,7 +350,7 @@ def call(env) # Prevent response body from being compressed env['HTTP_ACCEPT_ENCODING'] = 'identity' if config.suppress_encoding - if query_settings.flamegraph? || env['HTTP_REFERER'] =~ /pp=async-flamegraph/ + if query_settings.pp_flamegraph? || env['HTTP_REFERER'] =~ /pp=async-flamegraph/ if defined?(StackProf) && StackProf.respond_to?(:run) # do not sully our profile with mini profiler timings current.measure = false @@ -399,19 +416,19 @@ def call(env) return client_settings.handle_cookie(dump_exceptions exceptions) end - if matches_action?("env", env) + if query_settings.env? return tool_disabled_message(client_settings) if !advanced_debugging_enabled? body.close if body.respond_to? :close return client_settings.handle_cookie(dump_env env) end - if matches_action?("analyze-memory", env) + if query_settings.analyze_memory? return tool_disabled_message(client_settings) if !advanced_debugging_enabled? body.close if body.respond_to? :close return client_settings.handle_cookie(analyze_memory) end - if matches_action?("help", env) + if query_settings.help? body.close if body.respond_to? :close return client_settings.handle_cookie(help(client_settings, env)) end @@ -420,7 +437,7 @@ def call(env) page_struct[:user] = user(env) page_struct[:root].record_time((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000) - if flamegraph && matches_action?("flamegraph", env) + if flamegraph && query_settings.flamegraph? body.close if body.respond_to? :close return client_settings.handle_cookie(self.flamegraph(flamegraph, path, env)) elsif flamegraph # async-flamegraph From c40b353523f71d1fa14d72fd9211d7e8f9c6ffa4 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 18 Oct 2023 12:41:33 -0400 Subject: [PATCH 11/17] move rest of things to query_settings --- lib/mini_profiler.rb | 45 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index 3f170e09..e8afe451 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -113,6 +113,25 @@ def help? def flamegraph? profile_value == 'flamegraph' end + + def profile_gc? + profile_value == 'profile-gc' + end + + def profile_memory? + profile_value == 'profile-memory' + end + + def memory_profiler_options + options = { + ignore_files: @query_params['memory_profiler_ignore_files'], + allow_files: @query_params['memory_profiler_allow_files'], + } + + options[:top] = Integer(@query_params['memory_profiler_top']) if @query_params.key?('memory_profiler_top') + + options + end end class << self @@ -297,14 +316,33 @@ def call(env) client_settings.disable_profiling = false # profile gc - if matches_action?('profile-gc', env) + if query_settings.profile_gc? + return tool_disabled_message(client_settings) if !advanced_debugging_enabled? current.measure = false if current return serve_profile_gc(env, client_settings) end # profile memory - if matches_action?('profile-memory', env) - return serve_profile_memory(env, client_settings) + if query_settings.profile_memory? + return tool_disabled_message(client_settings) if !advanced_debugging_enabled? + + unless defined?(MemoryProfiler) && MemoryProfiler.respond_to?(:report) + message = "Please install the memory_profiler gem and require it: add gem 'memory_profiler' to your Gemfile" + status, headers, body = @app.call(env) + body.close if body.respond_to? :close + + return client_settings.handle_cookie( + text_result(message, status: 500, headers: headers) + ) + end + + result = StringIO.new + report = MemoryProfiler.report(query_settings.memory_profiler_options) do + _, _, body = @app.call(env) + body.close if body.respond_to? :close + end + report.pretty_print(result) + return client_settings.handle_cookie(text_result(result.string)) end # any other requests past this point are going to the app to be profiled @@ -354,7 +392,6 @@ def call(env) if defined?(StackProf) && StackProf.respond_to?(:run) # do not sully our profile with mini profiler timings current.measure = false - match_data = action_parameters(env)['flamegraph_sample_rate'] sample_rate = query_settings.flamegraph_sample_rate || config.flamegraph_sample_rate mode = query_settings.flamegraph_mode || config.flamegraph_mode From 855d8543c073641fba3f5b1268bd55376daf91fa Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 18 Oct 2023 12:45:33 -0400 Subject: [PATCH 12/17] move allow_authorized logic into config, and remove unauthorized on MiniProfiler --- lib/mini_profiler.rb | 6 +----- lib/mini_profiler/config.rb | 4 ++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index e8afe451..6a088281 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -285,7 +285,7 @@ def call(env) return client_settings.handle_cookie(@app.call(env)) end - if @config.pre_authorized?(env) || unauthorized?(client_settings) + if @config.pre_authorized?(env) || (config.allow_authorized? && !client_settings.has_valid_cookie?) if take_snapshot?(path) return client_settings.handle_cookie(take_snapshot(env, start)) else @@ -763,9 +763,5 @@ def take_snapshot(env, start) def public_base_path(env) "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}" end - - def unauthorized?(client_settings) - @config.authorization_mode == :allow_authorized && !client_settings.has_valid_cookie? - end end end diff --git a/lib/mini_profiler/config.rb b/lib/mini_profiler/config.rb index 7eb37138..e7f207b4 100644 --- a/lib/mini_profiler/config.rb +++ b/lib/mini_profiler/config.rb @@ -144,6 +144,10 @@ def merge!(config) def pre_authorized?(env) @pre_authorize_cb && !@pre_authorize_cb.call(env) end + + def allow_authorized? + @authorization_mode == :allow_authorized + end end end end From 021b8c3165af44dbcb64f5f7b21b5168aaacccaf Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Fri, 3 Nov 2023 08:58:31 -0400 Subject: [PATCH 13/17] run rubocop --- lib/mini_profiler.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index 6a088281..b2eaacb9 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -75,7 +75,7 @@ def trace_exceptions? # FIXME this should use profile_parameter and be the same as flamegraph? def pp_flamegraph? - @query_string.match?(/pp=(async-)?flamegraph/ ) + @query_string.match?(/pp=(async-)?flamegraph/) end def flamegraph_sample_rate @@ -392,8 +392,7 @@ def call(env) if defined?(StackProf) && StackProf.respond_to?(:run) # do not sully our profile with mini profiler timings current.measure = false - - sample_rate = query_settings.flamegraph_sample_rate || config.flamegraph_sample_rate + sample_rate = query_settings.flamegraph_sample_rate || config.flamegraph_sample_rate mode = query_settings.flamegraph_mode || config.flamegraph_mode ignore_gc_match_data = action_parameters(env)['flamegraph_ignore_gc'] @@ -474,7 +473,7 @@ def call(env) page_struct[:user] = user(env) page_struct[:root].record_time((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000) - if flamegraph && query_settings.flamegraph? + if flamegraph && query_settings.flamegraph? body.close if body.respond_to? :close return client_settings.handle_cookie(self.flamegraph(flamegraph, path, env)) elsif flamegraph # async-flamegraph From f114b35fb7bf66d87e3733cd84bcaa2b4d14d15c Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Thu, 15 Feb 2024 11:06:22 -0500 Subject: [PATCH 14/17] correcting rebase --- lib/mini_profiler.rb | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index b2eaacb9..37b4e598 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -274,14 +274,13 @@ def call(env) MiniProfiler.deauthorize_request if @config.authorization_mode == :allow_authorized status = headers = body = nil - query_string = env['QUERY_STRING'] path = env['PATH_INFO'].sub('//', '/') - query_settings = QuerySettings.new(query_string, @config.profile_parameter, @config.skip_paths, path) + query_settings = QuerySettings.new(env['QUERY_STRING'], @config.profile_parameter, @config.skip_paths, path) # Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME'] = ENV['PASSENGER_BASE_URI'] || env['SCRIPT_NAME'] - if matches_action?('skip', env) || query_settings.skip? || query_settings.skip_path? + if query_settings.skip? || query_settings.skip_path? return client_settings.handle_cookie(@app.call(env)) end @@ -301,7 +300,7 @@ def call(env) skip_it = true end - if matches_action?('enable', env) || query_settings.manual_enable? + if query_settings.manual_enable? skip_it = false config.enabled = true end @@ -318,35 +317,16 @@ def call(env) # profile gc if query_settings.profile_gc? return tool_disabled_message(client_settings) if !advanced_debugging_enabled? + current.measure = false if current return serve_profile_gc(env, client_settings) end - # profile memory if query_settings.profile_memory? - return tool_disabled_message(client_settings) if !advanced_debugging_enabled? - - unless defined?(MemoryProfiler) && MemoryProfiler.respond_to?(:report) - message = "Please install the memory_profiler gem and require it: add gem 'memory_profiler' to your Gemfile" - status, headers, body = @app.call(env) - body.close if body.respond_to? :close - - return client_settings.handle_cookie( - text_result(message, status: 500, headers: headers) - ) - end - - result = StringIO.new - report = MemoryProfiler.report(query_settings.memory_profiler_options) do - _, _, body = @app.call(env) - body.close if body.respond_to? :close - end - report.pretty_print(result) - return client_settings.handle_cookie(text_result(result.string)) + return serve_profile_memory(env, client_settings) end # any other requests past this point are going to the app to be profiled - MiniProfiler.create_current(env, @config) if query_settings.normal_backtrace? @@ -376,7 +356,6 @@ def call(env) end begin - # Strip all the caching headers so we don't get 304s back # This solves a very annoying bug where rack mini profiler never shows up if config.disable_caching @@ -758,9 +737,5 @@ def take_snapshot(env, start) self.current = nil results end - - def public_base_path(env) - "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}" - end end end From b3e0b5192432dcaea01bea3ba82c435d638e4794 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Thu, 15 Feb 2024 11:07:38 -0500 Subject: [PATCH 15/17] fix syntax --- lib/mini_profiler.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index 37b4e598..9f6c443b 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -295,6 +295,7 @@ def call(env) # handle all /mini-profiler requests here if path.start_with? @config.base_url_path file_name = path.sub(@config.base_url_path, '') + end if query_settings.manual_disable? || client_settings.disable_profiling? skip_it = true From 4ac9e5acc543e6c9681ab3286f306d18fecf0971 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Thu, 15 Feb 2024 11:10:57 -0500 Subject: [PATCH 16/17] move query_settings to its own file. rubocop --- lib/mini_profiler.rb | 119 +--------------------------- lib/mini_profiler/query_settings.rb | 117 +++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 117 deletions(-) create mode 100644 lib/mini_profiler/query_settings.rb diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index 9f6c443b..79d2dca9 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -14,126 +14,13 @@ require 'mini_profiler/snapshots_transporter' require 'mini_profiler/views' require 'mini_profiler/actions' +require 'mini_profiler/query_settings' module Rack class MiniProfiler include Actions include Views - class QuerySettings - def initialize(query_string, profile_parameter, skip_paths, path) - @query_string = query_string - @query_params = Rack::Utils.parse_nested_query(query_string) - - @profile_parameter = profile_parameter - @skip_paths = skip_paths - - @path = path - end - - def skip? - @query_string.match?(/#{@profile_parameter}=skip/) - end - - def skip_path? - @skip_paths && @skip_paths.any? do |p| - if p.instance_of?(String) - @path.start_with?(p) - elsif p.instance_of?(Regexp) - p.match?(@path) - end - end - end - - def profile_value - @query_params[@profile_parameter] - end - - def manual_enable? - profile_value == 'enable' - end - - def manual_disable? - profile_value == 'disable' - end - - def normal_backtrace? - profile_value == 'normal-backtrace' - end - - def no_backtrace? - profile_value == 'no-backtrace' - end - - def full_backtrace? - profile_value == 'full-backtrace' - end - - def trace_exceptions? - profile_value == 'trace-exceptions' - end - - # FIXME this should use profile_parameter and be the same as flamegraph? - def pp_flamegraph? - @query_string.match?(/pp=(async-)?flamegraph/) - end - - def flamegraph_sample_rate - match_data = @query_string.match(/flamegraph_sample_rate=([\d\.]+)/) - if match_data && !match_data[1].to_f.zero? - match_data[1].to_f - end - end - - VALID_MODES = [:cpu, :wall, :object, :custom].freeze - def flamegraph_mode - mode_match_data = @query_string.match(/flamegraph_mode=([a-zA-Z]+)/) - - if mode_match_data && VALID_MODES.include?(mode_match_data[1].to_sym) - mode_match_data[1].to_sym - end - end - - def trace_exceptions_filter - @query_params['trace_exceptions_filter'] - end - - def env? - profile_value == 'env' - end - - def analyze_memory? - profile_value == 'analyze-memory' - end - - def help? - profile_value == 'help' - end - - def flamegraph? - profile_value == 'flamegraph' - end - - def profile_gc? - profile_value == 'profile-gc' - end - - def profile_memory? - profile_value == 'profile-memory' - end - - def memory_profiler_options - options = { - ignore_files: @query_params['memory_profiler_ignore_files'], - allow_files: @query_params['memory_profiler_allow_files'], - } - - options[:top] = Integer(@query_params['memory_profiler_top']) if @query_params.key?('memory_profiler_top') - - options - end - end - class << self include Rack::MiniProfiler::ProfilingMethods attr_accessor :subscribe_sql_active_record @@ -274,7 +161,7 @@ def call(env) MiniProfiler.deauthorize_request if @config.authorization_mode == :allow_authorized status = headers = body = nil - path = env['PATH_INFO'].sub('//', '/') + path = env['PATH_INFO'].sub('//', '/') query_settings = QuerySettings.new(env['QUERY_STRING'], @config.profile_parameter, @config.skip_paths, path) # Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it @@ -317,8 +204,6 @@ def call(env) # profile gc if query_settings.profile_gc? - return tool_disabled_message(client_settings) if !advanced_debugging_enabled? - current.measure = false if current return serve_profile_gc(env, client_settings) end diff --git a/lib/mini_profiler/query_settings.rb b/lib/mini_profiler/query_settings.rb new file mode 100644 index 00000000..d46065ec --- /dev/null +++ b/lib/mini_profiler/query_settings.rb @@ -0,0 +1,117 @@ +module Rack + class MiniProfiler + class QuerySettings + def initialize(query_string, profile_parameter, skip_paths, path) + @query_string = query_string + @query_params = Rack::Utils.parse_nested_query(query_string) + + @profile_parameter = profile_parameter + @skip_paths = skip_paths + + @path = path + end + + def skip? + @query_string.match?(/#{@profile_parameter}=skip/) + end + + def skip_path? + @skip_paths && @skip_paths.any? do |p| + if p.instance_of?(String) + @path.start_with?(p) + elsif p.instance_of?(Regexp) + p.match?(@path) + end + end + end + + def profile_value + @query_params[@profile_parameter] + end + + def manual_enable? + profile_value == 'enable' + end + + def manual_disable? + profile_value == 'disable' + end + + def normal_backtrace? + profile_value == 'normal-backtrace' + end + + def no_backtrace? + profile_value == 'no-backtrace' + end + + def full_backtrace? + profile_value == 'full-backtrace' + end + + def trace_exceptions? + profile_value == 'trace-exceptions' + end + + # FIXME this should use profile_parameter and be the same as flamegraph? + def pp_flamegraph? + @query_string.match?(/pp=(async-)?flamegraph/) + end + + def flamegraph_sample_rate + match_data = @query_string.match(/flamegraph_sample_rate=([\d\.]+)/) + if match_data && !match_data[1].to_f.zero? + match_data[1].to_f + end + end + + VALID_MODES = [:cpu, :wall, :object, :custom].freeze + def flamegraph_mode + mode_match_data = @query_string.match(/flamegraph_mode=([a-zA-Z]+)/) + + if mode_match_data && VALID_MODES.include?(mode_match_data[1].to_sym) + mode_match_data[1].to_sym + end + end + + def trace_exceptions_filter + @query_params['trace_exceptions_filter'] + end + + def env? + profile_value == 'env' + end + + def analyze_memory? + profile_value == 'analyze-memory' + end + + def help? + profile_value == 'help' + end + + def flamegraph? + profile_value == 'flamegraph' + end + + def profile_gc? + profile_value == 'profile-gc' + end + + def profile_memory? + profile_value == 'profile-memory' + end + + def memory_profiler_options + options = { + ignore_files: @query_params['memory_profiler_ignore_files'], + allow_files: @query_params['memory_profiler_allow_files'], + } + + options[:top] = Integer(@query_params['memory_profiler_top']) if @query_params.key?('memory_profiler_top') + + options + end + end + end +end From ad0e68444ad2a91dba36414f73b70512089e2818 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Thu, 15 Feb 2024 11:22:48 -0500 Subject: [PATCH 17/17] add back logic for handling results/snapshot/flamegraph. not sure why i removed it in b293550222e51fd6633fca6c3a77617a80ecdc3f --- lib/mini_profiler.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/mini_profiler.rb b/lib/mini_profiler.rb index 79d2dca9..2bce7d4b 100644 --- a/lib/mini_profiler.rb +++ b/lib/mini_profiler.rb @@ -182,6 +182,18 @@ def call(env) # handle all /mini-profiler requests here if path.start_with? @config.base_url_path file_name = path.sub(@config.base_url_path, '') + + case file_name + when 'results' + return serve_results(env) + when 'snapshots' + self.current = nil + return serve_snapshot(env) + when 'flamegraph' + return serve_flamegraph(env) + else + return client_settings.handle_cookie(serve_file(env, file_name: file_name)) + end end if query_settings.manual_disable? || client_settings.disable_profiling? @@ -202,7 +214,6 @@ def call(env) # remember that profiling is not disabled (ie enabled) client_settings.disable_profiling = false - # profile gc if query_settings.profile_gc? current.measure = false if current return serve_profile_gc(env, client_settings)