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,40 @@
1
+ <li class="trace <%= trace.type %>">
2
+ <span class="trace-name " style="width: calc(calc(<%= trace.from_start_percent %>% * .9) + 10%)"><%= trace.label %></span>
3
+ <div class="trace-bar" style="width: max(calc(<%= trace.duration_percent %>% * .9), 5px)">
4
+ <div class="popover">
5
+ <section class="popover-header">
6
+ <h1 class="popover-description"><%= trace.description %></h1>
7
+ <button class="popover-close">x</button>
8
+ </section>
9
+ <section class="popover-body">
10
+ <%= trace.payload %>
11
+ <table class="trace-table">
12
+ <thead>
13
+ <tr>
14
+ <th class="text-left"></th>
15
+ <th class="text-right">Response Time</th>
16
+ <th class="text-right">Allocations</th>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ <tr>
21
+ <td>Total</td>
22
+ <td class="text-right"><%= trace.duration %>ms</td>
23
+ <td class="text-right"><%= trace.allocations %></td>
24
+ </tr>
25
+ <tr>
26
+ <td>Relative</td>
27
+ <td class="text-right"><%= trace.duration_percent %>%</td>
28
+ <td class="text-right"><%= trace.allocations_percent %>%</td>
29
+ </tr>
30
+ </table>
31
+ </section>
32
+
33
+ <% if trace.backtrace %>
34
+ <section class="popover-footer">
35
+ <pre><%= trace.backtrace %></pre>
36
+ </section>
37
+ <% end %>
38
+ </div>
39
+ </div>
40
+ </li>
@@ -0,0 +1,40 @@
1
+ <section class="request-details">
2
+ <h1> <%= @profiled_request.request_path %> </h1>
3
+
4
+ <ul class="request-details-data">
5
+ <li class="data-item">
6
+ <small>Method</small>
7
+ <span class="pill request-method request-method-<%= @profiled_request.request_method %>"><%= @profiled_request.request_method %></span>
8
+ </li>
9
+ <li class="data-item">
10
+ <small>Status</small>
11
+ <span class="pill request-status request-status-<%= @profiled_request.response_status %>"><%= @profiled_request.response_status %></span>
12
+ </li>
13
+ <li class="data-item">
14
+ <small>Response Time</small>
15
+ <span><%= @profiled_request.duration %>ms</span>
16
+ </li>
17
+ <li class="data-item">
18
+ <small>Allocations</small>
19
+ <span><%= @profiled_request.allocations %></span>
20
+ </li>
21
+
22
+ </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
+ <%= @profiled_request.flamegraph_button %>
31
+ </section>
32
+
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>
@@ -0,0 +1,8 @@
1
+ <% flash.each do |type, message| %>
2
+ <% if type == "alert" %>
3
+ <div class="flash flash-error"><%= message %></div>
4
+ <% end %>
5
+ <% if type == "notice" %>
6
+ <div class="flash flash-notice"><%= message %></div>
7
+ <% end %>
8
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <header class="header">
2
+ <nav class="nav">
3
+ <a class="home" href="<%= profiled_requests_path %>">
4
+ <%= inline_svg_tag('rails_mini_profiler/logo', class: 'home-logo') %>
5
+ <h1 class="home-title">Rails Mini Profiler </h1>
6
+ </a>
7
+ <ul class="header-links">
8
+ <li>
9
+ <a href="https://github.com/hschne/rails-mini-profiler#rails-mini-profiler">
10
+ Docs
11
+ </a>
12
+ </li>
13
+ </ul>
14
+ </nav>
15
+ </header>
data/config/routes.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ RailsMiniProfiler::Engine.routes.draw do
4
+ root 'profiled_requests#index'
5
+ resources :profiled_requests, only: %i[index show destroy] do
6
+ collection do
7
+ delete 'destroy_all'
8
+ end
9
+ end
10
+ resources :flamegraphs, only: %i[show]
11
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateRmp < ActiveRecord::Migration[6.0]
4
+ def change
5
+ create_table :rmp_profiled_requests do |t|
6
+ t.string :user_id
7
+ t.integer :start
8
+ t.integer :finish
9
+ t.integer :duration
10
+ t.integer :allocations
11
+ t.string :request_path
12
+ t.string :request_query_string
13
+ t.string :request_method
14
+ t.json :request_headers
15
+ t.text :request_body
16
+ t.integer :response_status
17
+ t.text :response_body
18
+ t.json :response_headers
19
+ t.string :response_media_type
20
+
21
+ t.timestamps
22
+ end
23
+
24
+ create_table :rmp_traces do |t|
25
+ t.belongs_to :rmp_profiled_request, null: false, foreign_key: true
26
+ t.string :name
27
+ t.integer :start
28
+ t.integer :finish
29
+ t.integer :duration
30
+ t.integer :allocations
31
+ t.json :payload
32
+ t.json :backtrace
33
+
34
+ t.timestamps
35
+ end
36
+
37
+ create_table :rmp_flamegraphs do |t|
38
+ t.belongs_to :rmp_profiled_request, null: false, foreign_key: true
39
+ t.binary :data
40
+
41
+ t.timestamps
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,2 @@
1
+ description:
2
+ Enable rails-mini-profiler in development for your application.
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ desc 'Install rails-mini-profiler'
9
+ def install
10
+ route("mount RailsMiniProfiler::Engine => '/rails_mini_profiler'")
11
+ template 'rails_mini_profiler.rb.erb', 'config/initializers/rails_mini_profiler.rb'
12
+ system('rails rails_mini_profiler:install:migrations')
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ RailsMiniProfiler.configure do |config|
2
+ # Customize when Rails Mini Profiler should run
3
+ # config.enabled = proc { |env| Rails.env.development? || request.headers[RMP_ENABLED].present? }
4
+
5
+ # Disable Flamegraph generation
6
+ # config.flamegraph_enabled = false
7
+
8
+ # Use RailsMiniProfiler::Storage::ActiveRecord to store profiles in your Database
9
+ config.storage = RailsMiniProfiler::Storage::Memory
10
+
11
+ # Uncomment to customize how users are detected
12
+ # config.user_provider = proc { |env| Rack::Request.new(env).ip }
13
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'inline_svg'
5
+
6
+ require 'rails_mini_profiler/version'
7
+ require 'rails_mini_profiler/engine'
8
+
9
+ require 'rails_mini_profiler/errors'
10
+
11
+ require 'rails_mini_profiler/user'
12
+ require 'rails_mini_profiler/request_context'
13
+
14
+ require 'rails_mini_profiler/models/base_model'
15
+ require 'rails_mini_profiler/models/trace'
16
+
17
+ require 'rails_mini_profiler/logger'
18
+ require 'rails_mini_profiler/configuration'
19
+ require 'rails_mini_profiler/storage'
20
+ require 'rails_mini_profiler/request_wrapper'
21
+ require 'rails_mini_profiler/response_wrapper'
22
+ require 'rails_mini_profiler/guard'
23
+ require 'rails_mini_profiler/flamegraph_guard'
24
+ require 'rails_mini_profiler/redirect'
25
+ require 'rails_mini_profiler/badge'
26
+ require 'rails_mini_profiler/tracers'
27
+ require 'rails_mini_profiler/middleware'
28
+
29
+ module RailsMiniProfiler
30
+ class << self
31
+ def configuration
32
+ @configuration ||= Configuration.new
33
+ end
34
+
35
+ def configure
36
+ yield(configuration)
37
+ end
38
+
39
+ def storage_configuration
40
+ configuration.storage.configuration
41
+ end
42
+
43
+ def logger
44
+ @logger ||= configuration.logger
45
+ end
46
+
47
+ def authorize!(current_user)
48
+ RailsMiniProfiler::User.current_user = current_user
49
+ end
50
+
51
+ def current_user=(current_user)
52
+ RailsMiniProfiler::User.current_user = current_user
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class Badge
5
+ include InlineSvg::ActionView::Helpers
6
+ include Engine.routes.url_helpers
7
+
8
+ def initialize(request_context, configuration: RailsMiniProfiler.configuration)
9
+ @configuration = configuration
10
+ @profiled_request = request_context.profiled_request
11
+ @original_response = request_context.response
12
+ end
13
+
14
+ def render
15
+ content_type = @original_response.headers['Content-Type']
16
+ return @original_response unless content_type =~ %r{text/html}
17
+
18
+ modified_response = Rack::Response.new([], @original_response.status, @original_response.headers)
19
+ modified_response.write(modified_body)
20
+ modified_response.finish
21
+
22
+ response = @original_response.response
23
+ response.close if response.respond_to?(:close)
24
+
25
+ ResponseWrapper.new(@original_response.status,
26
+ @original_response.headers,
27
+ modified_response)
28
+ end
29
+
30
+ private
31
+
32
+ def modified_body
33
+ body = @original_response.response.body
34
+ index = body.rindex(%r{</body>}i) || body.rindex(%r{</html>}i)
35
+ if index
36
+ body.dup.insert(index, badge_content)
37
+ else
38
+ body
39
+ end
40
+ end
41
+
42
+ def badge_content
43
+ html = IO.read(File.expand_path('../../app/views/rails_mini_profiler/badge.html.erb', __dir__))
44
+ @position = css_position
45
+ template = ERB.new(html)
46
+ template.result(binding)
47
+ end
48
+
49
+ def css_position
50
+ case RailsMiniProfiler.configuration.badge_position
51
+ when 'top-right'
52
+ 'top: 5px; right: 5px;'
53
+ when 'bottom-left'
54
+ 'bottom: 5px; left: 5px;'
55
+ when 'bottom-right'
56
+ 'bottom: 5px; right: 5px;'
57
+ else
58
+ 'top: 5px; left: 5px;'
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class Configuration
5
+ attr_reader :logger
6
+
7
+ attr_accessor :enabled,
8
+ :badge_enabled,
9
+ :badge_position,
10
+ :flamegraph_enabled,
11
+ :flamegraph_sample_rate,
12
+ :skip_paths,
13
+ :storage,
14
+ :user_provider
15
+
16
+ def initialize(**kwargs)
17
+ reset
18
+ kwargs.each { |key, value| instance_variable_set("@#{key}", value) }
19
+ end
20
+
21
+ def reset
22
+ @enabled = proc { |_env| Rails.env.development? || Rails.env.test? }
23
+ @badge_enabled = true
24
+ @badge_position = 'top-left'
25
+ @flamegraph_enabled = true
26
+ @flamegraph_sample_rate = 0.5
27
+ @logger = RailsMiniProfiler::Logger.new(Rails.logger)
28
+ @skip_paths = []
29
+ @storage = Storage.new
30
+ @user_provider = proc { |env| Rack::Request.new(env).ip }
31
+ end
32
+
33
+ def logger=(logger)
34
+ if logger.nil?
35
+ @logger.level = Logger::FATAL
36
+ else
37
+ @logger = logger
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace RailsMiniProfiler
6
+
7
+ initializer 'rails_mini_profiler.add_middleware' do |app|
8
+ app.middleware.use(RailsMiniProfiler::Middleware)
9
+ end
10
+
11
+ initializer 'rails_mini_profiler.assets.precompile', group: :all do |app|
12
+ app.config.assets.precompile += %w[rails_mini_profiler.js rails_mini_profiler/application.css]
13
+ end
14
+
15
+ config.generators do |g|
16
+ g.test_framework :rspec
17
+ end
18
+
19
+ initializer 'rails_mini_profiler_add_static assets' do |app|
20
+ app.middleware.insert_before(ActionDispatch::Static, ActionDispatch::Static, "#{root}/public")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ RailsMiniProfilerError = Class.new(StandardError)
5
+
6
+ # Storage errors
7
+ StorageError = Class.new(RailsMiniProfilerError)
8
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsMiniProfiler
4
+ class FlamegraphGuard
5
+ def initialize(request_context, configuration: RailsMiniProfiler.configuration)
6
+ @request_context = request_context
7
+ @request = request_context.request
8
+ @configuration = configuration
9
+ end
10
+
11
+ def record(&block)
12
+ return block.call unless enabled?
13
+
14
+ sample_rate = @configuration.flamegraph_sample_rate
15
+ if StackProf.running?
16
+ RailsMiniProfiler.logger.error('Stackprof is already running, cannot record Flamegraph')
17
+ return block.call
18
+ end
19
+
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
+
25
+ unless flamegraph
26
+ RailsMiniProfiler.logger.error('Failed to record Flamegraph, possibly due to concurrent requests')
27
+ return result
28
+ end
29
+
30
+ @request_context.flamegraph = flamegraph.to_json
31
+ result
32
+ end
33
+
34
+ private
35
+
36
+ def enabled?
37
+ defined?(StackProf) && StackProf.respond_to?(:run) && config_enabled?
38
+ end
39
+
40
+ def config_enabled?
41
+ params = CGI.parse(@request.query_string).transform_values(&:first).with_indifferent_access
42
+ return params[:rmp_flamegraph] if params[:rmp_flamegraph]
43
+
44
+ RailsMiniProfiler.configuration.flamegraph_enabled
45
+ end
46
+ end
47
+ end