MiniProfiler/rack-mini-profiler

View on GitHub
lib/mini_profiler/actions.rb

Summary

Maintainability
B
4 hrs
Test Coverage
# frozen_string_literal: true
module Rack
  class MiniProfiler
    module Actions
      def serve_snapshot(env)
        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

      def serve_file(env, file_name:)
        resources_env = env.dup
        resources_env['PATH_INFO'] = file_name

        if Gem::Version.new(Rack.release) >= Gem::Version.new("2.1.0")
          rack_file = Rack::Files.new(resources_root, 'Cache-Control' => "max-age=#{cache_control_value}")
        else
          rack_file = Rack::File.new(resources_root, 'Cache-Control' => "max-age=#{cache_control_value}")
        end

        rack_file.call(resources_env)
      end

      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

      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

      def serve_profile_gc(env, client_settings)
        return tool_disabled_message(client_settings) if !advanced_debugging_enabled?

        client_settings.handle_cookie(Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env))
      end

      def serve_profile_memory(env, client_settings)
        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(env['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)
        client_settings.handle_cookie(text_result(result.string))
      end
    end
  end
end