rails_mini_profiler 0.6.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +47 -35
  3. data/app/adapters/rails_mini_profiler/database_adapter.rb +16 -0
  4. data/app/controllers/rails_mini_profiler/application_controller.rb +9 -15
  5. data/app/controllers/rails_mini_profiler/profiled_requests_controller.rb +31 -4
  6. data/app/helpers/rails_mini_profiler/profiled_requests_helper.rb +10 -0
  7. data/app/javascript/js/clipboard_controller.js +9 -2
  8. data/app/javascript/js/filter_controller.js +4 -0
  9. data/app/javascript/stylesheets/components/buttons.scss +59 -0
  10. data/app/javascript/stylesheets/components/{profiled_request_table/profiled_request_table.scss → dropdown.scss} +0 -76
  11. data/app/javascript/stylesheets/components/input.scss +10 -0
  12. data/app/javascript/stylesheets/{navbar.scss → components/navbar.scss} +0 -0
  13. data/app/javascript/stylesheets/components/page_header.scss +7 -0
  14. data/app/javascript/stylesheets/components/{profiled_request_table/placeholder.scss → placeholder.scss} +4 -1
  15. data/app/javascript/stylesheets/components/profiled_request_table.scss +55 -0
  16. data/app/javascript/stylesheets/components/trace.scss +93 -0
  17. data/app/javascript/stylesheets/profiled_requests.scss +3 -67
  18. data/app/javascript/stylesheets/rails-mini-profiler.scss +16 -30
  19. data/app/javascript/stylesheets/traces.scss +44 -76
  20. data/app/models/rails_mini_profiler/flamegraph.rb +1 -1
  21. data/app/models/rails_mini_profiler/profiled_request.rb +1 -1
  22. data/app/models/rails_mini_profiler/trace.rb +4 -19
  23. data/app/presenters/rails_mini_profiler/controller_trace_presenter.rb +10 -2
  24. data/app/presenters/rails_mini_profiler/instantiation_trace_presenter.rb +15 -3
  25. data/app/presenters/rails_mini_profiler/profiled_request_presenter.rb +2 -2
  26. data/app/presenters/rails_mini_profiler/render_partial_trace_presenter.rb +6 -2
  27. data/app/presenters/rails_mini_profiler/render_template_trace_presenter.rb +6 -2
  28. data/app/presenters/rails_mini_profiler/sequel_trace_presenter.rb +16 -4
  29. data/app/presenters/rails_mini_profiler/trace_presenter.rb +10 -8
  30. data/app/search/rails_mini_profiler/profiled_request_search.rb +0 -1
  31. data/app/search/rails_mini_profiler/trace_search.rb +27 -0
  32. data/app/views/rails_mini_profiler/profiled_requests/shared/header/_header.erb +1 -2
  33. data/app/views/rails_mini_profiler/profiled_requests/shared/table/_table_head.erb +7 -7
  34. data/app/views/rails_mini_profiler/profiled_requests/{shared → show}/_trace.html.erb +24 -22
  35. data/app/views/rails_mini_profiler/profiled_requests/show/_trace_list.erb +12 -0
  36. data/app/views/rails_mini_profiler/profiled_requests/show/_trace_list_header.erb +87 -0
  37. data/app/views/rails_mini_profiler/profiled_requests/show/_trace_list_placeholder.erb +12 -0
  38. data/app/views/rails_mini_profiler/profiled_requests/show.html.erb +4 -17
  39. data/db/migrate/20210621185018_create_rmp.rb +3 -3
  40. data/lib/generators/rails_mini_profiler/templates/rails_mini_profiler.rb.erb +1 -0
  41. data/lib/rails_mini_profiler/badge.rb +20 -11
  42. data/lib/rails_mini_profiler/configuration/user_interface.rb +26 -0
  43. data/lib/rails_mini_profiler/configuration.rb +4 -0
  44. data/lib/rails_mini_profiler/flamegraph_guard.rb +10 -6
  45. data/lib/rails_mini_profiler/middleware.rb +12 -10
  46. data/lib/rails_mini_profiler/request_context.rb +22 -18
  47. data/lib/rails_mini_profiler/request_wrapper.rb +12 -55
  48. data/lib/rails_mini_profiler/response_wrapper.rb +21 -17
  49. data/lib/rails_mini_profiler/{tracing → tracers}/controller_tracer.rb +15 -1
  50. data/lib/rails_mini_profiler/tracers/instantiation_tracer.rb +17 -0
  51. data/lib/rails_mini_profiler/{tracing → tracers}/null_trace.rb +1 -1
  52. data/lib/rails_mini_profiler/tracers/registry.rb +76 -0
  53. data/lib/rails_mini_profiler/tracers/rmp_tracer.rb +17 -0
  54. data/lib/rails_mini_profiler/{tracing → tracers}/sequel_tracer.rb +15 -1
  55. data/lib/rails_mini_profiler/{tracing → tracers}/sequel_tracker.rb +4 -1
  56. data/lib/rails_mini_profiler/{tracing → tracers}/subscriptions.rb +6 -12
  57. data/lib/rails_mini_profiler/{tracing → tracers}/trace.rb +2 -2
  58. data/lib/rails_mini_profiler/tracers/trace_factory.rb +27 -0
  59. data/lib/rails_mini_profiler/tracers/tracer.rb +52 -0
  60. data/lib/rails_mini_profiler/tracers/view_tracer.rb +29 -0
  61. data/lib/rails_mini_profiler/tracers.rb +14 -0
  62. data/lib/rails_mini_profiler/user.rb +1 -1
  63. data/lib/rails_mini_profiler/version.rb +1 -1
  64. data/lib/rails_mini_profiler.rb +1 -1
  65. data/vendor/assets/javascripts/rails-mini-profiler.css +1 -1
  66. data/vendor/assets/javascripts/rails-mini-profiler.js +1 -1
  67. metadata +39 -26
  68. data/app/javascript/stylesheets/components/page_header/page_header.scss +0 -3
  69. data/app/models/rails_mini_profiler/controller_trace.rb +0 -37
  70. data/app/models/rails_mini_profiler/instantiation_trace.rb +0 -37
  71. data/app/models/rails_mini_profiler/render_partial_trace.rb +0 -37
  72. data/app/models/rails_mini_profiler/render_template_trace.rb +0 -37
  73. data/app/models/rails_mini_profiler/rmp_trace.rb +0 -35
  74. data/app/models/rails_mini_profiler/sequel_trace.rb +0 -37
  75. data/lib/rails_mini_profiler/tracing/trace_factory.rb +0 -37
  76. data/lib/rails_mini_profiler/tracing/tracer.rb +0 -31
  77. data/lib/rails_mini_profiler/tracing/view_tracer.rb +0 -12
  78. data/lib/rails_mini_profiler/tracing.rb +0 -11
