So we can change the configuration if we want
# File Ruby/lib/mini_profiler/profiler.rb, line 41 def config @config ||= Config.default end
# File Ruby/lib/mini_profiler/profiler.rb, line 64 def create_current(env={}, options={}) # profiling the request self.current = Context.new self.current.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest') self.current.page_struct = PageTimerStruct.new(env) self.current.current_timer = current.page_struct['Root'] end
# File Ruby/lib/mini_profiler/profiler.rb, line 50 def current Thread.current[:mini_profiler_private] end
# File Ruby/lib/mini_profiler/profiler.rb, line 54 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 existing results, don't track this request
# File Ruby/lib/mini_profiler/profiler.rb, line 60 def discard_results self.current.discard = true if current end
# File Ruby/lib/mini_profiler/profiler.rb, line 32 def generate_id rand(36**20).to_s(36) end
options: :auto_inject - should script be automatically injected on every html page (not xhr)
# File Ruby/lib/mini_profiler/profiler.rb, line 89 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
# File Ruby/lib/mini_profiler/profiler.rb, line 36 def reset_config @config = Config.default end
# File Ruby/lib/mini_profiler/profiler.rb, line 174 def call(env) client_settings = ClientSettings.new(env) status = headers = body = nil query_string = env['QUERY_STRING'] path = env['PATH_INFO'] skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) || (@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) || query_string =~ /pp=skip/ has_profiling_cookie = client_settings.has_cookie? if skip_it || (@config.authorization_mode == :whitelist && !has_profiling_cookie) status,headers,body = @app.call(env) if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized? client_settings.write!(headers) end return [status,headers,body] end # handle all /mini-profiler requests here return 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 end if skip_it status,headers,body = @app.call(env) client_settings.disable_profiling = true client_settings.write!(headers) return [status,headers,body] else client_settings.disable_profiling = false end if query_string =~ /pp=profile-gc/ if query_string =~ /pp=profile-gc-time/ return Rack::MiniProfiler::GCProfiler.new.profile_gc_time(@app, env) else return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env) end end MiniProfiler.create_current(env, @config) MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist 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 start = Time.now 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 env['HTTP_IF_MODIFIED_SINCE'] = '' env['HTTP_IF_NONE_MATCH'] = '' 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 # first param is the path # 0.5 means attempt to collect a sample each 0.5 secs flamegraph = Flamegraph.generate(nil, fidelity: 0.5) do status,headers,body = @app.call(env) end end else status,headers,body = @app.call(env) end client_settings.write!(headers) ensure trace.disable if trace end skip_it = current.discard if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?) # this is non-obvious, don't kill the profiling cookie on errors or short requests # this ensures that stuff that never reaches the rails stack does not kill profiling if status == 200 && ((Time.now - start) > 0.1) client_settings.discard_cookie!(headers) end skip_it = true end return [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 return dump_exceptions exceptions end if query_string =~ /pp=env/ body.close if body.respond_to? :close return dump_env env end if query_string =~ /pp=help/ body.close if body.respond_to? :close return help(client_settings) 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 self.flamegraph(flamegraph) end # no matter what it is, it should be unviewed, otherwise we will miss POST @storage.set_unviewed(page_struct['User'], page_struct['Id']) @storage.save(page_struct) # inject headers, script if headers['Content-Type'] && status == 200 client_settings.write!(headers) result = inject_profiler(env,status,headers,body) return result if result end client_settings.write!(headers) [status, headers, body] ensure # Make sure this always happens current = nil end
cancels automatic injection of profile script for the current page
# File Ruby/lib/mini_profiler/profiler.rb, line 514 def cancel_auto_inject(env) current.inject_js = false end
# File Ruby/lib/mini_profiler/profiler.rb, line 169 def config @config end
# File Ruby/lib/mini_profiler/profiler.rb, line 160 def current MiniProfiler.current end
# File Ruby/lib/mini_profiler/profiler.rb, line 164 def current=(c) MiniProfiler.current=c end
# File Ruby/lib/mini_profiler/profiler.rb, line 421 def dump_env(env) headers = {'Content-Type' => 'text/plain'} 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" [200, headers, [body]] end
# File Ruby/lib/mini_profiler/profiler.rb, line 411 def dump_exceptions(exceptions) headers = {'Content-Type' => 'text/plain'} body = "Exceptions (#{exceptions.length} raised during request)\n\n" exceptions.each do |e| body << "#{e.class} #{e.message}\n#{e.backtrace.join("\n")}\n\n\n\n" end [200, headers, [body]] end
# File Ruby/lib/mini_profiler/profiler.rb, line 466 def flamegraph(graph) headers = {'Content-Type' => 'text/html'} [200, headers, [graph]] end
#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 Ruby/lib/mini_profiler/profiler.rb, line 489 def get_profile_script(env) ids = ids_comma_separated(env) path = "#{env['SCRIPT_NAME']}#{@config.base_url_path}" version = MiniProfiler::VERSION position = @config.position showTrivial = false showChildren = false maxTracesToShow = 10 showControls = false currentId = current.page_struct["Id"] authorized = true toggleShortcut = @config.toggle_shortcut startHidden = @config.start_hidden # TODO : cache this snippet script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__))) # replace the variables [:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized, :toggleShortcut, :startHidden].each do |v| regex = Regexp.new("\\{#{v.to_s}\\}") script.gsub!(regex, eval(v.to_s).to_s) end current.inject_js = false script end
# File Ruby/lib/mini_profiler/profiler.rb, line 444 def help(client_settings) headers = {'Content-Type' => 'text/plain'} body = "Append the following to your query string: pp=help : display this screen pp=env : display the rack environment pp=skip : skip mini profiler for this request pp=no-backtrace #{"(*) " if client_settings.backtrace_none?}: don't collect stack traces from all the SQL executed (sticky, use pp=normal-backtrace to enable) pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable) pp=disable : disable profiling for this session pp=enable : enable profiling for this session (if previously disabled) pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only) pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only) pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem). pp=trace-exceptions: requires Ruby 2.0, will return all the spots where your application raises execptions " client_settings.write!(headers) [200, headers, [body]] end
# File Ruby/lib/mini_profiler/profiler.rb, line 477 def ids_comma_separated(env) # cap at 10 ids, otherwise there is a chance you can blow the header ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8] ids.join(",") end
# File Ruby/lib/mini_profiler/profiler.rb, line 471 def ids_json(env) # cap at 10 ids, otherwise there is a chance you can blow the header ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8] ::JSON.generate(ids.uniq) end
# File Ruby/lib/mini_profiler/profiler.rb, line 374 def inject(fragment, script) if fragment.match(/<\/body>/) # explicit </body> regex = /<\/body>/ close_tag = '</body>' elsif fragment.match(/<\/html>/) # implicit </body> regex = /<\/html>/ close_tag = '</html>' else # implicit </body> and </html>. Just append the script. return fragment + script end matches = fragment.scan(regex).length index = 1 fragment.gsub(regex) do # though malformed there is an edge case where /body exists earlier in the html, work around if index < matches index += 1 close_tag else # 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 + close_tag).force_encoding(fragment.encoding) else script + close_tag end end end end
# File Ruby/lib/mini_profiler/profiler.rb, line 344 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'] headers.delete('ETag') headers.delete('Date') headers['Cache-Control'] = '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
# File Ruby/lib/mini_profiler/profiler.rb, line 139 def serve_html(env) file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000] return serve_results(env) if file_name.eql?('results') full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__)) return [404, {}, ["Not found"]] unless ::File.exists? full_path f = Rack::File.new nil f.path = full_path begin f.cache_control = "max-age:86400" f.serving env rescue # old versions of rack have a different api status, headers, body = f.serving headers.merge! 'Cache-Control' => "max-age:86400" [status, headers, body] end end
# File Ruby/lib/mini_profiler/profiler.rb, line 104 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) return [404, {}, ["Request not found: #{request['id']} - user #{user(env)}"]] end unless page_struct['HasUserViewed'] page_struct['ClientTimings'] = ClientTimerStruct.init_from_form_data(env, page_struct) page_struct['HasUserViewed'] = true @storage.save(page_struct) @storage.set_viewed(user(env), id) end result_json = page_struct.to_json # If we're an XMLHttpRequest, serve up the contents as JSON if request.xhr? [200, { 'Content-Type' => 'application/json'}, [result_json]] else # Otherwise give the HTML back html = MiniProfiler.share_template.dup html.gsub!(/\{path\}/, "#{env['SCRIPT_NAME']}#{@config.base_url_path}") html.gsub!(/\{version\}/, MiniProfiler::VERSION) html.gsub!(/\{json\}/, result_json) html.gsub!(/\{includes\}/, get_profile_script(env)) html.gsub!(/\{name\}/, page_struct['Name']) html.gsub!(/\{duration\}/, "%.1f" % page_struct.duration_ms) [200, {'Content-Type' => 'text/html'}, [html]] end end
# File Ruby/lib/mini_profiler/profiler.rb, line 100 def user(env) @config.user_provider.call(env) end