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
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class TraceSearch < BaseSearch
5
+ option(:name) do |scope, value|
6
+ scope.where(name: value)
7
+ end
8
+
9
+ option(:duration) do |scope, value|
10
+ scope.where('duration > :duration', duration: value)
11
+ end
12
+
13
+ option(:allocations) do |scope, value|
14
+ scope.where('allocations > :allocations', allocations: value)
15
+ end
16
+
17
+ option(:payload) do |scope, value|
18
+ payload_column = DatabaseAdapter.cast_to_text(:payload)
19
+ scope.where("#{payload_column} LIKE ?", "%#{value}%")
20
+ end
21
+
22
+ option(:backtrace) do |scope, value|
23
+ backtrace_column = Adapters::DatabaseAdapter.cast_to_text(:backtrace)
24
+ scope.where("#{backtrace_column} LIKE ?", "%#{value}%")
25
+ end
26
+ end
27
+ end
@@ -12,9 +12,8 @@
12
12
  }) -%>
13
13
  <%= link_to(destroy_all_profiled_requests_url,
14
14
  method: :delete,
15
- class: 'clear-action',
16
15
  data: { confirm: "This will delete all requests. Are you sure?" }) do %>
17
- <%= button_tag('Delete All') %>
16
+ <%= button_tag('Delete All', class: 'btn-red') %>
18
17
  <% end %>
19
18
  </div>
20
19
  </div>
@@ -23,7 +23,7 @@
23
23
  placeholder: 'Search Path',
24
24
  class: 'dropdown-search-field',
25
25
  data: { 'filters-target': 'filter' } %>
26
- <%= form.submit 'Search', class: 'dropdown-search-button', data: { action: 'click->search#submit' } -%>
26
+ <%= form.submit 'Search', class: 'btn-red', data: { action: 'click->search#submit' } -%>
27
27
  <% end %>
28
28
  </div>
29
29
  </div>
@@ -74,7 +74,7 @@
74
74
  </button>
75
75
  </div>
76
76
  </th>
77
- <th style="width: 15%" class="" data-controller="dropdown">
77
+ <th style="width: 15%;" class="" data-controller="dropdown">
78
78
  <button data-action="click->dropdown#toggle click@window->dropdown#hide" data-dropdown-target="button" class="dropdown-toggle none">
79
79
  Media Type <%= inline_svg('filter.svg', class: 'table-filter-icon') %> </button>
80
80
  <div data-controller="checklist" data-dropdown-target="menu" class="dropdown-container hidden">
@@ -97,7 +97,7 @@
97
97
  </button>
98
98
  </div>
99
99
  </th>
100
- <th style="width: 10%" class="text-left" data-controller="dropdown">
100
+ <th style="width: 10%;" class="text-left" data-controller="dropdown">
101
101
  <button data-action="click->dropdown#toggle click@window->dropdown#hide" data-dropdown-target="button" class="dropdown-toggle none">
102
102
  Duration <%= inline_svg('filter.svg', class: 'table-filter-icon') %> </button>
103
103
  <div data-controller="checklist" data-dropdown-target="menu" class="dropdown-container hidden">
@@ -107,12 +107,12 @@
107
107
  Clear filter
108
108
  </button>
109
109
  </div>
110
- <% %w[>100ms >250ms].each do |duration| %>
110
+ <% [100_00, 250_00, 500_00].each do |duration| %>
111
111
  <%= label_tag nil, class: "dropdown-entry" do %>
112
- <%= radio_button_tag 'duration', duration, params.fetch(:duration, []).include?(duration),
112
+ <%= radio_button_tag 'duration', duration.to_s , params.fetch(:duration, []).include?(duration.to_s),
113
113
  class: "",
114
114
  data: { target: "filters.filter" } %>
115
- <%= duration %>
115
+ <%= "> #{duration / 100}ms" %>
116
116
  <% end %>
117
117
  <% end %>
