class Rack::MiniProfiler

Constants

ASSET_VERSION
SOURCE_CODE_URI
VERSION

Attributes

subscribe_sql_active_record[RW]

Public Class Methods

add_snapshot_custom_field(key, value) click to toggle source
# File lib/mini_profiler.rb, line 59
def add_snapshot_custom_field(key, value)
  thread_var_key = :mini_profiler_snapshot_custom_fields
  Thread.current[thread_var_key] ||= {}
  Thread.current[thread_var_key][key] = value
end
advanced_tools_message() click to toggle source
# File lib/mini_profiler.rb, line 95
      def advanced_tools_message
        <<~TEXT
          This feature is disabled by default, to enable set the enable_advanced_debugging_tools option to true in Mini Profiler config.
        TEXT
      end
authorize_request() click to toggle source
# File lib/mini_profiler.rb, line 83
def authorize_request
  Thread.current[:mp_authorized] = true
end
binds_to_params(binds) click to toggle source
# File lib/mini_profiler.rb, line 101
def binds_to_params(binds)
  return if binds.nil? || config.max_sql_param_length == 0
  # map ActiveRecord::Relation::QueryAttribute to [name, value]
  params = binds.map { |c| c.kind_of?(Array) ? [c.first, c.last] : [c.name, c.value] }
  if (skip = config.skip_sql_param_names)
    params.map { |(n, v)| n =~ skip ? [n, nil] : [n, v] }
  else
    params
  end
end
config() click to toggle source

So we can change the configuration if we want

# File lib/mini_profiler.rb, line 36
def config
  @config ||= Config.default
end
create_current(env = {}, options = {}) click to toggle source
# File lib/mini_profiler.rb, line 74
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.rb, line 48
def current
  Thread.current[:mini_profiler_private]
end
current=(c) click to toggle source
# File lib/mini_profiler.rb, line 52
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_snapshot_custom_fields] = nil
  Thread.current[:mp_ongoing_snapshot] = nil
  Thread.current[:mini_profiler_private] = c
end
deauthorize_request() click to toggle source
# File lib/mini_profiler.rb, line 87
def deauthorize_request
  Thread.current[:mp_authorized] = nil
end
discard_results() click to toggle source

discard existing results, don’t track this request

# File lib/mini_profiler.rb, line 70
def discard_results
  self.current.discard = true if current
end
generate_id() click to toggle source
# File lib/mini_profiler.rb, line 27
def generate_id
  rand(36**20).to_s(36)
end
get_snapshot_custom_fields() click to toggle source
# File lib/mini_profiler.rb, line 65
def get_snapshot_custom_fields
  Thread.current[:mini_profiler_snapshot_custom_fields]
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.rb, line 126
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
patch_rails?() click to toggle source
# File lib/mini_profiler.rb, line 23
def patch_rails?
  !!defined?(Rack::MINI_PROFILER_ENABLE_RAILS_PATCHES)
end
redact_sql_queries?() click to toggle source
# File lib/mini_profiler.rb, line 117
def redact_sql_queries?
  Thread.current[:mp_ongoing_snapshot] == true &&
  Rack::MiniProfiler.config.snapshots_redact_sql_queries
end
request_authorized?() click to toggle source
# File lib/mini_profiler.rb, line 91
def request_authorized?
  Thread.current[:mp_authorized]
end
reset_config() click to toggle source
# File lib/mini_profiler.rb, line 31
def reset_config
  @config = Config.default
end
resources_root() click to toggle source
# File lib/mini_profiler.rb, line 40
def resources_root
  @resources_root ||= ::File.expand_path("../html", __FILE__)
end
share_template() click to toggle source
# File lib/mini_profiler.rb, line 44
def share_template
  @share_template ||= ERB.new(::File.read(::File.expand_path("html/share.html", ::File.dirname(__FILE__))))
end
snapshots_transporter?() click to toggle source
# File lib/mini_profiler.rb, line 112
def snapshots_transporter?
  !!config.snapshots_transport_destination_url &&
  !!config.snapshots_transport_auth_key
