class Rack::MiniProfiler

Constants

VERSION

Public Class Methods

new(app, config = nil) click to toggle source

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

Public Instance Methods

authorize_request() click to toggle source
# File Ruby/lib/mini_profiler/profiler.rb, line 72
def authorize_request
  Thread.current[:mp_authorized] = true
end
call(env) click to toggle source
# 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
cancel_auto_inject(env) click to toggle source

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
config() click to toggle source

So we can change the configuration if we want

# File Ruby/lib/mini_profiler/profiler.rb, line 41
def config
  @config ||= Config.default
end
create_current(env={}, options={}) click to toggle source
# 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
current() click to toggle source
# File Ruby/lib/mini_profiler/profiler.rb, line 50
def current
  Thread.current[:mini_profiler_private]
end
current=(c) click to toggle source
# 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
deauthorize_request() click to toggle source
# File Ruby/lib/mini_profiler/profiler.rb, line 76
def deauthorize_request
  Thread.current[:mp_authorized] = nil
end
discard_results() click to toggle source

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
dump_env(env) click to toggle source
# 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
dump_exceptions(exceptions) click to toggle source
# 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
flamegraph(graph) click to toggle source
# File Ruby/lib/mini_profiler/profiler.rb, line 466
def flamegraph(graph)
  headers = {'Content-Type' => 'text/html'}
  [200, headers, [graph]]
end
generate_id() click to toggle source
# File Ruby/lib/mini_profiler/profiler.rb, line 32
def generate_id
  rand(36**20).to_s(36)
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 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
help(client_settings) click to toggle source
# 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
ids_comma_separated(env) click to toggle source
# 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
ids_json(env) click to toggle source
# 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
inject(fragment, script) click to toggle source
# File Ruby/lib/mini_profiler/profiler.rb, line 374
def inject(fragment, script)
  if fragment.match(/<\/body>/i)
    # explicit </body>

    regex = /<\/body>/i
    close_tag = '</body>'
  elsif fragment.match(/<\/html>/i)
    # implicit </body>

    regex = /<\/html>/i
    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
inject_profiler(env,status,headers,body) click to toggle source
# 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
request_authorized?() click to toggle source
# File Ruby/lib/mini_profiler/profiler.rb, line 80
def request_authorized?
  Thread.current[:mp_authorized]
end
reset_config() click to toggle source
# File Ruby/lib/mini_profiler/profiler.rb, line 36
def reset_config
  @config = Config.default
end
serve_html(env) click to toggle source
# 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
serve_results(env) click to toggle source
# 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
share_template() click to toggle source
# File Ruby/lib/mini_profiler/profiler.rb, line 45
def share_template
  return @share_template unless @share_template.nil?
  @share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
end
user(env) click to toggle source
# File Ruby/lib/mini_profiler/profiler.rb, line 100
def user(env)
  @config.user_provider.call(env)
end