118
118
  <button class="dropdown-footer" data-action="filters#apply">
@@ -120,6 +120,6 @@
120
120
  </button>
121
121
  </div>
122
122
  </th>
123
- <th style="width: 15%" class="text-left">Date</th>
123
+ <th style="width: 15%;" class="text-left">Date</th>
124
124
  </tr>
125
125
  </thead>
@@ -7,8 +7,29 @@
7
7
  <button class="popover-close">x</button>
8
8
  </section>
9
9
  <section class="popover-body">
10
- <%= trace.payload %>
11
- <table class="trace-table">
10
+ <%= trace.content %>
11
+
12
+ <% if trace.backtrace %>
13
+ <section class="popover-footer">
14
+ <div
15
+ class="backtrace"
16
+ data-controller="clipboard"
17
+ data-clipboard-filter=".+?(?=:in)"
18
+ data-clipboard-copied-message="Copied" >
19
+ <pre data-clipboard-target="source"><%= trace.backtrace %></pre>
20
+ <button
21
+ title="Copy to clipboard"
22
+ type="button"
23
+ data-action="clipboard#copy"
24
+ data-clipboard-target="button"
25
+ >
26
+ <%= inline_svg('copy.svg') %>
27
+ </button>
28
+ </div>
29
+ </section>
30
+ <% end %>
31
+
32
+ <table class="trace-details-table">
12
33
  <thead>
13
34
  <tr>
14
35
  <th class="text-left"></th>
@@ -30,26 +51,7 @@
30
51
  </table>
31
52
  </section>
32
53
 
33
- <% if trace.backtrace %>
34
- <section class="popover-footer">
35
- <div
36
- class="backtrace"
37
- data-controller="clipboard"
38
- data-clipboard-filter=".+?(?=:in)"
39
- data-clipboard-copied-class="copied"
40
- >
41
- <pre data-clipboard-target="source"><%= trace.backtrace %></pre>
42
- <button
43
- title="Copy to clipboard"
44
- type="button"
45
- data-action="clipboard#copy"
46
- data-clipboard-target="button"
47
- >
48
- <%= inline_svg('copy.svg') %>
49
- </button>
50
- </div>
51
- </section>
52
- <% end %>
54
+
53
55
  </div>
54
56
  </div>
55
57
  </li>
