class Rack::MiniProfiler
Constants
- ASSET_VERSION
- VERSION
Public Class Methods
config()
click to toggle source
So we can change the configuration if we want
# File lib/mini_profiler/profiler.rb, line 16 def config @config ||= Config.default end
create_current(env={}, options={})
click to toggle source
# File lib/mini_profiler/profiler.rb, line 42 def create_current(env={}, options={}) # profiling the request context = Context.new context.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest') context.page_struct = TimerStruct::Page.new(env) context.current_timer = context.page_struct[:root] self.current = context end
current()
click to toggle source
# File lib/mini_profiler/profiler.rb, line 28 def current Thread.current[:mini_profiler_private] end
current=(c)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 32 def current=(c) # we use TLS cause we need access to this from sql blocks and code blocks that have no access to env Thread.current[:mini_profiler_private] = c end
discard_results()
click to toggle source
discard existing results, don't track this request
# File lib/mini_profiler/profiler.rb, line 38 def discard_results self.current.discard = true if current end
generate_id()
click to toggle source
# File lib/mini_profiler/profiler.rb, line 7 def generate_id rand(36**20).to_s(36) end
new(app, config = nil)
click to toggle source
options: :auto_inject - should script be automatically injected on every html page (not xhr)
# File lib/mini_profiler/profiler.rb, line 68 def initialize(app, config = nil) MiniProfiler.config.merge!(config) @config = MiniProfiler.config @app = app @config.base_url_path << "/" unless @config.base_url_path.end_with? "/" unless @config.storage_instance @config.storage_instance = @config.storage.new(@config.storage_options) end @storage = @config.storage_instance end
reset_config()
click to toggle source
# File lib/mini_profiler/profiler.rb, line 11 def reset_config @config = Config.default end
resources_root()
click to toggle source
# File lib/mini_profiler/profiler.rb, line 20 def resources_root @resources_root ||= ::File.expand_path("../../html", __FILE__) end
Public Instance Methods
analyze_memory()
click to toggle source
# File lib/mini_profiler/profiler.rb, line 454 def analyze_memory require 'objspace' utf8 = "utf-8" GC.start trunc = lambda do |str| str = str.length > 200 ? str : str[0..200] if str.encoding != Encoding::UTF_8 str = str.dup str.force_encoding(utf8) unless str.valid_encoding? # work around bust string with a double conversion str.encode!("utf-16","utf-8",:invalid => :replace) str.encode!("utf-8","utf-16") end end str end body = "ObjectSpace stats:\n\n" counts = ObjectSpace.count_objects total_strings = counts[:T_STRING] body << counts .sort{|a,b| b[1] <=> a[1]} .map{|k,v| "#{k}: #{v}"} .join("\n") strings = [] string_counts = Hash.new(0) sample_strings = [] max_size = 1000 sample_every = total_strings / max_size i = 0 ObjectSpace.each_object(String) do |str| i += 1 string_counts[str] += 1 strings << [trunc.call(str), str.length] sample_strings << [trunc.call(str), str.length] if i % sample_every == 0 if strings.length > max_size * 2 trim_strings(strings, max_size) end end trim_strings(strings, max_size) body << "\n\n\n1000 Largest strings:\n\n" body << strings.map{|s,len| "#{s[0..1000]}\n(len: #{len})\n\n"}.join("\n") body << "\n\n\n1000 Sample strings:\n\n" body << sample_strings.map{|s,len| "#{s[0..1000]}\n(len: #{len})\n\n"}.join("\n") body << "\n\n\n1000 Most common strings:\n\n" body << string_counts.sort{|a,b| b[1] <=> a[1]}[0..max_size].map{|s,len| "#{trunc.call(s)}\n(x #{len})\n\n"}.join("\n") text_result(body) end
call(env)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 150 def call(env) start = Time.now client_settings = ClientSettings.new(env, @storage, start) MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist status = headers = body = nil query_string = env['QUERY_STRING'] 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'] skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) || (@config.skip_paths && @config.skip_paths.any?{ |p| path.start_with?(p) }) || query_string =~ /pp=skip/ if skip_it || ( @config.authorization_mode == :whitelist && !client_settings.has_valid_cookie? ) return client_settings.handle_cookie(@app.call(env)) end # handle all /mini-profiler requests here return client_settings.handle_cookie(serve_html(env)) if path.start_with? @config.base_url_path has_disable_cookie = client_settings.disable_profiling? # manual session disable / enable if query_string =~ /pp=disable/ || has_disable_cookie skip_it = true end if query_string =~ /pp=enable/ skip_it = false config.enabled = true end if skip_it || !config.enabled status,headers,body = @app.call(env) client_settings.disable_profiling = true return client_settings.handle_cookie([status,headers,body]) else client_settings.disable_profiling = false end # profile gc if query_string =~ /pp=profile-gc/ current.measure = false if current return client_settings.handle_cookie(Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)) end # profile memory if query_string =~ /pp=profile-memory/ query_params = Rack::Utils.parse_nested_query(query_string) 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') result = StringIO.new report = MemoryProfiler.report(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 MiniProfiler.create_current(env, @config) if query_string =~ /pp=normal-backtrace/ client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT elsif query_string =~ /pp=no-backtrace/ current.skip_backtrace = true client_settings.backtrace_level = ClientSettings::BACKTRACE_NONE elsif query_string =~ /pp=full-backtrace/ || client_settings.backtrace_full? current.full_backtrace = true client_settings.backtrace_level = ClientSettings::BACKTRACE_FULL elsif client_settings.backtrace_none? current.skip_backtrace = true end flamegraph = nil trace_exceptions = query_string =~ /pp=trace-exceptions/ && defined? TracePoint status, headers, body, exceptions,trace = nil if trace_exceptions exceptions = [] trace = TracePoint.new(:raise) do |tp| exceptions << tp.raised_exception end trace.enable 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 env['HTTP_IF_MODIFIED_SINCE'] = '' env['HTTP_IF_NONE_MATCH'] = '' end orig_accept_encoding = env['HTTP_ACCEPT_ENCODING'] # Prevent response body from being compressed env['HTTP_ACCEPT_ENCODING'] = 'identity' if config.suppress_encoding if query_string =~ /pp=flamegraph/ unless defined?(Flamegraph) && Flamegraph.respond_to?(:generate) flamegraph = "Please install the flamegraph gem and require it: add gem 'flamegraph' to your Gemfile" status,headers,body = @app.call(env) else # do not sully our profile with mini profiler timings current.measure = false match_data = query_string.match(/flamegraph_sample_rate=([\d\.]+)/) mode = query_string =~ /mode=c/ ? :c : :ruby if match_data && !match_data[1].to_f.zero? sample_rate = match_data[1].to_f else sample_rate = config.flamegraph_sample_rate end flamegraph = Flamegraph.generate(nil, :fidelity => sample_rate, :embed_resources => query_string =~ /embed/, :mode => mode) do status,headers,body = @app.call(env) end end else status,headers,body = @app.call(env) end ensure trace.disable if trace env['HTTP_ACCEPT_ENCODING'] = orig_accept_encoding if config.suppress_encoding end skip_it = current.discard if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?) skip_it = true end return client_settings.handle_cookie([status,headers,body]) if skip_it # we must do this here, otherwise current[:discard] is not being properly treated if trace_exceptions body.close if body.respond_to? :close query_params = Rack::Utils.parse_nested_query(query_string) trace_exceptions_filter = query_params['trace_exceptions_filter'] if trace_exceptions_filter trace_exceptions_regex = Regexp.new(trace_exceptions_filter) exceptions.reject! { |ex| ex.class.name =~ trace_exceptions_regex } end return client_settings.handle_cookie(dump_exceptions exceptions) end if query_string =~ /pp=env/ && !config.disable_env_dump body.close if body.respond_to? :close return client_settings.handle_cookie(dump_env env) end if query_string =~ /pp=analyze-memory/ body.close if body.respond_to? :close return client_settings.handle_cookie(analyze_memory) end if query_string =~ /pp=help/ body.close if body.respond_to? :close return client_settings.handle_cookie(help(client_settings, env)) end page_struct = current.page_struct page_struct[:user] = user(env) page_struct[:root].record_time((Time.now - start) * 1000) if flamegraph body.close if body.respond_to? :close return client_settings.handle_cookie(self.flamegraph(flamegraph)) end begin @storage.save(page_struct) # no matter what it is, it should be unviewed, otherwise we will miss POST @storage.set_unviewed(page_struct[:user], page_struct[:id]) # inject headers, script if status >= 200 && status < 300 result = inject_profiler(env,status,headers,body) return client_settings.handle_cookie(result) if result end rescue Exception => e if @config.storage_failure != nil @config.storage_failure.call(e) end end client_settings.handle_cookie([status, headers, body]) ensure # Make sure this always happens self.current = nil end
cancel_auto_inject(env)
click to toggle source
cancels automatic injection of profile script for the current page
# File lib/mini_profiler/profiler.rb, line 627 def cancel_auto_inject(env) current.inject_js = false end
config()
click to toggle source
# File lib/mini_profiler/profiler.rb, line 145 def config @config end
current()
click to toggle source
# File lib/mini_profiler/profiler.rb, line 136 def current MiniProfiler.current end
current=(c)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 140 def current=(c) MiniProfiler.current = c end
dump_env(env)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 426 def dump_env(env) body = "Rack Environment\n---------------\n" env.each do |k,v| body << "#{k}: #{v}\n" end body << "\n\nEnvironment\n---------------\n" ENV.each do |k,v| body << "#{k}: #{v}\n" end body << "\n\nRuby Version\n---------------\n" body << "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL}\n" body << "\n\nInternals\n---------------\n" body << "Storage Provider #{config.storage_instance}\n" body << "User #{user(env)}\n" body << config.storage_instance.diagnostics(user(env)) rescue "no diagnostics implemented for storage" text_result(body) end
dump_exceptions(exceptions)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 408 def dump_exceptions(exceptions) body = "Exceptions raised during request\n\n" if exceptions.empty? body << "No exceptions raised" else body << "Exceptions: (#{exceptions.size} total)\n" exceptions.group_by(&:class).each do |klass, exceptions| body << " #{klass.name} (#{exceptions.size})\n" end body << "\nBacktraces\n" exceptions.each_with_index do |e, i| body << "##{i+1}: #{e.class} - \"#{e.message}\"\n #{e.backtrace.join("\n ")}\n\n" end end text_result(body) end
flamegraph(graph)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 559 def flamegraph(graph) headers = {'Content-Type' => 'text/html'} [200, headers, [graph]] end
generate_html(page_struct, env, result_json = page_struct.to_json)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 111 def generate_html(page_struct, env, result_json = page_struct.to_json) path = "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}" version = MiniProfiler::ASSET_VERSION json = result_json includes = get_profile_script(env) name = page_struct[:name] duration = page_struct.duration_ms.round(1).to_s MiniProfiler.share_template.result(binding) end
get_profile_script(env)
click to toggle source
get_profile_script
returns script to be injected inside current html page By default, profile_script is appended to the end of all html requests automatically. Calling get_profile_script
cancels automatic append for the current page Use it when:
-
you have disabled auto append behaviour throught :auto_inject => false flag
-
you do not want script to be automatically appended for the current page. You can also call
cancel_auto_inject
# File lib/mini_profiler/profiler.rb, line 587 def get_profile_script(env) path = "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}" settings = { :path => path, :version => MiniProfiler::ASSET_VERSION, :verticalPosition => @config.vertical_position, :horizontalPosition => @config.horizontal_position, :showTrivial => @config.show_trivial, :showChildren => @config.show_children, :maxTracesToShow => @config.max_traces_to_show, :showControls => @config.show_controls, :authorized => true, :toggleShortcut => @config.toggle_shortcut, :startHidden => @config.start_hidden, :collapseResults => @config.collapse_results, :htmlContainer => @config.html_container } if current && current.page_struct settings[:ids] = ids_comma_separated(env) settings[:currentId] = current.page_struct[:id] else settings[:ids] = [] settings[:currentId] = "" end # TODO : cache this snippet script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__))) # replace the variables settings.each do |k,v| regex = Regexp.new("\\{#{k.to_s}\\}") script.gsub!(regex, v.to_s) end current.inject_js = false if current script end
help(client_settings, env)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 530 def help(client_settings, env) headers = {'Content-Type' => 'text/html'} body = "<html><body> <pre style='line-height: 30px; font-size: 16px;'> Append the following to your query string: #{make_link "help", env} : display this screen #{make_link "env", env} : display the rack environment #{make_link "skip", env} : skip mini profiler for this request #{make_link "no-backtrace", env} #{"(*) " if client_settings.backtrace_none?}: don't collect stack traces from all the SQL executed (sticky, use pp=normal-backtrace to enable) #{make_link "normal-backtrace", env} #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally #{make_link "full-backtrace", env} #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable) #{make_link "disable", env} : disable profiling for this session #{make_link "enable", env} : enable profiling for this session (if previously disabled) #{make_link "profile-gc", env} : perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only) #{make_link "profile-memory", env} : requires the memory_profiler gem, new location based report #{make_link "flamegraph", env} : works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem). #{make_link "flamegraph&flamegraph_sample_rate=1", env}: creates a flamegraph with the specified sample rate (in ms). Overrides value set in config #{make_link "flamegraph_embed", env} : works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem), embedded resources for use on an intranet. #{make_link "trace-exceptions", env} : requires Ruby 2.0, will return all the spots where your application raises exceptions #{make_link "analyze-memory", env} : requires Ruby 2.0, will perform basic memory analysis of heap </pre> </body> </html> " [200, headers, [body]] end
ids(env)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 564 def ids(env) all = ([current.page_struct[:id]] + (@storage.get_unviewed_ids(user(env)) || [])).uniq if all.size > @config.max_traces_to_show all = all[0...@config.max_traces_to_show] @storage.set_all_unviewed(user(env), all) end all end
ids_comma_separated(env)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 577 def ids_comma_separated(env) ids(env).join(",") end
ids_json(env)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 573 def ids_json(env) ::JSON.generate(ids(env)) end
inject(fragment, script)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 393 def inject(fragment, script) # find explicit or implicit body index = fragment.rindex(/<\/body>/i) || fragment.rindex(/<\/html>/i) if index # if for whatever crazy reason we dont get a utf string, # just force the encoding, no utf in the mp scripts anyway if script.respond_to?(:encoding) && script.respond_to?(:force_encoding) script = script.force_encoding(fragment.encoding) end fragment.insert(index, script) else fragment end end
inject_profiler(env,status,headers,body)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 359 def inject_profiler(env,status,headers,body) # mini profiler is meddling with stuff, we can not cache cause we will get incorrect data # Rack::ETag has already inserted some nonesense in the chain content_type = headers['Content-Type'] if config.disable_caching headers.delete('ETag') headers.delete('Date') end headers['X-MiniProfiler-Original-Cache-Control'] = headers['Cache-Control'] headers['Cache-Control'] = "#{"no-store, " if config.disable_caching}must-revalidate, private, max-age=0" # inject header if headers.is_a? Hash headers['X-MiniProfiler-Ids'] = ids_json(env) end if current.inject_js && content_type =~ /text\/html/ response = Rack::Response.new([], status, headers) script = self.get_profile_script(env) if String === body response.write inject(body,script) else body.each { |fragment| response.write inject(fragment, script) } end body.close if body.respond_to? :close response.finish else nil end end
make_link(postfix, env)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 525 def make_link(postfix, env) link = env["PATH_INFO"] + "?" + env["QUERY_STRING"].sub("pp=help", "pp=#{postfix}") "pp=<a href='#{link}'>#{postfix}</a>" end
serve_html(env)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 122 def serve_html(env) path = env['PATH_INFO'].sub('//', '/') file_name = path.sub(@config.base_url_path, '') return serve_results(env) if file_name.eql?('results') resources_env = env.dup resources_env['PATH_INFO'] = file_name rack_file = Rack::File.new(MiniProfiler.resources_root, {'Cache-Control' => 'max-age:86400'}) rack_file.call(resources_env) end
serve_results(env)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 83 def serve_results(env) request = Rack::Request.new(env) id = request[:id] page_struct = @storage.load(id) unless page_struct @storage.set_viewed(user(env), id) id = ERB::Util.html_escape(request['id']) user_info = ERB::Util.html_escape(user(env)) return [404, {}, ["Request not found: #{id} - user #{user_info}"]] end unless page_struct[:has_user_viewed] page_struct[:client_timings] = TimerStruct::Client.init_from_form_data(env, page_struct) page_struct[:has_user_viewed] = true @storage.save(page_struct) @storage.set_viewed(user(env), id) end # If we're an XMLHttpRequest, serve up the contents as JSON if request.xhr? result_json = page_struct.to_json [200, { 'Content-Type' => 'application/json'}, [result_json]] else # Otherwise give the HTML back html = generate_html(page_struct, env) [200, {'Content-Type' => 'text/html'}, [html]] end end
text_result(body)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 520 def text_result(body) headers = {'Content-Type' => 'text/plain'} [200, headers, [body]] end
trim_strings(strings, max_size)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 448 def trim_strings(strings, max_size) strings.sort!{|a,b| b[1] <=> a[1]} i = 0 strings.delete_if{|_| (i+=1) > max_size} end
user(env)
click to toggle source
# File lib/mini_profiler/profiler.rb, line 79 def user(env) @config.user_provider.call(env) end