@@ -1,69 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsMiniProfiler
4
- # A convenience wrapper around [Rack::Env]
5
- #
6
- # @!attribute body
7
- # @return [String] the request body
8
- # @!attribute method
9
- # @return [String] the request method
10
- # @!attribute path
11
- # @return [String] the request path
12
- # @!attribute query_string
13
- # @return [String] the request query string
14
- # @!attribute env
15
- # @return [Rack::Env] the original env
4
+ # A convenience wrapper extending {Rack::Request}
16
5
  #
17
6
  # @api private
18
- class RequestWrapper
19
- attr_reader :body,
20
- :method,
21
- :path,
22
- :query_string,
23
- :env
7
+ class RequestWrapper < Rack::Request
8
+ # Convenience method to read the request body as String
9
+ #
10
+ # @return [String] the request body
11
+ def body
12
+ return '' unless super
24
13
 
25
- def initialize(*_args, **attributes)
26
- @attributes = attributes
27
- setup
14
+ body = super.read
15
+ super.rewind
16
+ body
28
17
  end
29
18
 
30
19
  # The request headers
31
20
  #
32
- # @return [Hash] the headers
21
+ # @return [Hash] the request headers
33
22
  def headers
34
- @attributes[:headers] || @env.select { |k, _v| k.start_with? 'HTTP_' } || {}
35
- end
36
-
37
- private
38
-
39
- def setup
40
- @env = @attributes[:env] || {}
41
- @method = setup_method
42
- @query_string = setup_query_string
43
- @path = setup_path
44
- @body = setup_body
45
- end
46
-
47
- def setup_method
48
- @attributes[:method] || @env['REQUEST_METHOD'] || 'GET'
49
- end
50
-
51
- def setup_query_string
52
- @attributes[:query_string] || @env['QUERY_STRING'] || ''
53
- end
54
-
55
- def setup_path
56
- @attributes[:path] || @env['PATH_INFO'] || '/'
57
- end
58
-
59
- def setup_body
60
- return @attributes[:body] if @attributes[:body]
61
-
62
- return '' unless @env['rack.input']
63
-
64
- body = @env['rack.input'].read
65
- @env['rack.input'].rewind
66
- body
23
+ env.select { |k, _v| k.start_with? 'HTTP_' } || {}
67
24
  end
68
25
  end
69
26
  end
@@ -1,24 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsMiniProfiler
4
- class ResponseWrapper
5
- attr_reader :response, :rack_response
6
-
7
- delegate :status, :headers, to: :rack_response
8
-
9
- def initialize(status, headers, response)
10
- @rack_response = Rack::Response.new(response, status, headers)
11
- @response = response
12
- end
13
-
4
+ # A convenience wrapper extending {Rack::Response}
5
+ #
6
+ # @api private
7
+ class ResponseWrapper < Rack::Response
8
+ # Return the response body as String
9
+ #
10
+ # Depending on preceding middleware, response bodies may be Strings, Arrays or literally anything else. This method
11
+ # converts whatever it is to a string so we can store it later.
12
+ #
13
+ # @return [String] of the response body
14
14
  def body
15
- return '' unless json? || xml?
16
-
17
- response&.body || ''
18
- end
19
-
20
- def media_type
21
- @media_type ||= @rack_response.media_type
15
+ body = super
16
+ case body
17
+ when String
18
+ body
19
+ when Array
20
+ body.join
21
+ when ActionDispatch::Response::RackBody
22
+ body.body
23
+ else
24
+ ''
25
+ end
22
26
  end
23
27
 
24
28
  def json?
@@ -1,8 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsMiniProfiler
4
- module Tracing
4
+ module Tracers
5
5
  class ControllerTracer < Tracer