@@ -0,0 +1,12 @@
1
+ <section class="trace-list">
2
+ <%= render "rails_mini_profiler/profiled_requests/show/trace_list_header" %>
3
+ <% if @traces.empty? %>
4
+ <%= render "rails_mini_profiler/profiled_requests/show/trace_list_placeholder" %>
5
+ <% else %>
6
+ <ol>
7
+ <% @traces.each do |trace| %>
8
+ <%= render "rails_mini_profiler/profiled_requests/show/trace", trace: trace %>
9
+ <% end %>
10
+ </ol>
11
+ <% end %>
12
+ </section>
@@ -0,0 +1,87 @@
1
+ <div class="trace-list-header" data-controller="filters">
2
+ <div class="trace-list-filters">
3
+ <%= form_with url: profiled_request_url(@profiled_request.id), method: :get do |form| %>
4
+ <%= form.search_field :payload,
5
+ value: params[:payload],
6
+ placeholder: 'Search Traces...',
7
+ class: 'search-field',
8
+ data: { 'filters-target': 'filter' } %>
9
+ <% end %>
10
+ <div data-controller="dropdown">
11
+ <button data-action="click->dropdown#toggle click@window->dropdown#hide" data-dropdown-target="button" class="dropdown-toggle none" >
12
+ Type <%= inline_svg('filter.svg', class: 'table-filter-icon') %> </button>
13
+ <div data-controller="checklist" data-dropdown-target="menu" class="dropdown-container hidden">
14
+ <div class="dropdown-header">
15
+ Select Type...
16
+ <button class="clear-filters" data-action="checklist#checkNone">
17
+ Clear filter
18
+ </button>
19
+ </div>
20
+ <% trace_names = %w[process_action.action_controller sql.active_record instantiation.active_record render_template.action_view render_partial.action_view] %>
21
+ <% trace_names.each do |name| %>
22
+ <%= label_tag nil, class: "dropdown-entry" do %>
23
+ <%= check_box_tag 'name[]', name, params.fetch(:name, []).include?(name),
24
+ class: "",
25
+ data: { 'filters-target': "filter" } %>
26
+ <%= trace_display_name(name) %>
27
+ <% end %>
28
+ <% end %>
29
+ <button class="dropdown-footer" data-action="filters#apply">
30
+ Apply
31
+ </button>
32
+ </div>
33
+ </div>
34
+
35
+ <div data-controller="dropdown">
36
+ <button data-action="click->dropdown#toggle click@window->dropdown#hide" data-dropdown-target="button" class="dropdown-toggle none">
37
+ Duration <%= inline_svg('filter.svg', class: 'table-filter-icon') %> </button>
38
+ <div data-controller="checklist" data-dropdown-target="menu" class="dropdown-container hidden">
39
+ <div class="dropdown-header">
40
+ Select Duration...
41
+ <button class="clear-filters" data-action="checklist#checkNone filters#apply">
42
+ Clear filter
43
+ </button>
44
+ </div>
45
+ <% [100_00, 250_00, 500_00].each do |duration| %>
46
+ <%= label_tag nil, class: "dropdown-entry" do %>
47
+ <%= radio_button_tag 'duration', duration.to_s, params.fetch(:duration, []).include?(duration.to_s),
48
+ class: "",
49
+ data: { target: "filters.filter" } %>
50
+ <%= "> #{formatted_duration(duration)}ms" %>
51
+ <% end %>
52
+ <% end %>
53
+ <button class="dropdown-footer" data-action="filters#apply">
54
+ Apply
55
+ </button>
56
+ </div>
57
+ </div>
58
+
59
+ <div data-controller="dropdown">
60
+ <button data-action="click->dropdown#toggle click@window->dropdown#hide" data-dropdown-target="button" class="dropdown-toggle none">
61
+ Allocations <%= inline_svg('filter.svg', class: 'table-filter-icon') %> </button>
62
+ <div data-controller="checklist" data-dropdown-target="menu" class="dropdown-container hidden">
63
+ <div class="dropdown-header">
64
+ Select Allocation...
65
+ <button class="clear-filters" data-action="filters#apply">
66
+ Clear filter
67
+ </button>
68
+ </div>
69
+ <!-- https://twitter.com/nateberkopec/status/1442648442149367809-->
70
+ <% [10_000, 100_000, 1_000_000, 10_000_000].each do |allocations| %>
71
+ <%= label_tag nil, class: "dropdown-entry" do %>
72
+ <%= radio_button_tag 'allocations', allocations.to_s, params.fetch(:allocations, []).include?(allocations.to_s),
73
+ class: "",
74
+ data: { target: "filters.filter" } %>
75
+ <%= "> #{formatted_allocations(allocations)}" %>
76
+ <% end %>
77
+ <% end %>
78
+ <button class="dropdown-footer" data-action="filters#apply">
79
+ Apply
80
+ </button>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ <%= link_to(profiled_request_url(@profiled_request.id)) do %>
85
+ <%= button_tag('Clear All', class: 'btn-red') %>
86
+ <% end %>
87
+ </div>
@@ -0,0 +1,12 @@
1
+ <tr class="no-row">
2
+ <td colspan="100%">
3
+ <div class="placeholder">
4
+ <%= inline_svg('logo_variant.svg', class: 'placeholder-image') %>
5
+ <div class="placeholder-text">
6
+ <h2>No Traces found!</h2>
7
+ <p>Modify filters or reload this page</p>
8
+ </div>
9
+ <%= link_to('Clear Filters', profiled_request_url(@profiled_request.id), class: 'placeholder-link') %>
10
+ </div>
11
+ </td>
12
+ </tr>
@@ -1,6 +1,8 @@
1
1
  <section class="page-header">
