rails_mini_profiler 0.4.0 → 0.7.1
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.
- checksums.yaml +4 -4
- data/README.md +48 -34
- data/app/adapters/rails_mini_profiler/database_adapter.rb +16 -0
- data/app/controllers/rails_mini_profiler/application_controller.rb +9 -15
- data/app/controllers/rails_mini_profiler/profiled_requests_controller.rb +44 -14
- data/app/helpers/rails_mini_profiler/application_helper.rb +1 -1
- data/app/helpers/rails_mini_profiler/profiled_requests_helper.rb +10 -0
- data/app/javascript/images/check.svg +3 -0
- data/app/javascript/images/chevron.svg +3 -0
- data/app/javascript/images/copy.svg +6 -0
- data/app/javascript/images/filter.svg +1 -0
- data/app/javascript/images/logo_variant.svg +2 -2
- data/app/javascript/images/search.svg +4 -5
- data/app/javascript/js/checklist_controller.js +48 -0
- data/app/javascript/js/clipboard_controller.js +53 -0
- data/app/javascript/js/enable_controller.js +23 -0
- data/app/javascript/js/filter_controller.js +48 -0
- data/app/javascript/js/search_controller.js +18 -0
- data/app/javascript/js/select_controller.js +52 -0
- data/app/javascript/packs/rails-mini-profiler.js +69 -53
- data/app/javascript/stylesheets/components/buttons.scss +59 -0
- data/app/javascript/stylesheets/components/dropdown.scss +103 -0
- data/app/javascript/stylesheets/components/input.scss +10 -0
- data/app/javascript/stylesheets/{navbar.scss → components/navbar.scss} +7 -13
- data/app/javascript/stylesheets/components/page_header.scss +7 -0
- data/app/javascript/stylesheets/components/pagination.scss +14 -13
- data/app/javascript/stylesheets/components/placeholder.scss +36 -0
- data/app/javascript/stylesheets/components/profiled_request_table.scss +55 -0
- data/app/javascript/stylesheets/components/trace.scss +93 -0
- data/app/javascript/stylesheets/flamegraph.scss +3 -2
- data/app/javascript/stylesheets/flashes.scss +3 -5
- data/app/javascript/stylesheets/profiled_requests.scss +12 -161
- data/app/javascript/stylesheets/rails-mini-profiler.scss +81 -66
- data/app/javascript/stylesheets/traces.scss +52 -56
- data/app/models/rails_mini_profiler/profiled_request.rb +1 -1
- data/app/models/rails_mini_profiler/trace.rb +0 -15
- data/app/presenters/rails_mini_profiler/controller_trace_presenter.rb +10 -2
- data/app/presenters/rails_mini_profiler/instantiation_trace_presenter.rb +15 -3
- data/app/presenters/rails_mini_profiler/profiled_request_presenter.rb +10 -17
- data/app/presenters/rails_mini_profiler/render_partial_trace_presenter.rb +6 -2
- data/app/presenters/rails_mini_profiler/render_template_trace_presenter.rb +6 -2
- data/app/presenters/rails_mini_profiler/sequel_trace_presenter.rb +16 -4
- data/app/presenters/rails_mini_profiler/trace_presenter.rb +10 -8
- data/app/search/rails_mini_profiler/base_search.rb +67 -0
- data/app/search/rails_mini_profiler/profiled_request_search.rb +33 -0
- data/app/search/rails_mini_profiler/trace_search.rb +27 -0
- data/app/views/rails_mini_profiler/badge.html.erb +2 -2
- data/app/views/rails_mini_profiler/profiled_requests/index.html.erb +8 -58
- data/app/views/rails_mini_profiler/profiled_requests/shared/header/_header.erb +19 -0
- data/app/views/rails_mini_profiler/profiled_requests/shared/table/_placeholder.erb +12 -0
- data/app/views/rails_mini_profiler/profiled_requests/shared/table/_table.erb +14 -0
- data/app/views/rails_mini_profiler/profiled_requests/shared/table/_table_head.erb +125 -0
- data/app/views/rails_mini_profiler/profiled_requests/shared/table/_table_row.erb +21 -0
- data/app/views/rails_mini_profiler/profiled_requests/{shared → show}/_trace.html.erb +24 -7
- data/app/views/rails_mini_profiler/profiled_requests/show/_trace_list.erb +12 -0
- data/app/views/rails_mini_profiler/profiled_requests/show/_trace_list_header.erb +87 -0
- data/app/views/rails_mini_profiler/profiled_requests/show/_trace_list_placeholder.erb +12 -0
- data/app/views/rails_mini_profiler/profiled_requests/show.html.erb +5 -18
- data/app/views/rails_mini_profiler/shared/_head.erb +1 -1
- data/lib/generators/rails_mini_profiler/templates/rails_mini_profiler.rb.erb +2 -0
- data/lib/rails_mini_profiler/badge.rb +21 -12
- data/lib/rails_mini_profiler/configuration/user_interface.rb +31 -1
- data/lib/rails_mini_profiler/configuration.rb +7 -0
- data/lib/rails_mini_profiler/engine.rb +12 -8
- data/lib/rails_mini_profiler/flamegraph_guard.rb +10 -6
- data/lib/rails_mini_profiler/guard.rb +19 -1
- data/lib/rails_mini_profiler/middleware.rb +12 -10
- data/lib/rails_mini_profiler/request_context.rb +22 -18
- data/lib/rails_mini_profiler/request_wrapper.rb +12 -55
- data/lib/rails_mini_profiler/response_wrapper.rb +21 -17
- data/lib/rails_mini_profiler/tracers/controller_tracer.rb +29 -0
- data/lib/rails_mini_profiler/tracers/instantiation_tracer.rb +17 -0
- data/lib/rails_mini_profiler/tracers/null_trace.rb +7 -0
- data/lib/rails_mini_profiler/tracers/registry.rb +76 -0
- data/lib/rails_mini_profiler/tracers/rmp_tracer.rb +17 -0
- data/lib/rails_mini_profiler/tracers/sequel_tracer.rb +51 -0
- data/lib/rails_mini_profiler/tracers/sequel_tracker.rb +40 -0
- data/lib/rails_mini_profiler/tracers/subscriptions.rb +28 -0
- data/lib/rails_mini_profiler/{models → tracers}/trace.rb +11 -3
- data/lib/rails_mini_profiler/tracers/trace_factory.rb +27 -0
- data/lib/rails_mini_profiler/tracers/tracer.rb +45 -0
- data/lib/rails_mini_profiler/tracers/view_tracer.rb +29 -0
- data/lib/rails_mini_profiler/tracers.rb +12 -83
- data/lib/rails_mini_profiler/user.rb +1 -1
- data/lib/rails_mini_profiler/version.rb +1 -1
- data/lib/rails_mini_profiler.rb +4 -8
- data/vendor/assets/images/check.svg +3 -0
- data/vendor/assets/images/chevron.svg +3 -0
- data/vendor/assets/images/copy.svg +6 -0
- data/vendor/assets/images/filter.svg +1 -0
- data/vendor/assets/images/logo_variant.svg +2 -2
- data/vendor/assets/images/search.svg +4 -5
- data/vendor/assets/javascripts/rails-mini-profiler.css +1 -1
- data/vendor/assets/javascripts/rails-mini-profiler.js +1 -1
- metadata +58 -13
- data/app/models/rails_mini_profiler/controller_trace.rb +0 -37
- data/app/models/rails_mini_profiler/instantiation_trace.rb +0 -37
- data/app/models/rails_mini_profiler/render_partial_trace.rb +0 -37
- data/app/models/rails_mini_profiler/render_template_trace.rb +0 -37
- data/app/models/rails_mini_profiler/rmp_trace.rb +0 -35
- data/app/models/rails_mini_profiler/sequel_trace.rb +0 -37
@@ -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
|
-
<section class="
|
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
|
-
</
|
25
|
+
</div>
|
32
26
|
|
33
|
-
|
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" %>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<%= csrf_meta_tags %>
|
4
4
|
<%= csp_meta_tag %>
|
5
5
|
|
6
|
-
<% if defined?(Webpacker::Engine) %>
|
6
|
+
<% if defined?(Webpacker::Engine) && RailsMiniProfiler.configuration.ui.webpacker_enabled %>
|
7
7
|
<%= javascript_pack_tag "rails-mini-profiler" %>
|
8
8
|
<%= stylesheet_pack_tag 'rails-mini-profiler' %>
|
9
9
|
<% else %>
|
@@ -22,7 +22,9 @@ 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
|
27
|
+
# config.ui.webpacker_enabled = true
|
26
28
|
|
27
29
|
# Customize how users are detected
|
28
30
|
config.user_provider = proc { |env| Rack::Request.new(env).ip }
|
@@ -23,30 +23,39 @@ module RailsMiniProfiler
|
|
23
23
|
#
|
24
24
|
# @return [ResponseWrapper] The modified response
|
25
25
|
def render
|
26
|
-
|
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 =
|
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
|
-
|
36
|
-
response.close if response.respond_to?(:close)
|
32
|
+
@original_response.close if @original_response.respond_to?(:close)
|
37
33
|
|
38
|
-
|
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.
|
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)
|
@@ -59,7 +68,7 @@ module RailsMiniProfiler
|
|
59
68
|
#
|
60
69
|
# @return String The badge HTML content to be injected
|
61
70
|
def badge_content
|
62
|
-
html =
|
71
|
+
html = File.read(File.expand_path('../../app/views/rails_mini_profiler/badge.html.erb', __dir__))
|
63
72
|
@position = css_position
|
64
73
|
template = ERB.new(html)
|
65
74
|
template.result(binding)
|
@@ -9,8 +9,12 @@ 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
|
16
|
+
# @!attribute webpacker_enabled
|
17
|
+
# @return [Boolean] if webpacker assets should be used. Disable to fall back to the asset pipeline
|
14
18
|
class UserInterface
|
15
19
|
class << self
|
16
20
|
# Construct a new UI configuration instance
|
@@ -31,7 +35,10 @@ module RailsMiniProfiler
|
|
31
35
|
|
32
36
|
attr_accessor :badge_enabled,
|
33
37
|
:badge_position,
|
34
|
-
:page_size
|
38
|
+
:page_size,
|
39
|
+
:webpacker_enabled
|
40
|
+
|
41
|
+
attr_writer :base_controller
|
35
42
|
|
36
43
|
def initialize(**kwargs)
|
37
44
|
defaults!
|
@@ -42,7 +49,30 @@ module RailsMiniProfiler
|
|
42
49
|
def defaults!
|
43
50
|
@badge_enabled = true
|
44
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
|
45
56
|
@page_size = 25
|
57
|
+
@webpacker_enabled = true
|
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
|
46
76
|
end
|
47
77
|
end
|
48
78
|
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'rails_mini_profiler/configuration/storage'
|
4
|
+
require 'rails_mini_profiler/configuration/user_interface'
|
5
|
+
|
3
6
|
module RailsMiniProfiler
|
4
7
|
# The main Rails Mini Profiler configuration object
|
5
8
|
#
|
@@ -15,6 +18,8 @@ module RailsMiniProfiler
|
|
15
18
|
# @return [Array<String>] a list of regex patterns for paths to skip
|
16
19
|
# @!attribute storage
|
17
20
|
# @return [Storage] the storage configuration
|
21
|
+
# @!attribute tracers
|
22
|
+
# @return [Array<Symbol>] the list of enabled tracers
|
18
23
|
# @!attribute ui
|
19
24
|
# @return [UserInterface] the ui configuration
|
20
25
|
# @!attribute user_provider
|
@@ -27,6 +32,7 @@ module RailsMiniProfiler
|
|
27
32
|
:flamegraph_sample_rate,
|
28
33
|
:skip_paths,
|
29
34
|
:storage,
|
35
|
+
:tracers,
|
30
36
|
:ui,
|
31
37
|
:user_provider
|
32
38
|
|
@@ -43,6 +49,7 @@ module RailsMiniProfiler
|
|
43
49
|
@logger = RailsMiniProfiler::Logger.new(Rails.logger)
|
44
50
|
@skip_paths = []
|
45
51
|
@storage = Storage.new
|
52
|
+
@tracers = %i[controller instantiation sequel view rmp]
|
46
53
|
@ui = UserInterface.new
|
47
54
|
@user_provider = proc { |env| Rack::Request.new(env).ip }
|
48
55
|
end
|
@@ -11,14 +11,6 @@ module RailsMiniProfiler
|
|
11
11
|
app.middleware.use(RailsMiniProfiler::Middleware)
|
12
12
|
end
|
13
13
|
|
14
|
-
initializer 'rails_mini_profiler.assets.precompile', group: :all do |app|
|
15
|
-
app.config.assets.precompile += %w[
|
16
|
-
rails_mini_profiler.js
|
17
|
-
rails_mini_profiler/application.css
|
18
|
-
vendor/assets/images
|
19
|
-
]
|
20
|
-
end
|
21
|
-
|
22
14
|
config.generators do |g|
|
23
15
|
g.test_framework :rspec
|
24
16
|
end
|
@@ -26,5 +18,17 @@ module RailsMiniProfiler
|
|
26
18
|
initializer 'rails_mini_profiler_add_static assets' do |app|
|
27
19
|
app.middleware.insert_before(ActionDispatch::Static, ActionDispatch::Static, "#{root}/public")
|
28
20
|
end
|
21
|
+
|
22
|
+
# If sprockets is not being used then there is no need to hook into asset compilation. Calling config.assets
|
23
|
+
# without Sprockets installed breaks compilation.
|
24
|
+
if defined?(Sprockets::Rails)
|
25
|
+
initializer 'rails_mini_profiler.assets.precompile', group: :all do |app|
|
26
|
+
app.config.assets.precompile += %w[
|
27
|
+
rails_mini_profiler.js
|
28
|
+
rails_mini_profiler/application.css
|
29
|
+
vendor/assets/images
|
30
|
+
]
|
31
|
+
end
|
32
|
+
end
|
29
33
|
end
|
30
34
|
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 =
|
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
|
@@ -34,7 +34,9 @@ module RailsMiniProfiler
|
|
34
34
|
def ignored_path?
|
35
35
|
return true if /#{Engine.routes.find_script_name({})}/.match?(@request.path)
|
36
36
|
|
37
|
-
return true if
|
37
|
+
return true if asset_path?
|
38
|
+
|
39
|
+
return true if actioncable_request?
|
38
40
|
|
39
41
|
ignored_paths = @configuration.skip_paths
|
40
42
|
return true if Regexp.union(ignored_paths).match?(@request.path)
|
@@ -42,6 +44,22 @@ module RailsMiniProfiler
|
|
42
44
|
false
|
43
45
|
end
|
44
46
|
|
47
|
+
# Is the current request an asset request, e.g. to webpacker packs or assets?
|
48
|
+
#
|
49
|
+
# @return [Boolean] if the request path matches packs or assets
|
50
|
+
def asset_path?
|
51
|
+
%r{^/packs}.match?(@request.path) || %r{^/assets}.match?(@request.path)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Is the current request an actioncable ping
|
55
|
+
#
|
56
|
+
# @return [Boolean] if the request path matches the mount path of actioncable
|
57
|
+
def actioncable_request?
|
58
|
+
return false unless defined?(ActionCable)
|
59
|
+
|
60
|
+
/#{ActionCable.server.config.mount_path}/.match?(@request.path)
|
61
|
+
end
|
62
|
+
|
45
63
|
# Is the profiler enabled?
|
46
64
|
#
|
47
65
|
# Takes into account the current request env to decide if the profiler is enabled.
|
@@ -5,21 +5,25 @@ module RailsMiniProfiler
|
|
5
5
|
def initialize(app)
|
6
6
|
@app = app
|
7
7
|
@config = RailsMiniProfiler.configuration
|
8
|
-
Tracers.setup!
|
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
|
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
|
-
|
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 =
|
37
|
-
traces.append(trace)
|
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
|
-
|
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, :
|
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
|
-
|
62
|
-
|
63
|
-
|
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
|
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
|
@@ -1,69 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module RailsMiniProfiler
|
4
|
-
# A convenience wrapper
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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?
|