6
+ class << self
7
+ def subscribes_to
8
+ 'process_action.action_controller'
9
+ end
10
+
11
+ def build_from(event)
12
+ new(event).trace
13
+ end
14
+
15
+ def presents
16
+ ControllerTracePresenter
17
+ end
18
+ end
19
+
6
20
  def trace
7
21
  @event[:payload] = @event[:payload]
8
22
  .slice(:view_runtime, :db_runtime)
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ module Tracers
5
+ class InstantiationTracer < Tracer
6
+ class << self
7
+ def subscribes_to
8
+ 'instantiation.active_record'
9
+ end
10
+
11
+ def presents
12
+ InstantiationTracePresenter
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsMiniProfiler
4
- module Tracing
4
+ module Tracers
5
5
  class NullTrace < Trace; end
6
6
  end
7
7
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ module Tracers
5
+ # This serves as a central store for tracers. Based on the configuration, indidual tracers are registered and are
6
+ # then available to subscribe to events or render traced events.
7
+ #
8
+ # @api private
9
+ class Registry
10
+ class << self
11
+ def setup!(config)
12
+ new(config)
13
+ end
14
+ end
15
+
16
+ def initialize(config)
17
+ @config = config
18
+ @config.tracers.each { |tracer| add(tracer) }
19
+ end
20
+
21
+ # Tracers that are available in the application, indexed by events they subscribe to.
22
+ #
23
+ # @return [Hash] a hash where keys are event names and values are the corresponding tracers
24
+ def tracers
25
+ @tracers ||=
26
+ tracer_map
27
+ .values
28
+ .each_with_object({}) do |tracer, obj|
29
+ subscriptions = wrap(tracer.subscribes_to)
30
+ subscriptions.each { |subscription| obj[subscription] = tracer }
31
+ end
32
+ end
33
+
34
+ # Presenters that are available in the application, indexed by events they should present.
35
+ #
36
+ # @return [Hash] a hash where keys are event names and values are the corresponding presenters
37
+ def presenters
38
+ @presenters ||=
39
+ tracer_map
40
+ .values
41
+ .each_with_object({}) do |tracer, obj|
42
+ presenters = tracer.presents
43
+ if presenters.is_a?(Hash)
44
+ obj.merge!(presenters)
45
+ else
46
+ obj[tracer.subscribes_to] = presenters
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def tracer_map
54
+ @tracer_map ||= {}
55
+ end
56
+
57
+ def add(tracer)
58
+ tracer = tracer.to_sym
59
+ tracer = "#{tracer.to_s.camelize}Tracer"
60
+ constant = "RailsMiniProfiler::Tracers::#{tracer}".safe_constantize
61
+
62
+ tracer_map[tracer] = constant if constant
63
+ end
64
+
65
+ def wrap(object)
66
+ if object.nil?
67
+ []
68
+ elsif object.respond_to?(:to_ary)
69
+ object.to_ary || [object]
70
+ else
71
+ [object]
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ module Tracers
5
+ class RmpTracer < Tracer
6
+ class << self
7
+ def subscribes_to
8
+ 'rails_mini_profiler.total_time'
9
+ end
10
+
11
+ def presents
12
+ RmpTracePresenter
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,8 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsMiniProfiler
4
- module Tracing
4
+ module Tracers
5
5
  class SequelTracer < Tracer
6
+ class << self
7
+ def subscribes_to
8
+ 'sql.active_record'
9
+ end
10
+
11
+ def build_from(event)
12
+ new(event).trace
13
+ end
14
+
15
+ def presents
16
+ SequelTracePresenter
17
+ end
18
+ end
19
+
6
20
  def trace
7
21
  return NullTrace.new if ignore?
8
22
 
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsMiniProfiler
4
- module Tracing
4
+ module Tracers
5
+ # Utility to clean up and simplify SQL Notification events.
6
+ #
7
+ # @api private
5
8
  class SqlTracker
6
9
  TRACKED_SQL_COMMANDS = %w[SELECT INSERT UPDATE DELETE].freeze
7
10
  UNTRACKED_NAMES = %w[SCHEMA].freeze
@@ -1,20 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsMiniProfiler
4
- module Tracing
4
+ module Tracers
5
+ # Subscribe to application events. This is used during engine startup.
6
+ # @api private
5
7
  class Subscriptions
6
- DEFAULT_SUBSCRIPTIONS = %w[
7
- sql.active_record
8
- instantiation.active_record
9
- render_template.action_view
10
- render_partial.action_view
11
- process_action.action_controller
12
- rails_mini_profiler.total_time
13
- ].freeze
14
-
15
8
  class << self
16
- def setup!(&callback)
17
- DEFAULT_SUBSCRIPTIONS.each do |event|
9
+ # Subscribe to each individual active support event using a callback.
10
+ def setup!(subscriptions, &callback)
11
+ subscriptions.each do |event|
18
12
  subscribe(event, &callback)
19
13
  end
20
14
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsMiniProfiler
4
- module Tracing
4
+ module Tracers
5
5
  # A simplified representation of a trace.
6
6
  #
7
7
  # Is transformed into [RailsMiniProfiler::Trace] when recording has finished.
@@ -23,7 +23,7 @@ module RailsMiniProfiler
23
23
  # @!attribute backtrace
24
24
  # @return [String] the line where this trace was recorded
25
25
  # @!attribute allocations