2
2
  <h1> <%= @profiled_request.request_path %> </h1>
3
+ </section>
3
4
 
5
+ <div class="profiled-request-details">
4
6
  <ul class="request-details-data">
5
7
  <li class="data-item">
6
8
  <small>Method</small>
@@ -18,23 +20,8 @@
18
20
  <small>Allocations</small>
19
21
  <span><%= @profiled_request.allocations %></span>
20
22
  </li>
21
-
22
23
  </ul>
23
-
24
- </section>
25
-
26
- <section class="request-details-actions">
27
- <%= form_with id: 'trace-form', url: profiled_request_url(@profiled_request.id), method: :get do |form| %>
28
- <%= form.search_field :search, id: 'trace-search', placeholder: 'Search Traces...', class: 'search-field' %>
29
- <% end %>
30
24
  <%= @profiled_request.flamegraph_button %>
31
- </section>
25
+ </div>
32
26
 
33
- <section>
34
- <h2>Traces</h2>
35
- <ol class="trace-list">
36
- <% @traces.each do |trace| %>
37
- <%= render "rails_mini_profiler/profiled_requests/shared/trace", trace: trace %>
38
- <% end %>
39
- </ol>
40
- </section>
27
+ <%= render "rails_mini_profiler/profiled_requests/show/trace_list" %>
@@ -2,7 +2,7 @@
2
2
 
3
3
  class CreateRmp < ActiveRecord::Migration[6.0]
4
4
  def change
5
- create_table :rmp_profiled_requests do |t|
5
+ create_table :rmp_profiled_requests, charset: 'utf8' do |t|
6
6
  t.string :user_id
7
7
  t.bigint :start
8
8
  t.bigint :finish
@@ -23,7 +23,7 @@ class CreateRmp < ActiveRecord::Migration[6.0]
23
23
  t.index :created_at
24
24
  end
25
25
 
26
- create_table :rmp_traces do |t|
26
+ create_table :rmp_traces, charset: 'utf8' do |t|
27
27
  t.belongs_to :rmp_profiled_request, null: false, foreign_key: true
28
28
  t.string :name
29
29
  t.bigint :start
@@ -36,7 +36,7 @@ class CreateRmp < ActiveRecord::Migration[6.0]
36
36
  t.timestamps
37
37
  end
38
38
 
39
- create_table :rmp_flamegraphs do |t|
39
+ create_table :rmp_flamegraphs, charset: 'utf8' do |t|
40
40
  t.belongs_to :rmp_profiled_request, null: false, foreign_key: true
41
41
  t.binary :data
42
42
 
@@ -22,6 +22,7 @@ RailsMiniProfiler.configure do |config|
22
22
  # Configure the Rails Mini Profiler User Interface
23
23
  # config.ui.badge_enabled = true
24
24
  # config.ui.badge_position = 'top-left'
25
+ # config.ui.base_controller = ApplicationController
25
26
  # config.ui.page_size = 25
26
27
  # config.ui.webpacker_enabled = true
27
28
 
@@ -23,30 +23,39 @@ module RailsMiniProfiler
23
23
  #
24
24
  # @return [ResponseWrapper] The modified response
25
25
  def render
26
- content_type = @original_response.headers['Content-Type']
27
- return @original_response unless content_type =~ %r{text/html}
28
-
29
- return @original_response unless @configuration.ui.badge_enabled
26
+ return @original_response unless render_badge?
30
27
 
31
- modified_response = Rack::Response.new([], @original_response.status, @original_response.headers)
28
+ modified_response = ResponseWrapper.new([], @original_response.status, @original_response.headers)
32
29
  modified_response.write(modified_body)