end

Public Instance Methods

advanced_debugging_enabled?() click to toggle source
# File lib/mini_profiler.rb, line 217
def advanced_debugging_enabled?
  config.enable_advanced_debugging_tools
end
analyze_memory() click to toggle source
# File lib/mini_profiler.rb, line 595
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".dup

  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
cache_control_value() click to toggle source
# File lib/mini_profiler.rb, line 820
def cache_control_value
  86400
end
call(env) click to toggle source
# File lib/mini_profiler.rb, line 225
    def call(env)
      start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
      client_settings = ClientSettings.new(env, @storage, start)
      MiniProfiler.deauthorize_request if @config.authorization_mode == :allow_authorized

      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.profile_parameter}=skip/.match?(query_string) || (
        @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
        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 take_snapshot?(path)
          return client_settings.handle_cookie(take_snapshot(env, start))
        else
          return client_settings.handle_cookie(@app.call(env))
        end
      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 =~ /#{@config.profile_parameter}=disable/ || has_disable_cookie
        skip_it = true
      end

      if query_string =~ /#{@config.profile_parameter}=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 =~ /#{@config.profile_parameter}=profile-gc/
        return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
        current.measure = false if current
        return client_settings.handle_cookie(Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env))
      end

      # profile memory
      if query_string =~ /#{@config.profile_parameter}=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

        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 =~ /#{@config.profile_parameter}=normal-backtrace/
        client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
      elsif query_string =~ /#{@config.profile_parameter}=no-backtrace/
        current.skip_backtrace = true
        client_settings.backtrace_level = ClientSettings::BACKTRACE_NONE
      elsif query_string =~ /#{@config.profile_parameter}=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 =~ /#{@config.profile_parameter}=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=(async-)?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      = query_string.match(/flamegraph_sample_rate=([\d\.]+)/)

            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 = query_string.match(/flamegraph_mode=([a-zA-Z]+)/)

            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

            flamegraph = StackProf.run(
              mode: mode,
              raw: true,
              aggregate: false,
              interval: (sample_rate * 1000).to_i
            ) do
              status, headers, body = @app.call(env)
            end
          else
            message = "Please install the stackprof gem and require it: add gem 'stackprof' 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: status, headers: headers)
            )
          end
        elsif path == '/rack-mini-profiler/requests'
          blank_page_html = <<~HTML
            <!DOCTYPE html>
            <html>
              <head>
                <title>Rack::MiniProfiler Requests</title>
              </head>
              <body>
              </body>
            </html>
          HTML

          status, headers, body = [200, { 'Content-Type' => 'text/html' }, [blank_page_html.dup]]
        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 == :allow_authorized && !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 =~ /#{@config.profile_parameter}=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 query_string =~ /#{@config.profile_parameter}=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 query_string =~ /#{@config.profile_parameter}=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((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000)

      if flamegraph && query_string =~ /#{@config.profile_parameter}=flamegraph/
        body.close if body.respond_to? :close
        return client_settings.handle_cookie(self.flamegraph(flamegraph, path, env))
      elsif flamegraph # async-flamegraph
        page_struct[:has_flamegraph] = true
        page_struct[: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.rb, line 816
def cancel_auto_inject(env)
  current.inject_js = false
end
config() click to toggle source
# File lib/mini_profiler.rb, line 213
def config
  @config
end
current() click to toggle source
# File lib/mini_profiler.rb, line 205
def current
  MiniProfiler.current
end
current=(c) click to toggle source
# File lib/mini_profiler.rb, line 209
def current=(c)
  MiniProfiler.current = c
end
dump_env(env) click to toggle source
# File lib/mini_profiler.rb, line 567
def dump_env(env)
  body = "Rack Environment\n---------------\n".dup
  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.rb, line 549
def dump_exceptions(exceptions)
  body = "Exceptions raised during request\n\n".dup
  if exceptions.empty?
    body << "No exceptions raised"
  else
    body << "Exceptions: (#{exceptions.size} total)\n"
    exceptions.group_by(&:class).each do |klass, exceptions_per_class|
      body << "  #{klass.name} (#{exceptions_per_class.size})\n"
    end

    body << "\nBacktraces\n"
    exceptions.each_with_index do |e, i|
      body << "##{i + 1}: #{e.class} - \"#{e.message.lines.first.chomp}\"\n  #{e.backtrace.join("\n  ")}\n\n"
    end
  end
  text_result(body)
end
flamegraph(graph, path, env) click to toggle source
# File lib/mini_profiler.rb, line 708
    def flamegraph(graph, path, env)
      headers = { 'Content-Type' => 'text/html' }
      iframe_src = "#{public_base_path(env)}speedscope/index.html"
      html = <<~HTML
        <!DOCTYPE html>
        <html>
          <head>
            <title>Rack::MiniProfiler Flamegraph</title>
            <style>
              body { margin: 0; height: 100vh; }
              #speedscope-iframe { width: 100%; height: 100%; border: none; }
            </style>
          </head>
          <body>
            <script type="text/javascript">
              var graph = #{JSON.generate(graph)};
              var json = JSON.stringify(graph);
              var blob = new Blob([json], { type: 'text/plain' });
              var objUrl = encodeURIComponent(URL.createObjectURL(blob));
              var iframe = document.createElement('IFRAME');
              iframe.setAttribute('id', 'speedscope-iframe');
              document.body.appendChild(iframe);
              var iframeUrl = '#{iframe_src}#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{CGI.escape(path)}';
              iframe.setAttribute('src', iframeUrl);
            </script>
          </body>
        </html>
      HTML
      [200, headers, [html]]
    end
generate_html(page_struct, env, result_json = page_struct.to_json) click to toggle source
# File lib/mini_profiler.rb, line 178
def generate_html(page_struct, env, result_json = page_struct.to_json)
  # double-assigning to suppress "assigned but unused variable" warnings
  path = path = "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
  version = version = MiniProfiler::ASSET_VERSION
  json = json = result_json
  includes = includes = get_profile_script(env)
  name = name = page_struct[:name]
  duration = 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.rb, line 758
def get_profile_script(env)
  path = public_base_path(env)
  version = MiniProfiler::ASSET_VERSION
  if @config.assets_url
    url = @config.assets_url.call('rack-mini-profiler.js', version, env)
    css_url = @config.assets_url.call('rack-mini-profiler.css', version, env)
  end

  url = "#{path}includes.js?v=#{version}" if !url
  css_url = "#{path}includes.css?v=#{version}" if !css_url

  content_security_policy_nonce = @config.content_security_policy_nonce ||
                                  env["action_dispatch.content_security_policy_nonce"] ||
                                  env["secure_headers_content_security_policy_nonce"]

  settings = {
   path: path,
   url: url,
   cssUrl: css_url,
   version: 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,
   showTotalSqlCount: @config.show_total_sql_count,
   authorized: true,
   toggleShortcut: @config.toggle_shortcut,
   startHidden: @config.start_hidden,
   collapseResults: @config.collapse_results,
   htmlContainer: @config.html_container,
   hiddenCustomFields: @config.snapshot_hidden_custom_fields.join(','),
   cspNonce: content_security_policy_nonce,
   hotwireTurboDriveSupport: @config.enable_hotwire_turbo_drive_support,
  }

  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 = ::File.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.rb, line 671
    def help(client_settings, env)
      headers = { 'Content-Type' => 'text/html' }
      html = <<~HTML
        <!DOCTYPE html>
        <html>
          <head>
            <title>Rack::MiniProfiler Help</title>
          </head>
          <body>
            <pre style='line-height: 30px; font-size: 16px'>
              This is the help menu of the <a href='#{Rack::MiniProfiler::SOURCE_CODE_URI}'>rack-mini-profiler</a> gem, append the following to your query string for more options:

              #{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 #{@config.profile_parameter}=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 #{@config.profile_parameter}=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
              #{make_link "profile-memory", env} : requires the memory_profiler gem, new location based report
              #{make_link "flamegraph", env} : a graph representing sampled activity (requires the stackprof gem).
              #{make_link "async-flamegraph", env} : store flamegraph data for this page and all its AJAX requests. Flamegraph links will be available in the mini-profiler UI (requires the stackprof 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&flamegraph_mode=cpu", env}: creates a flamegraph with the specified mode (one of cpu, wall, object, or custom). Overrides value set in config
              #{make_link "flamegraph_embed", env} : a graph representing sampled activity (requires the stackprof gem), embedded resources for use on an intranet.
              #{make_link "trace-exceptions", env} : will return all the spots where your application raises exceptions
              #{make_link "analyze-memory", env} : will perform basic memory analysis of heap
            </pre>
          </body>
        </html>
      HTML

      [200, headers, [html]]
    end
ids(env) click to toggle source
# File lib/mini_profiler.rb, line 739
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.rb, line 748
def ids_comma_separated(env)
  ids(env).join(",")
end
inject(fragment, script) click to toggle source
# File lib/mini_profiler.rb, line 528
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

    safe_script = script
    if script.respond_to?(:html_safe)
      safe_script = script.html_safe
    end

    fragment.insert(index, safe_script)
  else
    fragment
  end
end
inject_profiler(env, status, headers, body) click to toggle source
# File lib/mini_profiler.rb, line 494
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'] unless headers['Cache-Control'].nil?
  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_comma_separated(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
serve_html(env) click to toggle source
# File lib/mini_profiler.rb, line 190
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')
  return handle_snapshots_request(env) if file_name.eql?('snapshots')
  return serve_flamegraph(env) if file_name.eql?('flamegraph')

  resources_env = env.dup
  resources_env['PATH_INFO'] = file_name

  rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' => "max-age=#{cache_control_value}")
  rack_file.call(resources_env)
end
serve_results(env) click to toggle source
# File lib/mini_profiler.rb, line 141
def serve_results(env)
  request     = Rack::Request.new(env)
  id          = request.params['id']
  group_name  = request.params['group']
  is_snapshot = group_name && group_name.size > 0
  if is_snapshot
    page_struct = @storage.load_snapshot(id, group_name)
  else
    page_struct = @storage.load(id)
  end
  if !page_struct && is_snapshot
    id = ERB::Util.html_escape(id)
    return [404, {}, ["Snapshot with id '#{id}' not found"]]
  elsif !page_struct
    @storage.set_viewed(user(env), id)
    id        = ERB::Util.html_escape(id)
    user_info = ERB::Util.html_escape(user(env))
    return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
  end
  if !page_struct[:has_user_viewed] && !is_snapshot
    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, status: 200, headers: nil) click to toggle source
# File lib/mini_profiler.rb, line 661
def text_result(body, status: 200, headers: nil)
  headers = (headers || {}).merge('Content-Type' => 'text/plain; charset=utf-8')
  [status, headers, [body]]
end
tool_disabled_message(client_settings) click to toggle source
# File lib/mini_profiler.rb, line 221
def tool_disabled_message(client_settings)
  client_settings.handle_cookie(text_result(Rack::MiniProfiler.advanced_tools_message))
end
trim_strings(strings, max_size) click to toggle source
# File lib/mini_profiler.rb, line 589
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.rb, line 137
def user(env)
  @config.user_provider.call(env)
end

Private Instance Methods

handle_snapshots_request(env) click to toggle source
# File lib/mini_profiler.rb, line 826
    def handle_snapshots_request(env)
      self.current = nil
      MiniProfiler.authorize_request
      status = 200
      headers = { 'Content-Type' => 'text/html' }
      qp = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
      if group_name = qp["group_name"]
        list = @storage.snapshots_group(group_name)
        list.each do |snapshot|
          snapshot[:url] = url_for_snapshot(snapshot[:id], group_name)
        end
        data = {
          group_name: group_name,
          list: list
        }
      else
        list = @storage.snapshots_overview
        list.each do |group|
          group[:url] = url_for_snapshots_group(group[:name])
        end
        data = {
          page: "overview",
          list: list
        }
      end
      data_html = <<~HTML
        <div style="display: none;" id="snapshots-data">
        #{data.to_json}
        </div>
      HTML
      response = Rack::Response.new([], status, headers)

      response.write <<~HTML
        <!DOCTYPE html>
        <html>
          <head>
            <title>Rack::MiniProfiler Snapshots</title>
          </head>
          <body class="mp-snapshots">
      HTML
      response.write(data_html)
      script = self.get_profile_script(env)
      response.write(script)
      response.write <<~HTML
          </body>
        </html>
      HTML
      response.finish
    end
public_base_path(env) click to toggle source
# File lib/mini_profiler.rb, line 950
def public_base_path(env)
  "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
end
rails_route_from_path(path, method) click to toggle source
# File lib/mini_profiler.rb, line 894
def rails_route_from_path(path, method)
  if defined?(Rails) && defined?(ActionController::RoutingError)
    hash = Rails.application.routes.recognize_path(path, method: method)
    if hash && hash[:controller] && hash[:action]
      "#{hash[:controller]}##{hash[:action]}"
    end
  end
rescue ActionController::RoutingError
  nil
end
serve_flamegraph(env) click to toggle source
# File lib/mini_profiler.rb, line 876
def serve_flamegraph(env)
  request     = Rack::Request.new(env)
  id          = request.params['id']
  page_struct = @storage.load(id)

  if !page_struct
    id        = ERB::Util.html_escape(id)
    user_info = ERB::Util.html_escape(user(env))
    return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
  end

  if !page_struct[:flamegraph]
    return [404, {}, ["No flamegraph available for #{ERB::Util.html_escape(id)}"]]
  end

  self.flamegraph(page_struct[:flamegraph], page_struct[:request_path], env)
end
take_snapshot(env, start) click to toggle source
# File lib/mini_profiler.rb, line 921
def take_snapshot(env, start)
  MiniProfiler.create_current(env, @config)
  Thread.current[:mp_ongoing_snapshot] = true
  results = @app.call(env)
  status = results[0].to_i
  if status >= 200 && status < 300
    page_struct = current.page_struct
    page_struct[:root].record_time(
      (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
    )
    custom_fields = MiniProfiler.get_snapshot_custom_fields
    page_struct[:custom_fields] = custom_fields if custom_fields
    if Rack::MiniProfiler.snapshots_transporter?
      Rack::MiniProfiler::SnapshotsTransporter.transport(page_struct)
    else
      group_name = rails_route_from_path(page_struct[:request_path], page_struct[:request_method])
      group_name ||= page_struct[:request_path]
      group_name = "#{page_struct[:request_method]} #{group_name}"
      @storage.push_snapshot(
        page_struct,
        group_name,
        @config
      )
    end
  end
  self.current = nil
  results
end
take_snapshot?(path) click to toggle source
# File lib/mini_profiler.rb, line 915
def take_snapshot?(path)
  @config.snapshot_every_n_requests > 0 &&
  !path.start_with?(@config.base_url_path) &&
  @storage.should_take_snapshot?(@config.snapshot_every_n_requests)
end
url_for_snapshot(id, group_name) click to toggle source
# File lib/mini_profiler.rb, line 910
def url_for_snapshot(id, group_name)
  qs = Rack::Utils.build_query({ id: id, group: group_name })
  "/#{@config.base_url_path.gsub('/', '')}/results?#{qs}"
end
url_for_snapshots_group(group_name) click to toggle source
# File lib/mini_profiler.rb, line 905
def url_for_snapshots_group(group_name)
  qs = Rack::Utils.build_query({ group_name: group_name })
  "/#{@config.base_url_path.gsub('/', '')}/snapshots?#{qs}"
end