rails_mini_profiler 0.1.0

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 (79) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +220 -0
  3. data/Rakefile +18 -0
  4. data/app/assets/config/rails_mini_profiler_manifest.js +1 -0
  5. data/app/assets/images/rails_mini_profiler/bookmark.svg +10 -0
  6. data/app/assets/images/rails_mini_profiler/chart.svg +12 -0
  7. data/app/assets/images/rails_mini_profiler/delete.svg +9 -0
  8. data/app/assets/images/rails_mini_profiler/graph.svg +11 -0
  9. data/app/assets/images/rails_mini_profiler/logo.svg +18 -0
  10. data/app/assets/images/rails_mini_profiler/logo_variant.svg +32 -0
  11. data/app/assets/images/rails_mini_profiler/search.svg +10 -0
  12. data/app/assets/images/rails_mini_profiler/setting.svg +10 -0
  13. data/app/assets/images/rails_mini_profiler/show.svg +11 -0
  14. data/app/assets/javascripts/rails_mini_profiler.js +90 -0
  15. data/app/assets/stylesheets/rails_mini_profiler/application.css +164 -0
  16. data/app/assets/stylesheets/rails_mini_profiler/flamegraph.css +14 -0
  17. data/app/assets/stylesheets/rails_mini_profiler/flashes.css +17 -0
  18. data/app/assets/stylesheets/rails_mini_profiler/navbar.css +50 -0
  19. data/app/assets/stylesheets/rails_mini_profiler/profiled_requests.css +180 -0
  20. data/app/assets/stylesheets/rails_mini_profiler/traces.css +87 -0
  21. data/app/controllers/rails_mini_profiler/application_controller.rb +28 -0
  22. data/app/controllers/rails_mini_profiler/flamegraphs_controller.rb +23 -0
  23. data/app/controllers/rails_mini_profiler/profiled_requests_controller.rb +55 -0
  24. data/app/helpers/rails_mini_profiler/application_helper.rb +12 -0
  25. data/app/helpers/rails_mini_profiler/profiled_requests_helper.rb +16 -0
  26. data/app/models/rails_mini_profiler/application_record.rb +17 -0
  27. data/app/models/rails_mini_profiler/controller_trace.rb +33 -0
  28. data/app/models/rails_mini_profiler/flamegraph.rb +33 -0
  29. data/app/models/rails_mini_profiler/instantiation_trace.rb +33 -0
  30. data/app/models/rails_mini_profiler/profiled_request.rb +59 -0
  31. data/app/models/rails_mini_profiler/render_partial_trace.rb +33 -0
  32. data/app/models/rails_mini_profiler/render_template_trace.rb +33 -0
  33. data/app/models/rails_mini_profiler/rmp_trace.rb +31 -0
  34. data/app/models/rails_mini_profiler/sequel_trace.rb +33 -0
  35. data/app/models/rails_mini_profiler/trace.rb +42 -0
  36. data/app/presenters/rails_mini_profiler/base_presenter.rb +25 -0
  37. data/app/presenters/rails_mini_profiler/controller_trace_presenter.rb +18 -0
  38. data/app/presenters/rails_mini_profiler/instantiation_trace_presenter.rb +14 -0
  39. data/app/presenters/rails_mini_profiler/profiled_request_presenter.rb +45 -0
  40. data/app/presenters/rails_mini_profiler/render_partial_trace_presenter.rb +11 -0
  41. data/app/presenters/rails_mini_profiler/render_template_trace_presenter.rb +15 -0
  42. data/app/presenters/rails_mini_profiler/rmp_trace_presenter.rb +9 -0
  43. data/app/presenters/rails_mini_profiler/sequel_trace_presenter.rb +69 -0
  44. data/app/presenters/rails_mini_profiler/trace_presenter.rb +61 -0
  45. data/app/views/layouts/rails_mini_profiler/application.html.erb +26 -0
  46. data/app/views/layouts/rails_mini_profiler/flamegraph.html.erb +18 -0
  47. data/app/views/rails_mini_profiler/badge.html.erb +37 -0
  48. data/app/views/rails_mini_profiler/flamegraphs/show.html.erb +13 -0
  49. data/app/views/rails_mini_profiler/profiled_requests/index.html.erb +59 -0
  50. data/app/views/rails_mini_profiler/profiled_requests/shared/_trace.html.erb +40 -0
  51. data/app/views/rails_mini_profiler/profiled_requests/show.html.erb +40 -0
  52. data/app/views/rails_mini_profiler/shared/_flashes.html.erb +8 -0
  53. data/app/views/rails_mini_profiler/shared/_navbar.html.erb +15 -0
  54. data/config/routes.rb +11 -0
  55. data/db/migrate/20210621185018_create_rmp.rb +44 -0
  56. data/lib/generators/rails_mini_profiler/USAGE +2 -0
  57. data/lib/generators/rails_mini_profiler/install_generator.rb +16 -0
  58. data/lib/generators/rails_mini_profiler/templates/rails_mini_profiler.rb.erb +13 -0
  59. data/lib/rails_mini_profiler.rb +55 -0
  60. data/lib/rails_mini_profiler/badge.rb +62 -0
  61. data/lib/rails_mini_profiler/configuration.rb +41 -0
  62. data/lib/rails_mini_profiler/engine.rb +23 -0
  63. data/lib/rails_mini_profiler/errors.rb +8 -0
  64. data/lib/rails_mini_profiler/flamegraph_guard.rb +47 -0
  65. data/lib/rails_mini_profiler/guard.rb +46 -0
  66. data/lib/rails_mini_profiler/logger.rb +20 -0
  67. data/lib/rails_mini_profiler/middleware.rb +74 -0
  68. data/lib/rails_mini_profiler/models/base_model.rb +18 -0
  69. data/lib/rails_mini_profiler/models/trace.rb +9 -0
  70. data/lib/rails_mini_profiler/redirect.rb +25 -0
  71. data/lib/rails_mini_profiler/request_context.rb +62 -0
  72. data/lib/rails_mini_profiler/request_wrapper.rb +33 -0
  73. data/lib/rails_mini_profiler/response_wrapper.rb +32 -0
  74. data/lib/rails_mini_profiler/storage.rb +29 -0
  75. data/lib/rails_mini_profiler/tracers.rb +85 -0
  76. data/lib/rails_mini_profiler/user.rb +40 -0
  77. data/lib/rails_mini_profiler/version.rb +5 -0
  78. data/lib/tasks/rails_mini_profiler_tasks.rake +8 -0
  79. metadata +151 -0
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: rmp_traces
6
+ #
7
+ # id :integer not null, primary key
8
+ # rmp_profiled_request_id :bigint not null
9
+ # name :string
10
+ # start :integer
11
+ # finish :integer
12
+ # duration :integer
13
+ # allocations :integer
14
+ # payload :json
15
+ # backtrace :json
16
+ # created_at :datetime not null
17
+ # updated_at :datetime not null
18
+ #
19
+ module RailsMiniProfiler
20
+ class Trace < RailsMiniProfiler::ApplicationRecord
21
+ self.table_name = RailsMiniProfiler.storage_configuration.traces_table
22
+ self.inheritance_column = :name
23
+
24
+ belongs_to :profiled_request,
25
+ class_name: 'RailsMiniProfiler::ProfiledRequest',
26
+ foreign_key: :rmp_profiled_request_id
27
+
28
+ class << self
29
+ def find_sti_class(name)
30
+ subclasses = {
31
+ 'process_action.action_controller' => RailsMiniProfiler::ControllerTrace,
32
+ 'sql.active_record' => RailsMiniProfiler::SequelTrace,
33
+ 'instantiation.active_record' => RailsMiniProfiler::InstantiationTrace,
34
+ 'rails_mini_profiler.total_time' => RailsMiniProfiler::RmpTrace,
35
+ 'render_template.action_view' => RailsMiniProfiler::RenderTemplateTrace,
36
+ 'render_partial.action_view' => RailsMiniProfiler::RenderPartialTrace
37
+ }
38
+ subclasses[name] || self
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class BasePresenter < SimpleDelegator
5
+ def initialize(model, view, **_kwargs)
6
+ @h = view
7
+ super(model)
8
+ end
9
+
10
+ attr_reader :h
11
+
12
+ alias model __getobj__
13
+
14
+ # To avoid having to address the view context explicitly we try to delegate to it
15
+ def method_missing(method, *args, &block)
16
+ h.public_send(method, *args, &block)
17
+ rescue NoMethodError
18
+ super
19
+ end
20
+
21
+ def respond_to_missing?(method_name, *args)
22
+ h.respond_to?(method_name, *args) || super
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class ControllerTracePresenter < TracePresenter
5
+ def label
6
+ 'Action Controller'
7
+ end
8
+
9
+ def payload
10
+ content_tag('div') do
11
+ content_tag('pre', class: 'trace-payload') do
12
+ content_tag(:div, "View Time: #{model.view_runtime} ms, DB Time: #{model.db_runtime} ms",
13
+ class: 'sequel-trace-query')
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class InstantiationTracePresenter < TracePresenter
5
+ def label
6
+ "#{model.class_name} Instantiation"
7
+ end
8
+
9
+ def description
10
+ record_string = 'Record'.pluralize(model.record_count)
11
+ "Instantiated #{model.record_count} #{model.class_name} #{record_string}"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class ProfiledRequestPresenter < BasePresenter
5
+ def request_name
6
+ model.request_path
7
+ end
8
+
9
+ def duration
10
+ formatted_duration(model.duration)
11
+ end
12
+
13
+ def allocations
14
+ formatted_allocations(model.allocations)
15
+ end
16
+
17
+ def created_at
18
+ time_tag(model.created_at.in_time_zone(Time.zone))
19
+ end
20
+
21
+ def flamegraph_icon
22
+ return nil unless RailsMiniProfiler.configuration.flamegraph_enabled
23
+
24
+ if model.flamegraph.present?
25
+ link_to(flamegraph_path(model.id), title: 'Show Flamegraph') do
26
+ inline_svg_tag('rails_mini_profiler/graph.svg')
27
+ end
28
+ else
29
+ link_to(flamegraph_path(model.id), title: 'No Flamegraph present for this request', class: 'link-disabled') do
30
+ inline_svg_tag('rails_mini_profiler/graph.svg')
31
+ end
32
+ end
33
+ end
34
+
35
+ def flamegraph_button
36
+ return nil unless RailsMiniProfiler.configuration.flamegraph_enabled
37
+
38
+ return nil unless model.flamegraph.present?
39
+
40
+ link_to(flamegraph_path(model.id), title: 'Show Flamegraph', class: 'flamegraph-button') do
41
+ content_tag('button', 'Flamegraph')
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class RenderPartialTracePresenter < TracePresenter
5
+ def label
6
+ root = Rails.root.to_s.split('/').to_set
7
+ identifier = model.identifier.split('/').to_set
8
+ (root ^ identifier).drop(2).join('/').reverse.truncate(30).reverse
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class RenderTemplateTracePresenter < TracePresenter
5
+ def label
6
+ root = Rails.root.to_s.split('/').to_set
7
+ identifier = model.identifier.split('/').to_set
8
+ (root ^ identifier).drop(2).join('/').reverse.truncate(30).reverse
9
+ end
10
+
11
+ def description
12
+ "Render #{label}"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class RmpTracePresenter < TracePresenter
5
+ def label
6
+ 'Total Time'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class SequelTracePresenter < TracePresenter
5
+ def label
6
+ sql_description
7
+ end
8
+
9
+ alias description label
10
+
11
+ def payload
12
+ return nil if transaction?
13
+
14
+ content_tag('div') do
15
+ content_tag('pre', class: 'trace-payload') do
16
+ content_tag(:div, model.sql, class: 'sequel-trace-query')
17
+ end + binding_content
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def transaction?
24
+ model.payload['name'] == 'TRANSACTION'
25
+ end
26
+
27
+ def schema?
28
+ model.payload['name'] == 'SCHEMA'
29
+ end
30
+
31
+ def sql_description
32
+ if transaction?
33
+ transaction_description
34
+ elsif schema?
35
+ 'Load Schema'
36
+ elsif model.payload['name'].present?
37
+ model.payload['name']
38
+ else
39
+ model.payload['sql'].truncate(15)
40
+ end
41
+ end
42
+
43
+ def transaction_description
44
+ # The raw SQL is something like 'BEGIN TRANSACTION', and we just turn it into 'Begin Transaction', which is less
45
+ # loud and nicer to look at.
46
+ model.sql.split.map(&:capitalize).join(' ')
47
+ end
48
+
49
+ def binding_content
50
+ return nil if simple_binds.empty?
51
+
52
+ content = simple_binds.collect do |hash|
53
+ flat = hash.to_a.flatten
54
+ "#{flat.first}=#{flat.second}"
55
+ end
56
+ content_tag(:pre, content.join(', '), class: 'sequel-trace-binds')
57
+ end
58
+
59
+ def simple_binds
60
+ return [] if model.binds.nil? || model.binds.empty?
61
+
62
+ model.binds.each_with_object({}) do |hash, object|
63
+ name = hash['name']
64
+ value = hash['value']
65
+ object[name] = value
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class TracePresenter < BasePresenter
5
+ def initialize(trace, view, profiled_request:)
6
+ super(trace, view)
7
+ @profiled_request = profiled_request
8
+ end
9
+
10
+ def label
11
+ ''
12
+ end
13
+
14
+ def description
15
+ label
16
+ end
17
+
18
+ def payload
19
+ nil
20
+ end
21
+
22
+ def backtrace
23
+ return if model.backtrace.empty?
24
+
25
+ model.backtrace.first
26
+ end
27
+
28
+ def type
29
+ # Turn this class name into a dasherized version for use in assigning CSS classes. E.g. 'RmpTracePresenter'
30
+ # becomes 'rmp-trace'
31
+ self.class.name.demodulize.delete_suffix('Presenter')
32
+ .underscore
33
+ .dasherize
34
+ end
35
+
36
+ def duration
37
+ formatted_duration(model.duration)
38
+ end
39
+
40
+ def duration_percent
41
+ (model.duration.to_f / @profiled_request.duration * 100).round
42
+ end
43
+
44
+ def allocations
45
+ formatted_allocations(model.allocations)
46
+ end
47
+
48
+ def allocations_percent
49
+ (model.allocations.to_f / @profiled_request.allocations * 100).round
50
+ end
51
+
52
+ def from_start
53
+ (model.start - @profiled_request.start).to_f / 100
54
+ end
55
+
56
+ def from_start_percent
57
+ ((model.start - @profiled_request.start).to_f /
58
+ (@profiled_request.finish - @profiled_request.start)).to_f * 100
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Rails Mini Profiler</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= javascript_include_tag "rails_mini_profiler" %>
9
+ <%= stylesheet_link_tag "rails_mini_profiler/application", media: "all" %>
10
+ <link rel="preconnect" href="https://fonts.gstatic.com">
11
+ <script src="https://unpkg.com/@popperjs/core@2"></script>
12
+ <script src="https://unpkg.com/tippy.js@6"></script>
13
+ <link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,600;0,700;0,800;1,300;1,400;1,600;1,700;1,800&display=swap" rel="stylesheet">
14
+ </head>
15
+ <body>
16
+
17
+ <%= render 'rails_mini_profiler/shared/navbar' %>
18
+ <main>
19
+ <section class="main-section">
20
+ <%= render 'rails_mini_profiler/shared/flashes' %>
21
+ <%= yield %>
22
+ </section>
23
+ </main>
24
+
25
+ </body>
26
+ </html>
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Rails Mini Profiler</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= javascript_include_tag "rails_mini_profiler" %>
9
+ <%= stylesheet_link_tag "rails_mini_profiler/application", media: "all" %>
10
+ </head>
11
+ <body>
12
+
13
+ <main>
14
+ <%= yield %>
15
+ </main>
16
+
17
+ </body>
18
+ </html>
@@ -0,0 +1,37 @@
1
+ <style>
2
+ #rails-mini-profiler-badge {
3
+ position: absolute;
4
+ <%= @position %>
5
+ display: flex;
6
+ margin: 0;
7
+ padding: .2em .2em;
8
+ line-height: normal;
9
+ align-items: center;
10
+ z-index: 2147483641;
11
+ overflow: auto;
12
+ border-radius: 5px;
13
+ text-decoration: none;
14
+ opacity: 60%;
15
+ background-color: #fff;
16
+ color: #555;
17
+ -moz-box-shadow: 0 1px 15px #555;
18
+ -webkit-box-shadow: 0 1px 15px #555;
19
+ box-shadow: 0 1px 15px #555;
20
+ font-size: 14px;
21
+ }
22
+
23
+ #rails-mini-profiler-badge:hover {
24
+ opacity: 100%;
25
+ transition: opacity 0.3s;
26
+ }
27
+
28
+ #rails-mini-profiler-badge-icon {
29
+ height: 18px;
30
+ width: 18px;
31
+ padding-bottom: 2px;
32
+ }
33
+ </style>
34
+ <a id="rails-mini-profiler-badge" href="<%= profiled_request_path(@profiled_request.id) %>" data-turbolinks="false" title="View performance data">
35
+ <%= inline_svg_tag('rails_mini_profiler/logo_variant', id: "rails-mini-profiler-badge-icon") %>
36
+ <%= (@profiled_request.duration.to_f / 100).round %>ms
37
+ </a>
@@ -0,0 +1,13 @@
1
+ <div id=wrapper>
2
+ <iframe id=speedscope-iframe></iframe>
3
+ </div>
4
+ <script type="text/javascript">
5
+ const graph = <%= raw @flamegraph %>
6
+ const json = JSON.stringify(graph)
7
+ const blob = new Blob([json], { type: 'text/plain' })
8
+ const objUrl = encodeURIComponent(URL.createObjectURL(blob))
9
+ const iframe = document.getElementById('speedscope-iframe')
10
+ const baseUrl = window.location.origin;
11
+ const iframeUrl = baseUrl + '/rails_mini_profiler/speedscope/index.html#profileURL=' + objUrl + '&title=' + 'Flamegraph';
12
+ iframe.setAttribute('src', iframeUrl)
13
+ </script>
@@ -0,0 +1,59 @@
1
+ <% if @profiled_requests.empty? %>
2
+ <section class="placeholder">
3
+ <%= inline_svg_tag('rails_mini_profiler/logo_variant', class: 'placeholder-image') %>
4
+ <div class="placeholder-text">
5
+ <h2> There is nothing here yet! </h2>
6
+ <p>Send some requests to your app for them to show up here</p>
7
+ </div>
8
+ </section>
9
+ <% else %>
10
+ <h1>Profiled Requests</h1>
11
+ <div class="profiled-requests-actions">
12
+ <%= form_with id: 'profiled-request-search-form', url: profiled_requests_url, method: :get do |form| %>
13
+ <%= form.search_field :path, id: 'profiled-request-search', placeholder: 'Search Path...', class: 'search-field' %>
14
+ <% end %>
15
+ <%= link_to(destroy_all_profiled_requests_url,
16
+ method: :delete,
17
+ class: 'clear-action',
18
+ data: { confirm: "This will delete all requests. Are you sure?" }) do %>
19
+ <button>Clear Requests</button>
20
+ <% end %>
21
+ </div>
22
+ <table id="profiled-requests-table" class="table">
23
+ <thead>
24
+ <tr>
25
+ <th class="text-left">Path</th>
26
+ <th class="text-left">Method</th>
27
+ <th class="text-left">Status</th>
28
+ <th class="text-left">Media Type</th>
29
+ <th class="text-right">Duration</th>
30
+ <th class="text-left"></th>
31
+ <th class="text-left"></th>
32
+ </tr>
33
+ </thead>
34
+
35
+ <tbody>
36
+ <% @profiled_requests.each do |profiled_request| %>
37
+ <tr data-link="<%= profiled_request_path(profiled_request.id)%>">
38
+ <td class="request-path"><%= profiled_request.request_name %></td>
39
+ <td> <%= profiled_request.request_method %> </td>
40
+ <td> <%= profiled_request.response_status %> </td>
41
+ <td> <%= profiled_request.response_media_type %> </td>
42
+ <td class="text-right"><%= profiled_request.duration %> ms</td>
43
+ <td class=""><%= profiled_request.created_at %></td>
44
+ <td class="request-buttons">
45
+ <%= link_to(profiled_request_path(profiled_request.id), title: 'Show Details') do %>
46
+ <%= inline_svg_tag('rails_mini_profiler/show.svg', options = {}) %>
47
+ <% end %>
48
+ <%= profiled_request.flamegraph_icon %>
49
+ <%= link_to(profiled_request_path(profiled_request.id), title: 'Delete Request', method: :delete) do %>
50
+ <%= inline_svg_tag('rails_mini_profiler/delete.svg') %>
51
+ <% end %>
52
+ </td>
53
+ </tr>
54
+ <% end %>
55
+ </tbody>
56
+ </table>
57
+
58
+ <br>
59
+ <% end %>