33
30
  modified_response.finish
34
31
 
35
- response = @original_response.response
36
- response.close if response.respond_to?(:close)
32
+ @original_response.close if @original_response.respond_to?(:close)
37
33
 
38
- ResponseWrapper.new(@original_response.status,
39
- @original_response.headers,
40
- modified_response)
34
+ modified_response
41
35
  end
42
36
 
43
37
  private
44
38
 
39
+ def render_badge?
40
+ content_type = @original_response.headers['Content-Type']
41
+ unless content_type =~ %r{text/html}
42
+ RailsMiniProfiler.logger.debug("badge not rendered, response has content type #{content_type}")
43
+ return false
44
+ end
45
+
46
+ unless @configuration.ui.badge_enabled
47
+ RailsMiniProfiler.logger.debug('badge not rendered, disabled in configuration')
48
+ return false
49
+ end
50
+
51
+ true
52
+ end
53
+
45
54
  # Modify the body of the original response
46
55
  #
47
56
  # @return String The modified body
48
57
  def modified_body
49
- body = @original_response.response.body
58
+ body = @original_response.body
50
59
  index = body.rindex(%r{</body>}i) || body.rindex(%r{</html>}i)
51
60
  if index
52
61
  body.dup.insert(index, badge_content)
@@ -9,6 +9,8 @@ module RailsMiniProfiler
9
9
  # @!attribute badge_position
10
10
  # @see Badge
11
11
  # @return [String] the position of the interactive HTML badge
12
+ # @!attribute base_controller
13
+ # @return [class] the controller the UI controllers should inherit from
12
14
  # @!attribute page_size
13
15
  # @return [Integer] how many items to render per page in list views
14
16
  # @!attribute webpacker_enabled
@@ -36,6 +38,8 @@ module RailsMiniProfiler
36
38
  :page_size,
37
39
  :webpacker_enabled
38
40
 
41
+ attr_writer :base_controller
42
+
39
43
  def initialize(**kwargs)
40
44
  defaults!
41
45
  kwargs.each { |key, value| instance_variable_set("@#{key}", value) }
@@ -45,8 +49,30 @@ module RailsMiniProfiler
45
49
  def defaults!
46
50
  @badge_enabled = true
47
51
  @badge_position = 'top-left'
52
+ # We must not set base controller during when the app loads, aka during autoload time, as we are loading
53
+ # constants. Rather, we only load the base controller constants when any engine controllers are first initialized
54
+ # and call #base_controller
55
+ @base_controller = nil
48
56
  @page_size = 25
49
57
  @webpacker_enabled = true
50
58
  end
59
+
60
+ def base_controller
61
+ @base_controller ||= default_base_controller
62
+ end
63
+
64
+ def default_base_controller
65
+ app_controller_exists = class_exists?('::ApplicationController')
66
+ return ::ApplicationController if app_controller_exists && ::ApplicationController < ActionController::Base
67
+
68
+ ActionController::Base
69
+ end
70
+
71
+ def class_exists?(class_name)
72
+ klass = Module.const_get(class_name)
73
+ klass.is_a?(Class)
74
+ rescue NameError
75
+ false
76
+ end
51
77
  end
52
78
  end
@@ -18,6 +18,8 @@ module RailsMiniProfiler
18
18
  # @return [Array<String>] a list of regex patterns for paths to skip
19
19
  # @!attribute storage
20
20
  # @return [Storage] the storage configuration
21
+ # @!attribute tracers
22
+ # @return [Array<Symbol>] the list of enabled tracers
21
23
  # @!attribute ui
22
24
  # @return [UserInterface] the ui configuration
23
25
  # @!attribute user_provider
@@ -30,6 +32,7 @@ module RailsMiniProfiler
30
32
  :flamegraph_sample_rate,
31
33
  :skip_paths,
32
34
  :storage,
35
+ :tracers,
33
36
  :ui,