26
- # @return [Integer] the number of alloactions
26
+ # @return [Integer] the number of allocations
27
27
  # @!attribute created_at
28
28
  # @return [DateTime] the creation date
29
29
  # @!attribute updated_at
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ module Tracers
5
+ # Creates traces based on the tracers registered with the application
6
+ #
7
+ # For example, an event with name 'sequel' will result in the lookup of a tracers that responds to that particular
8
+ # event. The tracer itself will then build the trace from the event.
9
+ #
10
+ # @api private
11
+ class TraceFactory
12
+ def initialize(registry)
13
+ @tracers = registry.tracers
14
+ end
15
+
16
+ # Create a new trace from an event
17
+ #
18
+ # @param event [ActiveSupport::Notifications::Event] an event from the application
19
+ #
20
+ # @return [Trace] a processed trace
21
+ def create(event)
22
+ tracer = @tracers[event.name] || Tracer
23
+ tracer.build_from(event)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ module Tracers
5
+ class Tracer
6
+ TIMESTAMP_MULTIPLIER = Rails::VERSION::MAJOR < 7 ? 100 : 1
7
+
8
+ class << self
9
+ def subscribes_to
10
+ []
11
+ end
12
+
13
+ def presents
14
+ TracePresenter
15
+ end
16
+
17
+ def build_from(event)
18
+ new(event).trace
19
+ end
20
+ end
21
+
22
+ def initialize(event)
23
+ @event = event_data(event)
24
+ end
25
+
26
+ def trace
27
+ Trace.new(**@event)
28
+ end
29
+
30
+ private
31
+
32
+ def event_data(event)
33
+ # Rails 7 changes event timings and now uses CPU milliseconds as float for start and end. We multiply by 100
34
+ # to convert the float with precision of 2 digits to integer, because integers are just easier to store and
35
+ # process than floats.
36
+ #
37
+ # See https://github.com/rails/rails/commit/81d0dc90becfe0b8e7f7f26beb66c25d84b8ec7f
38
+ start = (event.time.to_f * TIMESTAMP_MULTIPLIER).to_i
39
+ finish = (event.end.to_f * TIMESTAMP_MULTIPLIER).to_i
40
+ {
41
+ name: event.name,
42
+ start: start,
43
+ finish: finish,
44
+ duration: (event.duration.to_f * 100).to_i,
45
+ allocations: event.allocations,
46
+ backtrace: Rails.backtrace_cleaner.clean(caller),
47
+ payload: event.payload
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ module Tracers
5
+ class ViewTracer < Tracer
6
+ class << self
7
+ def subscribes_to
8
+ %w[render_template.action_view render_partial.action_view]
9
+ end
10
+
11
+ def build_from(event)
12
+ new(event).trace
13
+ end
14
+
15
+ def presents
16
+ {
17
+ 'render_template.action_view' => RenderTemplateTracePresenter,
18
+ 'render_partial.action_view' => RenderPartialTracePresenter
19
+ }
20
+ end
21
+ end
22
+
23
+ def trace
24
+ @event[:payload].slice!(:identifier, :count)
25
+ super
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_mini_profiler/tracers/registry'
4
+ require 'rails_mini_profiler/tracers/subscriptions'
5
+ require 'rails_mini_profiler/tracers/trace'
6
+ require 'rails_mini_profiler/tracers/tracer'
7
+ require 'rails_mini_profiler/tracers/controller_tracer'
8
+ require 'rails_mini_profiler/tracers/instantiation_tracer'
9
+ require 'rails_mini_profiler/tracers/sequel_tracker'
10
+ require 'rails_mini_profiler/tracers/sequel_tracer'
11
+ require 'rails_mini_profiler/tracers/view_tracer'
12
+ require 'rails_mini_profiler/tracers/rmp_tracer'
13
+ require 'rails_mini_profiler/tracers/null_trace'
14
+ require 'rails_mini_profiler/tracers/trace_factory'
@@ -30,7 +30,7 @@ module RailsMiniProfiler
30
30
  end
31
31
 
32
32
  def find_current_user
33
- return unless Rails.env.development? || Rails.env.test?
33
+ return if Rails.env.production?
34
34
 
35
35
  user = RailsMiniProfiler.configuration.user_provider.call(@env)
36
36
  User.current_user = user
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsMiniProfiler
4
- VERSION = '0.6.0'
4
+ VERSION = '0.7.2'
5
5
  end
@@ -8,7 +8,7 @@ require 'rails_mini_profiler/version'
8
8
  require 'rails_mini_profiler/engine'
9
9
 
10
10
  require 'rails_mini_profiler/models/base_model'
11
- require 'rails_mini_profiler/tracing'
11
+ require 'rails_mini_profiler/tracers'
12
12
  require 'rails_mini_profiler/configuration'
13
13
 
14
14
  require 'rails_mini_profiler/user'
@@ -1 +1 @@
1
- @import url("https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap");.flash{border-radius:5px;margin-top:1rem;padding:1rem}.flash-error{background:var(--red-500);color:#fff}.flash-notice{background:var(--green-400);color:#fff}#wrapper{height:100vh;width:100%}#speedscope-iframe{border:none;height:100%;width:100%}.header{background:var(--primary);box-shadow:0 0 20px 0 rgba(0,0,0,.2);display:flex;justify-content:center;margin:0;padding:1.5rem 0}.header a{color:#fff;text-decoration:none}.nav{justify-content:space-between;width:var(--main-width)}.home,.nav{display:flex}.home{align-items:center;text-decoration:none}.home-logo{height:64px;width:64px}.home-title{margin:0;padding:0 0 0 1rem}.header-links{font-weight:700;list-style:none;margin:0;padding:0}.header-links,.placeholder{align-items:center;display:flex}.placeholder{flex-direction:column;justify-content:center;padding-bottom:2rem;width:100%}.placeholder-image{-webkit-filter:grayscale(1) brightness(2.5);height:30%;width:30%}.placeholder-text{padding:1rem 0;text-align:center}.placeholder-link,.placeholder-link:visited,.placeholder-text{color:var(--grey-400)}.placeholder-link:hover{color:var(--grey-900)}.table{border:1px hidden var(--border-color);border-collapse:collapse;border-radius:5px;box-shadow:0 0 0 1px var(--border-color);table-layout:fixed;width:100%}.table td,.table th{padding:.5rem 1rem}.table thead tr{border:1px hidden var(--border-color);box-shadow:0 0 0 1px var(--border-color);color:var(--grey-400)}.table tr:nth-child(2n){background:var(--grey-50)}.table thead th{font-weight:400}.table-filter-icon{margin-left:.5rem;width:14px}.table tbody tr:not(.no-row){border:1px solid var(--border-color);color:var(--grey-700);cursor:pointer}.table tbody tr:not(.no-row):hover{background:var(--grey-100)}.request-checkbox{height:1rem;width:1rem;z-index:1}.dropdown-body{padding:.33rem .5rem}.dropdown-search-field{border:1px solid var(--grey-400);border-radius:5px;color:var(--grey-700);padding:.5rem}.dropdown-toggle{align-items:center;color:inherit;display:flex}.dropdown-toggle:hover{color:var(--grey-900)}.dropdown-container{background:#fff;border:1px solid var(--grey-200);border-radius:5px;box-shadow:0 8px 30px rgba(0,0,0,.12);box-sizing:border-box;color:var(--grey-400);cursor:default;margin-top:.3em;min-width:240px;overflow:hidden;position:absolute;z-index:1}.dropdown-container,.dropdown-search,.dropdown-search button{display:flex;flex-direction:column}.dropdown-entry{display:flex;padding:.33rem 1rem;text-decoration:none}.dropdown-entry:hover{background:var(--grey-100);color:var(--grey-900)}.dropdown-entry input{margin-right:.5em}.dropdown-header{align-items:center;background:var(--grey-100);border-bottom:1px solid var(--grey-200);display:flex;font-size:.833rem;justify-content:space-between;padding:.5rem 1rem;text-align:left}.dropdown-header button{background:none;border:none;color:var(--grey-500);font-size:.833rem;outline:none;padding:0;text-decoration:underline}.dropdown-header button:hover{box-shadow:none;color:var(--grey-900)}.dropdown-footer{background:var(--red-500);border:none;border-radius:0;color:#fff;font-weight:600;outline:none;padding:.5rem;width:100%}.dropdown-footer:hover{background:var(--red-600)}.dropdown-search-button{background:var(--red-500);border:none;border-radius:5px;color:#fff;cursor:pointer;display:inline-block;font-size:1rem;font-weight:600;padding:.5em;text-align:center;text-decoration:none}.dropdown-search-button:hover{background:var(--red-600)}.request-path{max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}main{display:flex;justify-content:center;width:100%}.main-section{width:var(--main-width)}.search-field{border:1px solid var(--grey-400);border-radius:5px;box-sizing:border-box;padding:.5rem}.request-details-data{display:flex;padding:1rem 0}.request-details-actions{align-items:center;display:flex;justify-content:space-between;margin:0;padding:0 0 1rem}.data-item{align-items:flex-start;display:flex;flex-direction:column;list-style:none;margin-right:3rem;padding:0}.data-item small{color:var(--grey-400)}.data-item span{margin:.25rem 0}[class*=request-method-get],[class*=request-status-2]{background:var(--green-400)!important;color:#fff}[class*=request-method-patch],[class*=request-method-put],[class*=request-status-4]{background:var(--yellow-400)!important;color:#fff}[class*=request-method-delete],[class*=request-status-5]{background:var(--red-500)!important;color:#fff}.flamegraph-button button{background:var(--grey-200)}.clear-action button{background:var(--red-500);color:#fff;font-weight:600}.clear-action button:hover{background:var(--red-600)}.profiled-requests-header{align-items:center;display:flex;flex-direction:row;justify-content:space-between}.trace-list{margin:2rem 0;padding:0}.trace{align-items:center;display:flex;justify-content:flex-start;list-style:none;padding:.25em 0}.trace:nth-child(odd){background:var(--grey-100)}.trace .trace-bar{background:linear-gradient(to top right,var(--grey-500),var(--grey-400));cursor:pointer;height:16px;margin:0;padding:0;position:relative}.instantiation-trace .trace-bar{background:linear-gradient(to top right,var(--green-400),var(--green-300))}.sequel-trace .trace-bar{background:linear-gradient(to top right,var(--green-500),var(--green-400))}.controller-trace .trace-bar{background:linear-gradient(to top right,var(--yellow-500),var(--yellow-400))}.render-partial-trace .trace-bar,.render-template-trace .trace-bar{background:linear-gradient(to top right,var(--blue-500),var(--blue-400))}.trace-name{box-sizing:border-box;color:var(--grey-400);font-size:14px;margin:0;overflow:hidden;padding:0 .5em;text-align:right;text-overflow:ellipsis}.trace-payload{margin:0}.sequel-trace-query{background:var(--grey-100);padding:1em}.sequel-trace-binds,.sequel-trace-query{max-height:100px;overflow:auto;white-space:pre-wrap}.sequel-trace-binds{background:var(--grey-50);font-size:12px;margin:0 0 1em;padding:.5em 1em}.trace-table{border:1px hidden var(--border-color);border-collapse:collapse;margin-top:1em;width:100%}.backtrace{align-items:center;background:var(--grey-100);display:flex;flex-direction:row;justify-content:space-between;padding:.5em}.backtrace button{background:none;color:var(--grey-500);height:20px;margin:0;padding:0;transition:color .2s ease-in-out;width:20px}.backtrace button.copied{color:var(--green-400)}.backtrace button svg{height:20px;width:20px}.pagy-nav{align-items:center;display:flex;justify-content:center;padding:1em 0}.pagy-nav>.page.active,.pagy-nav>.page.disabled,.pagy-nav>.page>a{background:#fff;border:1px solid var(--border-color);border-right:none;color:var(--grey-900);cursor:pointer;padding:.5rem 1rem;text-decoration:none}.pagy-nav>.page.prev.disabled,.pagy-nav>.page.prev a{border-radius:5px 0 0 5px}.pagy-nav>.page.next.disabled,.pagy-nav>.page.next a{border-radius:0 5px 5px 0;border-right:1px solid var(--border-color)}.pagy-nav>.page.active{border-color:var(--red-500);color:#fff;cursor:default}.pagy-nav>.page.active,.pagy-nav>.page.active:hover{background:var(--red-500)}.pagy-nav>.page.disabled{color:var(--grey-500);cursor:default}.pagy-nav>.page.disabled:hover{background:#fff}.pagy-nav>.page.active:hover,.pagy-nav>.page.disabled:hover,.pagy-nav>.page>a:hover{background:var(--grey-100)}.page-header{padding:2rem 0}@font-face{font-family:Open Sans;font-style:normal;font-weight:400}@font-face{font-family:Open Sans;font-style:normal;font-weight:600}@font-face{font-family:Open Sans;font-style:normal;font-weight:700}html{--grey-50:#f9fafb;--grey-100:#f3f4f6;--grey-200:#e5e7eb;--grey-400:#9ca3af;--grey-500:#6b7280;--grey-700:#374151;--grey-900:#111827;--red-400:#f87171;--red-500:#ef4444;--red-600:#dc2626;--yellow-400:#fbbf24;--yellow-500:#fbbf24;--yellow-600:#d97706;--yellow-700:#b45309;--green-300:#6ee7b7;--green-400:#34d399;--green-500:#10b981;--blue-400:#60a5fa;--blue-500:#3b82f6;--main-width:1056px;--primary:var(--red-600);--border-color:var(--grey-200);--text-color:var(--grey-900);font-family:Open Sans,monospace;height:100%;width:100%}*,body,html{margin:0;padding:0}body{color:var(--text-color);height:100%;width:100%}.button,button{border:none;border-radius:.25rem;cursor:pointer;display:inline-block;font-size:1rem;padding:.5em;text-align:center;text-decoration:none}.button:disabled,button:disabled{background:var(--grey-200);cursor:not-allowed}button:hover{box-shadow:0 .25rem .25rem 0 var(--grey-50)}button.none{background:none;border:none;outline:none;padding:0}button.none:hover{box-shadow:none}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.hidden{display:none}.flex-row{display:flex;flex-direction:row}.flex-column{display:flex;flex-direction:column}.pill{background:var(--grey-200);border-radius:5px;font-size:.9rem;font-weight:600;letter-spacing:.1rem;margin:.2rem 0;padding:.1rem .4rem}.popover{color:#000;display:flex;flex-direction:column;padding:1em;width:600px}.popover-header{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding-bottom:1em}.popover-description{margin:0;padding:0}.popover-close{background:transparent;color:var(--grey-400);font-size:20px;font-weight:700;padding:0}.popover-body{padding:1em 0}.popover-footer{border-top:1px solid var(--grey-50)}.tippy-box[data-theme~=rmp]{background:#fff;border-radius:5px;box-shadow:8px 0 30px 4px rgba(0,0,0,.2),8px 4px 10px 0 rgba(0,0,0,.05);transition:all .2s cubic-bezier(.19,1,.22,1)}.tippy-box[data-theme~=rmp][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=rmp][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=rmp][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=rmp][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}
1
+ @import url("https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap");.flash{border-radius:5px;margin-top:1rem;padding:1rem}.flash-error{background:var(--red-500);color:#fff}.flash-notice{background:var(--green-400);color:#fff}#wrapper{height:100vh;width:100%}#speedscope-iframe{border:none;height:100%;width:100%}.table{border:1px hidden var(--border-color);border-collapse:collapse;border-radius:5px;box-shadow:0 0 0 1px var(--border-color);table-layout:fixed;width:100%}.table td,.table th{padding:.5rem 1rem}.table thead tr{color:var(--grey-500)}.table tr:nth-child(2n){background:var(--grey-50)}.table thead th{font-weight:400}.table-filter-icon{margin-left:.5rem;width:14px}.table tbody tr:not(.no-row){border:1px solid var(--border-color);color:var(--grey-700);cursor:pointer}.table tbody tr:not(.no-row):hover{background:var(--grey-100)}.request-checkbox{height:1rem;width:1rem;z-index:1}.request-path{max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.search-field{border:1px solid var(--grey-400);border-radius:5px;box-sizing:border-box;padding:.5rem}.profiled-requests-header{align-items:center;display:flex;flex-direction:row;justify-content:space-between}.clear-action button{background:var(--red-500);color:#fff;font-weight:600}.clear-action button:hover{background:var(--red-600)}.profiled-request-details{align-items:center;display:flex;justify-content:space-between;padding-bottom:2rem}.data-item,.request-details-data{display:flex}.data-item{align-items:flex-start;flex-direction:column;list-style:none;margin-right:3rem;padding:0}.data-item small{color:var(--grey-400)}.data-item span{margin:.25rem 0}[class*=request-method-get],[class*=request-status-2]{background:var(--green-400)!important;color:#fff}[class*=request-method-patch],[class*=request-method-put],[class*=request-status-4]{background:var(--yellow-400)!important;color:#fff}[class*=request-method-delete],[class*=request-status-5]{background:var(--red-500)!important;color:#fff}.trace-list{border:1px solid var(--grey-200);border-radius:5px;padding:1rem}.trace-details-table{border:1px hidden var(--border-color);border-collapse:collapse;margin-top:1em;width:100%}.trace-details-table tr th{font-weight:var(--fw-semibold)}.trace-list-header{padding-bottom:1rem}.trace-list-filters,.trace-list-header{align-items:center;display:flex;justify-content:space-between}.trace-list-filters{color:var(--grey-500);width:60%}.button,button{border:none;border-radius:5px;cursor:pointer;display:inline-block;font-size:1rem;padding:.5em;text-align:center;text-decoration:none}.button:disabled,button:disabled{background:var(--grey-200);cursor:not-allowed}.btn-red{background:var(--red-500);color:#fff;font-weight:600;outline:none}.btn-red:hover{background:var(--red-600)}.btn-grey{background:var(--grey-200)}.btn-grey:hover{background:var(--grey-100)}.btn-white{background:#fff;border:1px solid var(--grey-200)}.btn-white:hover{background:var(--grey-100)}button:hover{box-shadow:0 .25rem .25rem 0 var(--grey-50)}button.none{background:none;border:none;outline:none;padding:0}button.none:hover{box-shadow:none}input[type=submit]{border:none;border-radius:5px;cursor:pointer;display:inline-block;font-size:1rem;padding:.5em;text-align:center;text-decoration:none}.header{background:var(--primary);box-shadow:0 0 20px 0 rgba(0,0,0,.2);display:flex;justify-content:center;margin:0;padding:1.5rem 0}.header a{color:#fff;text-decoration:none}.nav{justify-content:space-between;width:var(--main-width)}.home,.nav{display:flex}.home{align-items:center;text-decoration:none}.home-logo{height:64px;width:64px}.home-title{margin:0;padding:0 0 0 1rem}.header-links{font-weight:700;list-style:none;margin:0;padding:0}.header-links,.pagy-nav{align-items:center;display:flex}.pagy-nav{justify-content:center;padding:1em 0}.pagy-nav>.page.active,.pagy-nav>.page.disabled,.pagy-nav>.page>a{background:#fff;border:1px solid var(--border-color);border-right:none;color:var(--grey-900);cursor:pointer;padding:.5rem 1rem;text-decoration:none}.pagy-nav>.page.prev.disabled,.pagy-nav>.page.prev a{border-radius:5px 0 0 5px}.pagy-nav>.page.next.disabled,.pagy-nav>.page.next a{border-radius:0 5px 5px 0;border-right:1px solid var(--border-color)}.pagy-nav>.page.active{border-color:var(--red-500);color:#fff;cursor:default}.pagy-nav>.page.active,.pagy-nav>.page.active:hover{background:var(--red-500)}.pagy-nav>.page.disabled{color:var(--grey-500);cursor:default}.pagy-nav>.page.disabled:hover{background:#fff}.pagy-nav>.page.active:hover,.pagy-nav>.page.disabled:hover,.pagy-nav>.page>a:hover{background:var(--grey-100)}.page-header{padding:2.5rem 0}.page-header h1{font-size:28px}.dropdown-body{padding:.33rem .5rem}.dropdown-search-field{border:1px solid var(--grey-400);border-radius:5px;color:var(--grey-700);padding:.5rem}.dropdown-toggle{align-items:center;color:inherit;display:flex}.dropdown-toggle:hover{color:var(--grey-900)}.dropdown-container{background:#fff;border:1px solid var(--grey-200);border-radius:5px;box-shadow:0 8px 30px rgba(0,0,0,.12);box-sizing:border-box;color:var(--grey-400);cursor:default;margin-top:.3em;min-width:240px;overflow:hidden;position:absolute;z-index:1}.dropdown-container,.dropdown-search,.dropdown-search button{display:flex;flex-direction:column}.dropdown-entry{display:flex;padding:.33rem 1rem;text-decoration:none}.dropdown-entry:hover{background:var(--grey-100);color:var(--grey-900)}.dropdown-entry input{margin-right:.5em}.dropdown-header{align-items:center;background:var(--grey-100);border-bottom:1px solid var(--grey-200);display:flex;font-size:.833rem;justify-content:space-between;padding:.5rem 1rem;text-align:left}.dropdown-header button{background:none;border:none;color:var(--grey-500);font-size:.833rem;outline:none;padding:0;text-decoration:underline}.dropdown-header button:hover{box-shadow:none;color:var(--grey-900)}.dropdown-footer{background:var(--red-500);border:none;border-radius:0;color:#fff;font-weight:600;outline:none;padding:.5rem;width:100%}.dropdown-footer:hover{background:var(--red-600)}.placeholder{align-items:center;display:flex;flex-direction:column;justify-content:center;padding-bottom:2rem;width:100%}.placeholder-image{-webkit-filter:grayscale(1) brightness(2.5);height:30%;width:30%}.placeholder-text{color:var(--grey-400);padding:1rem 0;text-align:center}.placeholder-text h2{padding-bottom:1rem}.placeholder-link,.placeholder-link:visited{color:var(--grey-400)}.placeholder-link:hover{color:var(--grey-900)}.trace{align-items:center;display:flex;justify-content:flex-start;list-style:none;padding:.25em 0}.trace:nth-child(odd){background:var(--grey-100)}.trace .trace-bar{background:linear-gradient(to top right,var(--grey-500),var(--grey-400));cursor:pointer;height:16px;margin:0;padding:0;position:relative}.instantiation-trace .trace-bar{background:linear-gradient(to top right,var(--green-400),var(--green-300))}.sequel-trace .trace-bar{background:linear-gradient(to top right,var(--green-500),var(--green-400))}.controller-trace .trace-bar{background:linear-gradient(to top right,var(--yellow-500),var(--yellow-400))}.render-partial-trace .trace-bar,.render-template-trace .trace-bar{background:linear-gradient(to top right,var(--blue-500),var(--blue-400))}.trace-name{box-sizing:border-box;color:var(--grey-400);font-size:14px;margin:0;overflow:hidden;padding:0 .5em;text-align:right;text-overflow:ellipsis}.trace-payload{margin:0}.sequel-trace-query{background:var(--grey-100);padding:1em}.sequel-trace-binds,.sequel-trace-query{max-height:100px;overflow:auto;white-space:pre-wrap}.sequel-trace-binds{background:var(--grey-50);font-size:12px;margin:0 0 1em;padding:.5em 1em}.backtrace{align-items:center;background:var(--grey-100);display:flex;flex-direction:row;justify-content:space-between;padding:.5em}.backtrace button{background:none;color:var(--grey-500);height:20px;margin:0;overflow:auto;padding:0}.backtrace button svg{height:20px;width:20px}@font-face{font-family:Open Sans;font-style:normal;font-weight:400}@font-face{font-family:Open Sans;font-style:normal;font-weight:600}@font-face{font-family:Open Sans;font-style:normal;font-weight:700}html{--grey-50:#f9fafb;--grey-100:#f3f4f6;--grey-200:#e5e7eb;--grey-400:#9ca3af;--grey-500:#6b7280;--grey-700:#374151;--grey-900:#111827;--red-400:#f87171;--red-500:#ef4444;--red-600:#dc2626;--yellow-400:#fbbf24;--yellow-500:#fbbf24;--yellow-600:#d97706;--yellow-700:#b45309;--green-300:#6ee7b7;--green-400:#34d399;--green-500:#10b981;--blue-400:#60a5fa;--blue-500:#3b82f6;--main-width:1056px;--primary:var(--red-600);--border-color:var(--grey-200);--text-color:var(--grey-900);--fw-normal:400;--fw-semibold:600;--fw-bold:700;font-family:Open Sans,monospace;height:100%;width:100%}*,body,html{margin:0;padding:0}body{color:var(--text-color);height:100%}body,main{width:100%}main{display:flex;justify-content:center}.main-section{width:var(--main-width)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.hidden{display:none}.flex-row{display:flex;flex-direction:row}.flex-column{display:flex;flex-direction:column}.pill{background:var(--grey-200);border-radius:5px;font-size:.9rem;font-weight:600;letter-spacing:.1rem;margin:.2rem 0;padding:.1rem .4rem}.popover{color:#000;display:flex;flex-direction:column;padding:1em;width:600px}.popover-header{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding-bottom:1em}.popover-description{margin:0;padding:0}.popover-close{background:transparent;color:var(--grey-400);font-size:20px;font-weight:700;padding:0}.popover-body{padding:1em 0}.popover-footer{border-top:1px solid var(--grey-50)}.tippy-box[data-theme~=rmp]{background:#fff;border-radius:5px;box-shadow:8px 0 30px 4px rgba(0,0,0,.2),8px 4px 10px 0 rgba(0,0,0,.05);transition:all .2s cubic-bezier(.19,1,.22,1)}.tippy-box[data-theme~=rmp][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=rmp][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=rmp][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=rmp][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}