34
37
  :user_provider
35
38
 
@@ -46,6 +49,7 @@ module RailsMiniProfiler
46
49
  @logger = RailsMiniProfiler::Logger.new(Rails.logger)
47
50
  @skip_paths = []
48
51
  @storage = Storage.new
52
+ @tracers = %i[controller instantiation sequel view rmp]
49
53
  @ui = UserInterface.new
50
54
  @user_provider = proc { |env| Rack::Request.new(env).ip }
51
55
  end
@@ -11,17 +11,12 @@ module RailsMiniProfiler
11
11
  def record(&block)
12
12
  return block.call unless enabled?
13
13
 
14
- sample_rate = @configuration.flamegraph_sample_rate
15
14
  if StackProf.running?
16
15
  RailsMiniProfiler.logger.error('Stackprof is already running, cannot record Flamegraph')
17
16
  return block.call
18
17
  end
19
18
 
20
- result = nil
21
- flamegraph = StackProf.run(mode: :wall, raw: true, aggregate: false, interval: (sample_rate * 1000).to_i) do
22
- result = block.call
23
- end
24
-
19
+ flamegraph, result = record_flamegraph(block)
25
20
  unless flamegraph
26
21
  RailsMiniProfiler.logger.error('Failed to record Flamegraph, possibly due to concurrent requests')
27
22
  return result
@@ -33,6 +28,15 @@ module RailsMiniProfiler
33
28
 
34
29
  private
35
30
 
31
+ def record_flamegraph(block)
32
+ sample_rate = @configuration.flamegraph_sample_rate
33
+ result = nil
34
+ flamegraph = StackProf.run(mode: :wall, raw: true, aggregate: false, interval: (sample_rate * 1000).to_i) do
35
+ result = block.call
36
+ end
37
+ [flamegraph, result]
38
+ end
39
+
36
40
  def enabled?
37
41
  defined?(StackProf) && StackProf.respond_to?(:run) && config_enabled?
38
42
  end
@@ -5,21 +5,25 @@ module RailsMiniProfiler
5
5
  def initialize(app)
6
6
  @app = app
7
7
  @config = RailsMiniProfiler.configuration
8
- Tracing::Subscriptions.setup! { |trace| track_trace(trace) }
8
+ @registry = Tracers::Registry.setup!(@config)
9
+ Tracers::Subscriptions.setup!(@registry.tracers.keys) { |trace| track_trace(trace) }
10
+ @trace_factory = Tracers::TraceFactory.new(@registry)
9
11
  end
10
12
 
11
13
  def call(env)
12
- request = RequestWrapper.new(env: env)
14
+ request = RequestWrapper.new(env)
13
15
  request_context = RequestContext.new(request)
14
16
  return @app.call(env) unless Guard.new(request_context).profile?
15
17
 
16
- request_context.profiled_request = ProfiledRequest.new
17
18
  result = with_tracing(request_context) { profile(request_context) }
18
19
  return result unless request_context.authorized?
19
20
 
20
- request_context.response = ResponseWrapper.new(*result)
21
+ status, headers, body = result
22
+ request_context.response = ResponseWrapper.new(body, status, headers)
21
23
  complete!(request_context)
22
- request_context.saved? ? render_response(request_context) : result
24
+ request_context.saved? ? render_response(request_context).to_a : result
25
+ ensure
26
+ User.current_user = nil
23
27
  end
24
28
 
25
29
  def traces
@@ -33,14 +37,13 @@ module RailsMiniProfiler
33
37
  def track_trace(event)
34
38
  return if traces.nil?
35
39
 
36
- trace = RailsMiniProfiler::Tracing::TraceFactory.create(event)
37
- traces.append(trace) unless trace.is_a?(RailsMiniProfiler::Tracing::NullTrace)
40
+ trace = @trace_factory.create(event)
41
+ traces.append(trace) unless trace.is_a?(RailsMiniProfiler::Tracers::NullTrace)
38
42
  end
39
43
 
40
44
  private
41
45
 
42
46
  def complete!(request_context)
43
- request_context.complete_profiling!
44
47
  request_context.save_results!
45
48
  true
46
49
  rescue ActiveRecord::ActiveRecordError => e
@@ -52,8 +55,7 @@ module RailsMiniProfiler
52
55
  redirect = Redirect.new(request_context).render
53
56
  return redirect if redirect
54
57
 
55
- modified_response = Badge.new(request_context).render
56
- [modified_response.status, modified_response.headers, modified_response.response]
58
+ Badge.new(request_context).render
57
59
  end
58
60
 
59
61
  def profile(request_context)
@@ -10,7 +10,7 @@ module RailsMiniProfiler
10
10
  # @return [RequestWrapper] the request as sent to the application
11
11
  # @!attribute response
12
12
  # @return [ResponseWrapper] the response as rendered by the application
13
- # @!attribute profiled_request
13
+ # # @!attribute profiled_request
14
14
  # @return [ProfiledRequest] the profiling data as gathered during profiling
15
15
  # @!attribute traces
16
16
  # @return [Array<Models::Trace>] trace wrappers gathered during profiling
@@ -19,8 +19,10 @@ module RailsMiniProfiler
19
19
  class RequestContext
20
20
  attr_reader :request
21
21
 
22
- attr_accessor :response, :profiled_request, :traces, :flamegraph
22
+ attr_accessor :response, :traces, :flamegraph, :profiled_request
23
23
 
24
+ # Create a new request context
25
+ #
24
26
  # @param request [RequestWrapper] the request as sent to the application
25
27
  def initialize(request)
26
28
  @request = request
@@ -36,39 +38,41 @@ module RailsMiniProfiler
36
38
  @authorized ||= User.get(@env).present?
37
39
  end
38
40
 
39
- # Completes profiling, setting all data and preparing for saving it.
40
- def complete_profiling!
41
- profiled_request.user_id = User.current_user
42
- profiled_request.request = @request
43
- profiled_request.response = @response
44
- total_time = traces.find { |trace| trace.name == 'rails_mini_profiler.total_time' }
45
- profiled_request.total_time = total_time
46
- @complete = true
47
- end
48
-
49
41
  # Save profiling data in the database.
50
42
  #
51
43
  # This will store the profiled request, as well as any attached traces and Flamgraph.
52
44
  def save_results!
53
45
  ActiveRecord::Base.transaction do
46
+ profiled_request = build_profiled_request
54
47
  profiled_request.flamegraph = RailsMiniProfiler::Flamegraph.new(data: flamegraph) if flamegraph.present?
55
48
  profiled_request.save
56
- insert_traces unless traces.empty?
49
+ insert_traces(profiled_request) unless traces.empty?
50
+ @profiled_request = profiled_request
57
51
  end
58
52
  @saved = true
59
53
  end
60
54
 
61
- def complete?
62
- @complete
63
- end
64
-
55
+ # Check if profiling results have been saved
56
+ #
57
+ # @return [Boolean] true if profiling results have been saved
65
58
  def saved?
66
59
  @saved
67
60
  end
68
61
 
69
62
  private
70
63
 
71
- def insert_traces
64
+ def build_profiled_request
65
+ new_profiled_request = ProfiledRequest.new
66
+ new_profiled_request.user_id = User.current_user
67
+ new_profiled_request.request = @request
68
+ new_profiled_request.response = @response
69
+ total_time = traces.find { |trace| trace.name == 'rails_mini_profiler.total_time' }
70
+ new_profiled_request.total_time = total_time
71
+ new_profiled_request
72
+ end
73
+
74
+ # We insert multiple at once for performance reasons.
75
+ def insert_traces(profiled_request)
72
76
  return if traces.empty?
73
77
 
74
78
  timestamp = Time.zone.now