├── .babelrc
├── .eslintrc.json
├── .github
├── ISSUE_TEMPLATE
│ ├── BUG_REPORT.md
│ ├── FEATURE_REQUEST.md
│ └── config.yml
├── PULL_REQUEST_TEMPLATE.md
├── stale.yml
└── workflows
│ ├── main.yml
│ └── release.yml
├── .gitignore
├── .prettierrc.json
├── .rspec
├── .rubocop.yml
├── .stylelintrc.json
├── .tool-versions
├── Appraisals
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── Rakefile
├── app
├── adapters
│ └── rails_mini_profiler
│ │ └── database_adapter.rb
├── assets
│ ├── config
│ │ └── rails_mini_profiler_manifest.js
│ ├── javascripts
│ │ └── rails_mini_profiler.js
│ └── stylesheets
│ │ └── rails_mini_profiler
│ │ └── application.css
├── controllers
│ └── rails_mini_profiler
│ │ ├── application_controller.rb
│ │ ├── flamegraphs_controller.rb
│ │ └── profiled_requests_controller.rb
├── helpers
│ └── rails_mini_profiler
│ │ ├── application_helper.rb
│ │ └── profiled_requests_helper.rb
├── javascript
│ ├── images
│ │ ├── bookmark.svg
│ │ ├── chart.svg
│ │ ├── check.svg
│ │ ├── chevron.svg
│ │ ├── copy.svg
│ │ ├── delete.svg
│ │ ├── filter.svg
│ │ ├── graph.svg
│ │ ├── logo.svg
│ │ ├── logo_variant.svg
│ │ ├── search.svg
│ │ ├── setting.svg
│ │ └── show.svg
│ ├── js
│ │ ├── checklist_controller.js
│ │ ├── clipboard_controller.js
│ │ ├── enable_controller.js
│ │ ├── filter_controller.js
│ │ ├── search_controller.js
│ │ └── select_controller.js
│ ├── packs
│ │ └── rails-mini-profiler.js
│ └── stylesheets
│ │ ├── components
│ │ ├── buttons.scss
│ │ ├── dropdown.scss
│ │ ├── input.scss
│ │ ├── navbar.scss
│ │ ├── page_header.scss
│ │ ├── pagination.scss
│ │ ├── placeholder.scss
│ │ ├── profiled_request_table.scss
│ │ └── trace.scss
│ │ ├── flamegraph.scss
│ │ ├── flashes.scss
│ │ ├── profiled_requests.scss
│ │ ├── rails-mini-profiler.scss
│ │ └── traces.scss
├── models
│ └── rails_mini_profiler
│ │ ├── application_record.rb
│ │ ├── flamegraph.rb
│ │ ├── profiled_request.rb
│ │ └── trace.rb
├── presenters
│ └── rails_mini_profiler
│ │ ├── base_presenter.rb
│ │ ├── controller_trace_presenter.rb
│ │ ├── instantiation_trace_presenter.rb
│ │ ├── profiled_request_presenter.rb
│ │ ├── render_partial_trace_presenter.rb
│ │ ├── render_template_trace_presenter.rb
│ │ ├── rmp_trace_presenter.rb
│ │ ├── sequel_trace_presenter.rb
│ │ └── trace_presenter.rb
├── search
│ └── rails_mini_profiler
│ │ ├── base_search.rb
│ │ ├── profiled_request_search.rb
│ │ └── trace_search.rb
└── views
│ ├── layouts
│ └── rails_mini_profiler
│ │ ├── application.html.erb
│ │ └── flamegraph.html.erb
│ ├── models
│ ├── _flamegraph.json.jb
│ ├── _profiled_request.jb
│ └── _trace.jb
│ └── rails_mini_profiler
│ ├── badge.html.erb
│ ├── flamegraphs
│ ├── show.html.erb
│ └── show.json.jb
│ ├── profiled_requests
│ ├── index.html.erb
│ ├── index.json.jb
│ ├── shared
│ │ ├── header
│ │ │ └── _header.erb
│ │ └── table
│ │ │ ├── _placeholder.erb
│ │ │ ├── _table.erb
│ │ │ ├── _table_head.erb
│ │ │ └── _table_row.erb
│ ├── show.html.erb
│ ├── show.json.jb
│ └── show
│ │ ├── _trace.html.erb
│ │ ├── _trace_list.erb
│ │ ├── _trace_list_header.erb
│ │ └── _trace_list_placeholder.erb
│ └── shared
│ ├── _flashes.html.erb
│ ├── _head.erb
│ └── _navbar.html.erb
├── bin
├── annotate
├── appraisal
├── rails
├── rake
└── rspec
├── commitlint.config.js
├── config
└── routes.rb
├── db
└── migrate
│ └── 20210621185018_create_rmp.rb
├── docker-compose.yml
├── docs
└── images
│ ├── logo.png
│ ├── overview.png
│ ├── trace-details.png
│ └── trace.png
├── gemfiles
├── rails_6.0.gemfile
├── rails_6.1.gemfile
└── rails_7.0.gemfile
├── lib
├── generators
│ └── rails_mini_profiler
│ │ ├── USAGE
│ │ ├── install_generator.rb
│ │ └── templates
│ │ ├── rails_mini_profiler.js.erb
│ │ └── rails_mini_profiler.rb.erb
├── rails_mini_profiler.rb
├── rails_mini_profiler
│ ├── badge.rb
│ ├── configuration.rb
│ ├── configuration
│ │ ├── storage.rb
│ │ └── user_interface.rb
│ ├── engine.rb
│ ├── flamegraph_guard.rb
│ ├── guard.rb
│ ├── logger.rb
│ ├── middleware.rb
│ ├── models
│ │ └── base_model.rb
│ ├── redirect.rb
│ ├── request_context.rb
│ ├── request_wrapper.rb
│ ├── response_wrapper.rb
│ ├── tracers.rb
│ ├── tracers
│ │ ├── controller_tracer.rb
│ │ ├── instantiation_tracer.rb
│ │ ├── null_trace.rb
│ │ ├── registry.rb
│ │ ├── rmp_tracer.rb
│ │ ├── sequel_tracer.rb
│ │ ├── sequel_tracker.rb
│ │ ├── subscriptions.rb
│ │ ├── trace.rb
│ │ ├── trace_factory.rb
│ │ ├── tracer.rb
│ │ └── view_tracer.rb
│ ├── user.rb
│ └── version.rb
└── tasks
│ └── rails_mini_profiler_tasks.rake
├── package-lock.json
├── package.json
├── public
└── rails_mini_profiler
│ └── speedscope
│ ├── LICENSE
│ ├── demangle-cpp.1768f4cc.js
│ ├── demangle-cpp.1768f4cc.js.map
│ ├── favicon-16x16.f74b3187.png
│ ├── favicon-32x32.bc503437.png
│ ├── file-format-schema.json
│ ├── import.a03bf119.js
│ ├── import.a03bf119.js.map
│ ├── index.html
│ ├── release.txt
│ ├── reset.8c46b7a1.css
│ ├── reset.8c46b7a1.css.map
│ ├── source-map.438fa06b.js
│ ├── source-map.438fa06b.js.map
│ ├── speedscope.eee21de6.js
│ └── speedscope.eee21de6.js.map
├── rails_mini_profiler.gemspec
├── rakelib
├── speedscope.rake
└── util
│ └── speedscope.rb
├── rollup.config.js
└── spec
├── adapters
└── rails_mini_profiler
│ └── database_adapter_spec.rb
├── dummy
├── Rakefile
├── app
│ ├── assets
│ │ ├── config
│ │ │ └── manifest.js
│ │ ├── images
│ │ │ └── .keep
│ │ └── stylesheets
│ │ │ ├── application.css
│ │ │ ├── movies.css
│ │ │ └── scaffold.css
│ ├── channels
│ │ └── application_cable
│ │ │ ├── channel.rb
│ │ │ └── connection.rb
│ ├── controllers
│ │ ├── application_controller.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ └── movies_controller.rb
│ ├── helpers
│ │ ├── application_helper.rb
│ │ └── movies_helper.rb
│ ├── javascript
│ │ └── packs
│ │ │ └── application.js
│ ├── jobs
│ │ └── application_job.rb
│ ├── mailers
│ │ └── application_mailer.rb
│ ├── models
│ │ ├── application_record.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ └── movie.rb
│ └── views
│ │ ├── layouts
│ │ ├── application.html.erb
│ │ ├── mailer.html.erb
│ │ └── mailer.text.erb
│ │ └── movies
│ │ ├── _form.html.erb
│ │ ├── _movie.json.jb
│ │ ├── edit.html.erb
│ │ ├── index.html.erb
│ │ ├── index.json.jb
│ │ ├── new.html.erb
│ │ ├── show.html.erb
│ │ └── show.json.jb
├── bin
│ ├── rails
│ ├── rake
│ └── setup
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── cable.yml
│ ├── credentials.yml.enc
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── application_controller_renderer.rb
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── content_security_policy.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ ├── permissions_policy.rb
│ │ ├── rails_mini_profiler.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ ├── puma.rb
│ ├── routes.rb
│ └── storage.yml
├── db
│ ├── migrate
│ │ └── 20210702114107_add_movies.rb
│ ├── movies.csv
│ ├── schema.rb
│ └── seeds.rb
├── lib
│ └── assets
│ │ └── .keep
├── log
│ └── .keep
├── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ └── favicon.ico
├── storage
│ └── .keep
└── tmp
│ ├── .keep
│ ├── development_secret.txt
│ ├── pids
│ └── .keep
│ └── storage
│ └── .keep
├── helpers
└── rails_mini_profiler
│ └── profiled_request_helper_spec.rb
├── lib
└── rails_mini_profiler
│ ├── badge_spec.rb
│ ├── guard_spec.rb
│ ├── redirect_spec.rb
│ └── tracers
│ ├── controller_tracer_spec.rb
│ ├── registry_spec.rb
│ ├── sequel_tracer_spec.rb
│ ├── sequel_tracker_spec.rb
│ ├── trace_factory_spec.rb
│ ├── tracer_spec.rb
│ └── view_tracer_spec.rb
├── models
└── rails_mini_profiler
│ └── profiled_request_spec.rb
├── presenters
└── rails_mini_profiler
│ └── trace_presenter_spec.rb
├── rails_helper.rb
├── rails_mini_profiler_spec.rb
├── requests
└── rails_mini_profiler
│ ├── application_spec.rb
│ ├── flamegraph_request_spec.rb
│ └── profiled_requests_spec.rb
├── routing
└── rails_mini_profiler
│ └── profiled_requests_routing_spec.rb
├── search
└── rails_mini_profiler
│ ├── profiled_request_search_spec.rb
│ └── trace_search_spec.rb
├── spec_helper.rb
└── support
└── spec_helpers.rb
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", {
4 | "modules": false
5 | }]
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true,
5 | "node": true
6 | },
7 | "globals": {
8 | "ENV": true
9 | },
10 | "parser": "@babel/eslint-parser",
11 | "extends": [
12 | "eslint:recommended",
13 | "prettier"
14 | ],
15 | "parserOptions": {
16 | "ecmaVersion": 2020,
17 | "sourceType": "module"
18 | },
19 | "ignorePatterns": [
20 | "public",
21 | "node_modules"
22 | ],
23 | "rules": {
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BUG_REPORT.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41E Bug Report"
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug 🐛
6 | assignees: ''
7 |
8 | ---
9 |
10 | Found a bug? Please fill out the sections below! 👍
11 |
12 | ### Issue Summary
13 |
14 | A summary of the issue.
15 |
16 | ### Steps to Reproduce
17 |
18 | 1. (for example) Start a new Rails project with Rails Mini Profiler
19 | 2. Edit something.rb as follows...
20 | 3. ...
21 |
22 | Any other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?
23 |
24 | ### Technical details
25 |
26 | * Rails version: Run `rails --version` to find out.
27 | * Ruby version: Run `ruby -v` to find out.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F680 Feature request"
3 | about: Suggest an idea for improving Rails Mini Profiler
4 | title: ''
5 | labels: feature 🚀
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Is your proposal related to a problem?
11 |
12 | (Provide a clear and concise description of what the problem is.)
13 |
14 | ### Describe the solution you'd like
15 |
16 | (Provide a clear and concise description of what you want to happen.)
17 |
18 | ### Describe any alternatives you've considered
19 |
20 | (Let us know about other solutions you've tried or researched.)
21 |
22 | ### Additional context
23 |
24 | (Is there anything else you can add about the proposal? You might want to link to related issues here, if you haven't already.)
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Thanks for contributing to Rails Mini Profiler! 🎉
2 |
3 | Before submitting, please review the [contributor guidelines](/CONTRIBUTING.md). In particular:
4 |
5 | - [ ] Your tests pass on all supported Rails versions. Run `bin/appraisal rspec` to verify.
6 | - [ ] Your code adheres to the repository code style. Run `bin/rake lint` to verify.
7 | - [ ] For functional updates, has the documentation been updated accordingly?
8 |
9 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | daysUntilStale: 60
2 | daysUntilClose: 14
3 | staleLabel: stale
4 | markComment: >
5 | This issue has been automatically marked as stale because it has not had
6 | recent activity. It will be closed if no further activity occurs. Thank you
7 | for your contributions.
8 | exemptProjects: true
9 | closeComment: false
10 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --format documentation
2 | --color
3 | --require spec_helper
4 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | AllCops:
2 | TargetRubyVersion: 2.6
3 | NewCops: enable
4 | SuggestExtensions: false
5 | Exclude:
6 | - vendor/**/*
7 | - node_modules/**/*
8 | - gemfiles/**/*
9 | - db/migrate/*.rb
10 | - bin/*
11 | - !ruby/regexp /schema\.rb$/
12 |
13 | Layout/LineLength:
14 | Max: 120
15 |
16 | Layout/MultilineMethodCallIndentation:
17 | EnforcedStyle: indented_relative_to_receiver
18 |
19 | Metrics/AbcSize:
20 | Max: 20
21 |
22 | Metrics/MethodLength:
23 | CountAsOne: ['array', 'heredoc']
24 | Max: 15
25 |
26 | Metrics/BlockLength:
27 | IgnoredMethods: ['describe', 'context']
28 |
29 | Style/Documentation:
30 | Enabled: false
31 |
32 | Style/OpenStructUse:
33 | Exclude: ['**/*_spec.rb']
34 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "stylelint-config-standard",
4 | "stylelint-config-idiomatic-order"
5 | ],
6 | "plugins": [
7 | "stylelint-order",
8 | "stylelint-prettier",
9 | "stylelint-scss"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | ruby 3.3.0
2 | nodejs 16.4.2
3 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | appraise 'rails-6.0' do
4 | gem 'rails', '~> 6.0.0'
5 |
6 | group :test, :development do
7 | remove_gem 'sprockets-rails'
8 | end
9 | end
10 |
11 | appraise 'rails-6.1' do
12 | gem 'rails', '~> 6.1.0'
13 | group :test, :development do
14 | remove_gem 'sprockets-rails'
15 | end
16 | end
17 |
18 | appraise 'rails-7.0' do
19 | gem 'rails', '~> 7.0.0'
20 | end
21 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | # Specify your gem's dependencies in rails_mini_profiler.gemspec.
6 | gemspec
7 |
8 | group :development do
9 | gem 'annotate', '~> 3.1', github: 'ctran/annotate_models'
10 | gem 'appraisal', '~> 2.4'
11 | gem 'httparty', '~> 0.20.0', require: false
12 | gem 'puma', '~> 6.0'
13 | gem 'rubyzip', '~> 2.3', require: false
14 | end
15 |
16 | group :test, :development do
17 | gem 'activerecord-import', '~> 1.4'
18 | gem 'jb', '~> 0.8'
19 | gem 'pg', '~> 1.5'
20 | gem 'pry', '~> 0.14'
21 | gem 'rubocop', '~> 1.39'
22 | gem 'sprockets-rails'
23 | gem 'sqlite3', '~> 1.5'
24 | gem 'stackprof', '~> 0.2'
25 | end
26 |
27 | group :test do
28 | gem 'rspec-rails', '~> 6.0'
29 | gem 'simplecov', '~> 0.21'
30 | end
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021 hschne
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'bundler/setup'
4 |
5 | APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
6 | load 'rails/tasks/engine.rake'
7 | load 'rails/tasks/statistics.rake'
8 |
9 | require 'bundler/gem_tasks'
10 |
11 | require 'rspec/core/rake_task'
12 | rspec = RSpec::Core::RakeTask.new(:spec)
13 | rspec.verbose = false
14 |
15 | require 'rubocop/rake_task'
16 | RuboCop::RakeTask.new
17 |
18 | task lint: %i[rubocop lint:js lint:css lint:commit]
19 | namespace :lint do
20 | task :js do
21 | system 'npm run lint'
22 | end
23 |
24 | task :css do
25 | system 'npm run lint:scss'
26 | end
27 |
28 | task :commit do
29 | system 'npm run lint:commit'
30 | end
31 | end
32 |
33 | task default: %i[spec rubocop]
34 |
--------------------------------------------------------------------------------
/app/adapters/rails_mini_profiler/database_adapter.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class DatabaseAdapter
5 | class << self
6 | def cast_to_text(column)
7 | if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
8 | # Cast json field to text to have access to the LIKE operator
9 | "#{column}::text"
10 | else
11 | column
12 | end
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/app/assets/config/rails_mini_profiler_manifest.js:
--------------------------------------------------------------------------------
1 | //= link_directory ../stylesheets/rails_mini_profiler .css
2 |
--------------------------------------------------------------------------------
/app/assets/javascripts/rails_mini_profiler.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require rails-ujs
14 | //= require_tree .
15 | //= require rails-mini-profiler
16 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/rails_mini_profiler/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | *= require rails-mini-profiler
16 | */
17 |
--------------------------------------------------------------------------------
/app/controllers/rails_mini_profiler/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class ApplicationController < RailsMiniProfiler.configuration.ui.base_controller
5 | rescue_from ActiveRecord::RecordNotFound, with: ->(error) { handle(error, 404) }
6 |
7 | before_action :check_rmp_user
8 |
9 | private
10 |
11 | def handle(error, status = 500)
12 | respond_to do |format|
13 | format.html { redirect_back(fallback_location: profiled_requests_path, alert: error.to_s) }
14 | format.json { render status: status, json: { message: error.to_s } }
15 | end
16 | end
17 |
18 | def check_rmp_user
19 | user = User.get(request.env).present?
20 | redirect_back(fallback_location: fallback_location) unless user
21 | end
22 |
23 | def fallback_location
24 | defined?(main_app.root_path) ? main_app.root_path : '/'
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/app/controllers/rails_mini_profiler/flamegraphs_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_dependency 'rails_mini_profiler/application_controller'
4 |
5 | module RailsMiniProfiler
6 | class FlamegraphsController < ApplicationController
7 | layout 'rails_mini_profiler/flamegraph'
8 |
9 | before_action :set_flamegraph, only: %i[show]
10 |
11 | def show; end
12 |
13 | private
14 |
15 | def set_flamegraph
16 | @flamegraph = Flamegraph.find_by!(rmp_profiled_request_id: params[:id]).json_data
17 | end
18 |
19 | def configuration
20 | @configuration ||= RailsMiniProfiler.configuration
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/helpers/rails_mini_profiler/application_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module ApplicationHelper
5 | include Pagy::Frontend
6 |
7 | def present(model, presenter_class = nil, **kwargs)
8 | klass = presenter_class || "#{model.class}Presenter".constantize
9 | presenter = klass.new(model, self, **kwargs)
10 | yield(presenter) if block_given?
11 | presenter
12 | end
13 |
14 | def inline_svg(path, options = {})
15 | if defined?(Webpacker::Engine) && RailsMiniProfiler.configuration.ui.webpacker_enabled
16 | path = "media/images/#{path}"
17 | inline_svg_pack_tag(path, options)
18 | else
19 | inline_svg_tag(path, options)
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/helpers/rails_mini_profiler/profiled_requests_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module ProfiledRequestsHelper
5 | include ApplicationHelper
6 |
7 | def formatted_duration(duration)
8 | duration = (duration.to_f / 100)
9 | duration < 1 ? duration : duration.round
10 | end
11 |
12 | def formatted_allocations(allocations)
13 | number_to_human(allocations, units: { unit: '', thousand: 'k', million: 'M', billion: 'B', trillion: 'T' })
14 | end
15 |
16 | def trace_display_name(name)
17 | {
18 | 'sql.active_record': 'ActiveRecord Query',
19 | 'instantiation.active_record': 'ActiveRecord Instantiation',
20 | 'render_template.action_view': 'Render View',
21 | 'render_partial.action_view': 'Render Partial',
22 | 'process_action.action_controller': 'Controller'
23 | }[name.to_sym]
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/app/javascript/images/bookmark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/javascript/images/chart.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/app/javascript/images/check.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/javascript/images/chevron.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/javascript/images/copy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/javascript/images/delete.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/app/javascript/images/filter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/javascript/images/graph.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/app/javascript/images/logo.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/app/javascript/images/search.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/app/javascript/images/setting.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/javascript/images/show.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/app/javascript/js/checklist_controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from "@hotwired/stimulus";
2 |
3 | export default class extends Controller {
4 | static targets = ["count"];
5 |
6 | connect() {
7 | this.setCount();
8 | }
9 |
10 | checkAll() {
11 | this.setAllCheckboxes(true);
12 | this.setCount();
13 | }
14 |
15 | checkNone() {
16 | this.setAllCheckboxes(false);
17 | this.setCount();
18 | }
19 |
20 | onChecked() {
21 | this.setCount();
22 | }
23 |
24 | setAllCheckboxes(checked) {
25 | this.checkboxes.forEach((el) => {
26 | const checkbox = el;
27 |
28 | if (!checkbox.disabled) {
29 | checkbox.checked = checked;
30 | }
31 | });
32 | }
33 |
34 | setCount() {
35 | if (this.hasCountTarget) {
36 | const count = this.selectedCheckboxes.length;
37 | this.countTarget.innerHTML = `${count} selected`;
38 | }
39 | }
40 |
41 | get selectedCheckboxes() {
42 | return this.checkboxes.filter((c) => c.checked);
43 | }
44 |
45 | get checkboxes() {
46 | return new Array(...this.element.querySelectorAll("input[type=checkbox]"));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/javascript/js/clipboard_controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from "@hotwired/stimulus";
2 |
3 | export default class extends Controller {
4 | static targets = ["button", "source"];
5 |
6 | connect() {
7 | if (!this.hasButtonTarget) return;
8 |
9 | this.originalText = this.buttonTarget.innerText;
10 | this.successDuration = 2000;
11 | }
12 |
13 | copy(event) {
14 | event.preventDefault();
15 |
16 | let text = this.sourceTarget.innerText;
17 | const filter = this.data.get("filter");
18 | if (filter) {
19 | text = new RegExp(filter).exec(text)[0];
20 | }
21 | const temporaryInput = document.createElement("textarea");
22 | temporaryInput.value = text;
23 | document.body.appendChild(temporaryInput);
24 | temporaryInput.select();
25 | document.execCommand("copy");
26 | document.body.removeChild(temporaryInput);
27 |
28 | this.copied();
29 | }
30 |
31 | copied() {
32 | if (!this.hasButtonTarget) return;
33 |
34 | if (this.timeout) {
35 | clearTimeout(this.timeout);
36 | }
37 |
38 | const copiedClass = this.data.get("copiedClass");
39 | if (copiedClass) {
40 | this.buttonTarget.classList.add(copiedClass);
41 | }
42 | const copiedMessage = this.data.get("copiedMessage");
43 | const content = this.buttonTarget.innerHTML;
44 | if (copiedMessage) {
45 | this.buttonTarget.innerHTML = copiedMessage;
46 | }
47 |
48 | this.timeout = setTimeout(() => {
49 | this.buttonTarget.classList.remove(copiedClass);
50 | this.buttonTarget.innerHTML = content;
51 | }, this.successDuration);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/javascript/js/enable_controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from "@hotwired/stimulus";
2 |
3 | export default class extends Controller {
4 | static targets = ["enable"];
5 |
6 | enable() {
7 | this.enableTarget.disabled = false;
8 | }
9 |
10 | disable() {
11 | this.enableTarget.disabled = true;
12 | }
13 |
14 | change(event) {
15 | if (event.type.match(/rmp:select:.*/)) {
16 | if (event.detail.count > 0) {
17 | this.enable();
18 | } else {
19 | this.disable();
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/javascript/js/filter_controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from "@hotwired/stimulus";
2 |
3 | export default class extends Controller {
4 | static targets = ["filter"];
5 |
6 | apply() {
7 | location.href = `${window.location.pathname}?${this.params}`;
8 | }
9 |
10 | reset() {
11 | location.href = `${window.location.pathname}`;
12 | }
13 |
14 | post() {
15 | const token = document.head.querySelector(
16 | 'meta[name="csrf-token"]'
17 | ).content;
18 | const path = `${window.location.pathname}/destroy_all?${this.params}`;
19 | fetch(path, {
20 | method: "DELETE",
21 | redirect: "follow",
22 | headers: {
23 | "Content-Type": "application/json",
24 | credentials: "same-origin",
25 | },
26 | body: JSON.stringify({ authenticity_token: token }),
27 | }).then((response) => {
28 | if (response.redirected) {
29 | window.location.href = response.url;
30 | }
31 | });
32 | }
33 |
34 | get params() {
35 | return this.activeFilterTargets()
36 | .map((t) => `${t.name}=${t.value}`)
37 | .join("&");
38 | }
39 |
40 | activeFilterTargets() {
41 | return this.filterTargets.filter(function (target) {
42 | if (target.type === "checkbox" || target.type === "radio")
43 | return target.checked;
44 |
45 | return target.value.length > 0;
46 | });
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/javascript/js/search_controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from "@hotwired/stimulus";
2 |
3 | export default class extends Controller {
4 | static targets = ["field"];
5 |
6 | clear() {
7 | this.eventTarget.value = null;
8 | window.dispatchEvent(new CustomEvent("search-controller:submit", {}));
9 | }
10 |
11 | submit(event) {
12 | event.preventDefault();
13 |
14 | if (event.key === "Enter" || event.type === "click") {
15 | window.dispatchEvent(new CustomEvent("search-controller:submit", {}));
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/javascript/js/select_controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from "@hotwired/stimulus";
2 |
3 | export default class extends Controller {
4 | static targets = ["all", "selectable"];
5 |
6 | selectAll(event) {
7 | const checked = event.target.checked;
8 | this.allTarget.indeterminate = false;
9 | this._setAllCheckboxes(checked);
10 | this._dispatch("change", { count: this.selectedCount });
11 | }
12 |
13 | onSelected() {
14 | this.allTarget.indeterminate = !!this._indeterminate;
15 | this._dispatch("change", { count: this.selectedCount });
16 | }
17 |
18 | get selectedCount() {
19 | return this.selected.length;
20 | }
21 |
22 | get selected() {
23 | return this.selectables.filter((c) => c.checked);
24 | }
25 |
26 | get selectables() {
27 | return new Array(...this.selectableTargets);
28 | }
29 |
30 | _setAllCheckboxes(checked) {
31 | this.selectables.forEach((el) => {
32 | const checkbox = el;
33 |
34 | if (!checkbox.disabled) {
35 | checkbox.checked = checked;
36 | }
37 | });
38 | }
39 |
40 | get _indeterminate() {
41 | return (
42 | this.selected.length !== this.selectableTargets.length &&
43 | this.selected.length > 0
44 | );
45 | }
46 |
47 | _dispatch(name, detail) {
48 | window.dispatchEvent(
49 | new CustomEvent(`rmp:select:${name}`, { bubbles: true, detail })
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/components/buttons.scss:
--------------------------------------------------------------------------------
1 | button,
2 | .button {
3 | display: inline-block;
4 | padding: 0.5em 0.5em;
5 | border: none;
6 | border-radius: 5px;
7 | cursor: pointer;
8 | font-size: 1rem;
9 | text-align: center;
10 | text-decoration: none;
11 |
12 | &:disabled {
13 | background: var(--grey-200);
14 | cursor: not-allowed;
15 | }
16 | }
17 |
18 | .btn-red {
19 | background: var(--red-500);
20 | color: white;
21 | font-weight: 600;
22 | outline: none;
23 |
24 | &:hover {
25 | background: var(--red-600);
26 | }
27 | }
28 |
29 | .btn-grey {
30 | background: var(--grey-200);
31 |
32 | &:hover {
33 | background: var(--grey-100);
34 | }
35 | }
36 |
37 | .btn-white {
38 | border: 1px solid var(--grey-200);
39 | background: white;
40 |
41 | &:hover {
42 | background: var(--grey-100);
43 | }
44 | }
45 |
46 | button:hover {
47 | box-shadow: 0 0.25rem 0.25rem 0 var(--grey-50);
48 | }
49 |
50 | button.none {
51 | padding: 0;
52 | border: none;
53 | background: none;
54 | outline: none;
55 |
56 | &:hover {
57 | box-shadow: none;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/components/dropdown.scss:
--------------------------------------------------------------------------------
1 | .dropdown-body {
2 | padding: 0.33rem 0.5rem;
3 | }
4 |
5 | .dropdown-search-field {
6 | padding: 0.5rem 0.5rem;
7 | border: 1px solid var(--grey-400);
8 | border-radius: 5px;
9 | color: var(--grey-700);
10 | }
11 |
12 | .dropdown-toggle {
13 | display: flex;
14 | align-items: center;
15 | color: inherit;
16 |
17 | &:hover {
18 | color: var(--grey-900);
19 | }
20 | }
21 |
22 | .dropdown-container {
23 | position: absolute;
24 | z-index: 1;
25 | display: flex;
26 | overflow: hidden;
27 | min-width: 240px;
28 | box-sizing: border-box;
29 | flex-direction: column;
30 | border: 1px solid var(--grey-200);
31 | margin-top: 0.3em;
32 | background: white;
33 | border-radius: 5px;
34 | box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
35 | color: var(--grey-400);
36 | cursor: default;
37 | }
38 |
39 | .dropdown-search {
40 | display: flex;
41 | flex-direction: column;
42 | }
43 |
44 | .dropdown-search button {
45 | display: flex;
46 | flex-direction: column;
47 | }
48 |
49 | .dropdown-entry {
50 | display: flex;
51 | padding: 0.33rem 1rem;
52 | text-decoration: none;
53 |
54 | &:hover {
55 | background: var(--grey-100);
56 | color: var(--grey-900);
57 | }
58 | }
59 |
60 | .dropdown-entry input {
61 | margin-right: 0.5em;
62 | }
63 |
64 | .dropdown-header {
65 | display: flex;
66 | align-items: center;
67 | justify-content: space-between;
68 | padding: 0.5rem 1rem;
69 | border-bottom: 1px solid var(--grey-200);
70 | background: var(--grey-100);
71 | font-size: 0.833rem;
72 | text-align: left;
73 | }
74 |
75 | .dropdown-header button {
76 | padding: 0;
77 | border: none;
78 | background: none;
79 | color: var(--grey-500);
80 | font-size: 0.833rem;
81 | outline: none;
82 | text-decoration: underline;
83 |
84 | &:hover {
85 | box-shadow: none;
86 | color: var(--grey-900);
87 | }
88 | }
89 |
90 | .dropdown-footer {
91 | width: 100%;
92 | padding: 0.5rem;
93 | border: none;
94 | background: var(--red-500);
95 | border-radius: 0;
96 | color: white;
97 | font-weight: 600;
98 | outline: none;
99 |
100 | &:hover {
101 | background: var(--red-600);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/components/input.scss:
--------------------------------------------------------------------------------
1 | input[type=submit] {
2 | display: inline-block;
3 | padding: 0.5em 0.5em;
4 | border: none;
5 | border-radius: 5px;
6 | cursor: pointer;
7 | font-size: 1rem;
8 | text-align: center;
9 | text-decoration: none;
10 | }
11 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/components/navbar.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | display: flex;
3 | justify-content: center;
4 | padding: 1.5rem 0;
5 | margin: 0;
6 | background: var(--primary);
7 | box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2);
8 | }
9 |
10 | .header a {
11 | color: white;
12 | text-decoration: none;
13 | }
14 |
15 | .nav {
16 | display: flex;
17 | width: var(--main-width);
18 | justify-content: space-between;
19 | }
20 |
21 | .home {
22 | display: flex;
23 | align-items: center;
24 | text-decoration: none;
25 | }
26 |
27 | .home-logo {
28 | width: 64px;
29 | height: 64px;
30 | }
31 |
32 | .home-title {
33 | padding: 0 0 0 1rem;
34 | margin: 0;
35 | }
36 |
37 | .header-links {
38 | display: flex;
39 | align-items: center;
40 | padding: 0;
41 | margin: 0;
42 | font-weight: 700;
43 | list-style: none;
44 | }
45 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/components/page_header.scss:
--------------------------------------------------------------------------------
1 | .page-header {
2 | padding: 2.5rem 0 2.5rem;
3 | }
4 |
5 | .page-header h1 {
6 | font-size: 28px;
7 | }
8 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/components/pagination.scss:
--------------------------------------------------------------------------------
1 | .pagy-nav {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | padding: 1em 0;
6 | }
7 |
8 | .pagy-nav > .page.disabled,
9 | .pagy-nav > .page.active,
10 | .pagy-nav > .page > a {
11 | padding: 0.5rem 1rem;
12 | border: 1px solid var(--border-color);
13 | border-right: none;
14 | background: white;
15 | color: var(--grey-900);
16 | cursor: pointer;
17 | text-decoration: none;
18 | }
19 |
20 | .pagy-nav > .page.prev a,
21 | .pagy-nav > .page.prev.disabled {
22 | border-radius: 5px 0 0 5px;
23 | }
24 |
25 | .pagy-nav > .page.next a,
26 | .pagy-nav > .page.next.disabled {
27 | border-right: 1px solid var(--border-color);
28 | border-radius: 0 5px 5px 0;
29 | }
30 |
31 | .pagy-nav > .page.active {
32 | border-color: var(--red-500);
33 | background: var(--red-500);
34 | color: white;
35 | cursor: default;
36 |
37 | &:hover {
38 | background: var(--red-500);
39 | }
40 | }
41 |
42 | .pagy-nav > .page.disabled {
43 | color: var(--grey-500);
44 | cursor: default;
45 |
46 | &:hover {
47 | background: white;
48 | }
49 | }
50 |
51 | .pagy-nav > .page.disabled:hover,
52 | .pagy-nav > .page.active:hover,
53 | .pagy-nav > .page > a:hover {
54 | background: var(--grey-100);
55 | }
56 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/components/placeholder.scss:
--------------------------------------------------------------------------------
1 | .placeholder {
2 | display: flex;
3 | width: 100%;
4 | flex-direction: column;
5 | align-items: center;
6 | justify-content: center;
7 | padding-bottom: 2rem;
8 | }
9 |
10 | .placeholder-image {
11 | width: 30%;
12 | height: 30%;
13 | -webkit-filter: grayscale(1) brightness(2.5);
14 | }
15 |
16 | .placeholder-text {
17 | padding: 1rem 0;
18 | color: var(--grey-400);
19 | text-align: center;
20 | }
21 |
22 | .placeholder-text h2 {
23 | padding-bottom: 1rem;
24 | }
25 |
26 | .placeholder-link {
27 | color: var(--grey-400);
28 |
29 | &:visited {
30 | color: var(--grey-400);
31 | }
32 |
33 | &:hover {
34 | color: var(--grey-900);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/components/profiled_request_table.scss:
--------------------------------------------------------------------------------
1 | .table {
2 | width: 100%;
3 | border: hidden 1px var(--border-color);
4 |
5 | /* Hack to get table row borders, see https://stackoverflow.com/a/2586780/2553104 */
6 | border-collapse: collapse;
7 | border-radius: 5px;
8 | box-shadow: 0 0 0 1px var(--border-color);
9 | table-layout: fixed;
10 | }
11 |
12 | .table th,
13 | .table td {
14 | padding: 0.5rem 1rem;
15 | }
16 |
17 | .table thead tr {
18 | color: var(--grey-500);
19 | }
20 |
21 | .table tr:nth-child(even) {
22 | background: var(--grey-50);
23 | }
24 |
25 | .table thead th {
26 | font-weight: 400;
27 | }
28 |
29 | .table-filter-icon {
30 | width: 14px;
31 | margin-left: 0.5rem;
32 | }
33 |
34 | .table tbody tr:not(.no-row) {
35 | border: solid 1px var(--border-color);
36 | color: var(--grey-700);
37 | cursor: pointer;
38 | }
39 |
40 | .table tbody tr:not(.no-row):hover {
41 | background: var(--grey-100);
42 | }
43 |
44 | .request-checkbox {
45 | z-index: 1;
46 | width: 1rem;
47 | height: 1rem;
48 | }
49 |
50 | .request-path {
51 | overflow: hidden;
52 | max-width: 280px;
53 | text-overflow: ellipsis;
54 | white-space: nowrap;
55 | }
56 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/components/trace.scss:
--------------------------------------------------------------------------------
1 | .trace {
2 | display: flex;
3 | align-items: center;
4 | justify-content: flex-start;
5 | padding: 0.25em 0;
6 | list-style: none;
7 | }
8 |
9 | .trace:nth-child(odd) {
10 | background: var(--grey-100);
11 | }
12 |
13 | .trace .trace-bar {
14 | position: relative;
15 | height: 16px;
16 | padding: 0;
17 | margin: 0;
18 | background: linear-gradient(to top right, var(--grey-500), var(--grey-400));
19 | cursor: pointer;
20 | }
21 |
22 | .instantiation-trace .trace-bar {
23 | background: linear-gradient(to top right, var(--green-400), var(--green-300));
24 | }
25 |
26 | .sequel-trace .trace-bar {
27 | background: linear-gradient(to top right, var(--green-500), var(--green-400));
28 | }
29 |
30 | .controller-trace .trace-bar {
31 | background: linear-gradient(to top right, var(--yellow-500), var(--yellow-400));
32 | }
33 |
34 | .render-template-trace .trace-bar,
35 | .render-partial-trace .trace-bar {
36 | background: linear-gradient(to top right, var(--blue-500), var(--blue-400));
37 | }
38 |
39 | .trace-name {
40 | overflow: hidden;
41 | box-sizing: border-box;
42 | padding: 0 0.5em;
43 | margin: 0;
44 | color: var(--grey-400);
45 | font-size: 14px;
46 | text-align: right;
47 | text-overflow: ellipsis;
48 | }
49 |
50 | .trace-payload {
51 | margin: 0;
52 | }
53 |
54 | .sequel-trace-query {
55 | overflow: auto;
56 | max-height: 100px;
57 | padding: 1em 1em;
58 | background: var(--grey-100);
59 | white-space: pre-wrap;
60 | }
61 |
62 | .sequel-trace-binds {
63 | overflow: auto;
64 | max-height: 100px;
65 | padding: 0.5em 1em;
66 | margin: 0 0 1em 0;
67 | background: var(--grey-50);
68 | font-size: 12px;
69 | white-space: pre-wrap;
70 | }
71 |
72 | .backtrace {
73 | display: flex;
74 | flex-direction: row;
75 | align-items: center;
76 | justify-content: space-between;
77 | padding: 0.5em;
78 | background: var(--grey-100);
79 | }
80 |
81 | .backtrace button {
82 | overflow: auto;
83 | height: 20px;
84 | padding: 0;
85 | margin: 0;
86 | background: none;
87 | color: var(--grey-500);
88 | }
89 |
90 | .backtrace button svg {
91 | width: 20px;
92 | height: 20px;
93 | }
94 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/flamegraph.scss:
--------------------------------------------------------------------------------
1 | #wrapper {
2 | width: 100%;
3 | height: 100vh;
4 | }
5 |
6 | #speedscope-iframe {
7 | width: 100%;
8 | height: 100%;
9 | border: none;
10 | }
11 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/flashes.scss:
--------------------------------------------------------------------------------
1 | .flash {
2 | padding: 1rem 1rem;
3 | margin-top: 1rem;
4 | border-radius: 5px;
5 | }
6 |
7 | .flash-error {
8 | background: var(--red-500);
9 | color: white;
10 | }
11 |
12 | .flash-notice {
13 | background: var(--green-400);
14 | color: white;
15 | }
16 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/profiled_requests.scss:
--------------------------------------------------------------------------------
1 | @import 'components/profiled_request_table';
2 |
3 | .search-field {
4 | box-sizing: border-box;
5 | padding: 0.5rem;
6 | border: 1px solid var(--grey-400);
7 | border-radius: 5px;
8 | }
9 |
10 | .profiled-requests-header {
11 | display: flex;
12 | flex-direction: row;
13 | align-items: center;
14 | justify-content: space-between;
15 | }
16 |
17 | .clear-action button {
18 | background: var(--red-500);
19 | color: white;
20 | font-weight: 600;
21 |
22 | &:hover {
23 | background: var(--red-600);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/javascript/stylesheets/traces.scss:
--------------------------------------------------------------------------------
1 | .profiled-request-details {
2 | display: flex;
3 | align-items: center;
4 | justify-content: space-between;
5 | padding-bottom: 2rem;
6 | }
7 |
8 | .request-details-data {
9 | display: flex;
10 | }
11 |
12 | .data-item {
13 | display: flex;
14 | flex-direction: column;
15 | align-items: flex-start;
16 | padding: 0;
17 | margin-right: 3rem;
18 | list-style: none;
19 | }
20 |
21 | .data-item small {
22 | color: var(--grey-400);
23 | }
24 |
25 | .data-item span {
26 | margin: 0.25rem 0;
27 | }
28 |
29 | [class*='request-status-2'],
30 | [class*='request-method-get'] {
31 | background: var(--green-400) !important;
32 | color: white;
33 | }
34 |
35 | [class*='request-status-4'],
36 | [class*='request-method-put'],
37 | [class*='request-method-patch'] {
38 | background: var(--yellow-400) !important;
39 | color: white;
40 | }
41 |
42 | [class*='request-status-5'],
43 | [class*='request-method-delete'] {
44 | background: var(--red-500) !important;
45 | color: white;
46 | }
47 |
48 | .trace-list {
49 | padding: 1rem;
50 | border: 1px solid var(--grey-200);
51 | border-radius: 5px;
52 | }
53 |
54 | .trace-details-table {
55 | width: 100%;
56 | border: hidden 1px var(--border-color);
57 | margin-top: 1em;
58 | border-collapse: collapse;
59 | }
60 |
61 | .trace-details-table tr th {
62 | font-weight: var(--fw-semibold);
63 | }
64 |
65 | .trace-list-header {
66 | display: flex;
67 | align-items: center;
68 | justify-content: space-between;
69 | padding-bottom: 1rem;
70 | }
71 |
72 | .trace-list-filters {
73 | display: flex;
74 | width: 60%;
75 | align-items: center;
76 | justify-content: space-between;
77 | color: var(--grey-500);
78 | }
79 |
--------------------------------------------------------------------------------
/app/models/rails_mini_profiler/application_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class ApplicationRecord < ActiveRecord::Base
5 | if RailsMiniProfiler.storage_configuration.database.present?
6 | establish_connection(RailsMiniProfiler.storage_configuration.database)
7 | end
8 |
9 | self.abstract_class = true
10 |
11 | def self.record_timestamps
12 | # Some applications may disable timestamp setting, but in the context of the engine we always want to record
13 | # timestamps, as engine functionality relies on it.
14 | true
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/models/rails_mini_profiler/flamegraph.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # == Schema Information
4 | #
5 | # Table name: rmp_flamegraphs
6 | #
7 | # id :integer not null, primary key
8 | # rmp_profiled_request_id :integer not null
9 | # data :binary
10 | # created_at :datetime not null
11 | # updated_at :datetime not null
12 | #
13 | # Indexes
14 | #
15 | # index_rmp_flamegraphs_on_rmp_profiled_request_id (rmp_profiled_request_id)
16 | #
17 | module RailsMiniProfiler
18 | class Flamegraph < RailsMiniProfiler::ApplicationRecord
19 | self.table_name = RailsMiniProfiler.storage_configuration.flamegraphs_table
20 |
21 | belongs_to :profiled_request,
22 | class_name: 'RailsMiniProfiler::ProfiledRequest',
23 | foreign_key: :rmp_profiled_request_id
24 |
25 | before_save :compress
26 |
27 | def json_data
28 | @json_data = ActiveSupport::Gzip.decompress(data)
29 | end
30 |
31 | private
32 |
33 | def compress
34 | self.data = ActiveSupport::Gzip.compress(data)
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/app/models/rails_mini_profiler/trace.rb:
--------------------------------------------------------------------------------
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 :integer 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 | # Indexes
20 | #
21 | # index_rmp_traces_on_rmp_profiled_request_id (rmp_profiled_request_id)
22 | #
23 | module RailsMiniProfiler
24 | class Trace < RailsMiniProfiler::ApplicationRecord
25 | self.table_name = RailsMiniProfiler.storage_configuration.traces_table
26 |
27 | belongs_to :profiled_request,
28 | class_name: 'RailsMiniProfiler::ProfiledRequest',
29 | foreign_key: :rmp_profiled_request_id
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/app/presenters/rails_mini_profiler/base_presenter.rb:
--------------------------------------------------------------------------------
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
26 |
--------------------------------------------------------------------------------
/app/presenters/rails_mini_profiler/controller_trace_presenter.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class ControllerTracePresenter < TracePresenter
5 | def label
6 | 'Action Controller'
7 | end
8 |
9 | def view_runtime
10 | payload['view_runtime']
11 | end
12 |
13 | def db_runtime
14 | payload['db_runtime']
15 | end
16 |
17 | def content
18 | content_tag('div') do
19 | content_tag('pre', class: 'trace-payload') do
20 | content_tag(:div, "View Time: #{view_runtime} ms, DB Time: #{db_runtime} ms",
21 | class: 'sequel-trace-query')
22 | end
23 | end
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/app/presenters/rails_mini_profiler/instantiation_trace_presenter.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class InstantiationTracePresenter < TracePresenter
5 | def label
6 | "#{class_name} Instantiation"
7 | end
8 |
9 | def class_name
10 | payload['class_name']
11 | end
12 |
13 | def record_count
14 | payload['record_count']
15 | end
16 |
17 | def db_runtime
18 | payload['db_runtime']
19 | end
20 |
21 | def description
22 | record_string = 'Record'.pluralize(record_count)
23 | "Instantiated #{record_count} #{class_name} #{record_string}"
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/app/presenters/rails_mini_profiler/profiled_request_presenter.rb:
--------------------------------------------------------------------------------
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 | from_time = Time.now
19 | created_at = model.created_at.in_time_zone(Time.zone)
20 | distance = if from_time - created_at < 5.minutes
21 | 'Now'
22 | else
23 | "#{distance_of_time_in_words(from_time, created_at)} ago"
24 | end
25 | time_tag(created_at) { content_tag('span', distance) }
26 | end
27 |
28 | def flamegraph_button
29 | return nil unless RailsMiniProfiler.configuration.flamegraph_enabled
30 |
31 | return nil unless model.flamegraph.present?
32 |
33 | link_to(flamegraph_path(model.id), title: 'Show Flamegraph') do
34 | content_tag('button', 'Flamegraph', class: 'btn-grey')
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/app/presenters/rails_mini_profiler/render_partial_trace_presenter.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class RenderPartialTracePresenter < TracePresenter
5 | def identifier
6 | payload['identifier']
7 | end
8 |
9 | def label
10 | root = Rails.root.to_s.split('/').to_set
11 | id = identifier.split('/').to_set
12 | (root ^ id).drop(2).join('/').reverse.truncate(30).reverse
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/presenters/rails_mini_profiler/render_template_trace_presenter.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class RenderTemplateTracePresenter < TracePresenter
5 | def identifier
6 | payload['identifier']
7 | end
8 |
9 | def label
10 | root = Rails.root.to_s.split('/').to_set
11 | id = identifier.split('/').to_set
12 | (root ^ id).drop(2).join('/').reverse.truncate(30).reverse
13 | end
14 |
15 | def description
16 | "Render #{label}"
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/app/presenters/rails_mini_profiler/rmp_trace_presenter.rb:
--------------------------------------------------------------------------------
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
10 |
--------------------------------------------------------------------------------
/app/presenters/rails_mini_profiler/sequel_trace_presenter.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class SequelTracePresenter < TracePresenter
5 | def label
6 | sql_description
7 | end
8 |
9 | def name
10 | payload['name']
11 | end
12 |
13 | def sql
14 | payload['sql']
15 | end
16 |
17 | def binds
18 | payload['binds']
19 | end
20 |
21 | alias description label
22 |
23 | def content
24 | return nil if transaction?
25 |
26 | content_tag('div') do
27 | content_tag('pre', class: 'trace-payload') do
28 | content_tag(:div, sql, class: 'sequel-trace-query')
29 | end + binding_content
30 | end
31 | end
32 |
33 | private
34 |
35 | def transaction?
36 | model.payload['name'] == 'TRANSACTION'
37 | end
38 |
39 | def schema?
40 | model.payload['name'] == 'SCHEMA'
41 | end
42 |
43 | def sql_description
44 | if transaction?
45 | transaction_description
46 | elsif schema?
47 | 'Load Schema'
48 | elsif model.payload['name'].present?
49 | model.payload['name']
50 | else
51 | model.payload['sql'].truncate(15)
52 | end
53 | end
54 |
55 | def transaction_description
56 | # The raw SQL is something like 'BEGIN TRANSACTION', and we just turn it into 'Begin Transaction', which is less
57 | # loud and nicer to look at.
58 | model.sql.split.map(&:capitalize).join(' ')
59 | end
60 |
61 | def binding_content
62 | return nil if simple_binds.empty?
63 |
64 | content = simple_binds.collect do |hash|
65 | flat = hash.to_a.flatten
66 | "#{flat.first}=#{flat.second}"
67 | end
68 | content_tag(:pre, content.join(', '), class: 'sequel-trace-binds')
69 | end
70 |
71 | def simple_binds
72 | return [] if binds.nil? || binds.empty?
73 |
74 | binds.each_with_object({}) do |hash, object|
75 | name = hash['name']
76 | value = hash['value']
77 | object[name] = value
78 | end
79 | end
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/app/presenters/rails_mini_profiler/trace_presenter.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class TracePresenter < BasePresenter
5 | def initialize(trace, view, context: {})
6 | super(trace, view)
7 | @start = context[:start]
8 | @finish = context[:finish]
9 | @total_duration = context[:total_duration]
10 | @total_allocations = context[:total_allocations]
11 | end
12 |
13 | def label
14 | ''
15 | end
16 |
17 | def description
18 | label
19 | end
20 |
21 | def content
22 | nil
23 | end
24 |
25 | def backtrace
26 | return if model.backtrace.empty?
27 |
28 | model.backtrace.first
29 | end
30 |
31 | def type
32 | # Turn this class name into a dasherized version for use in assigning CSS classes. E.g. 'RmpTracePresenter'
33 | # becomes 'rmp-trace'
34 | self.class.name.demodulize.delete_suffix('Presenter')
35 | .underscore
36 | .dasherize
37 | end
38 |
39 | def duration
40 | formatted_duration(model.duration)
41 | end
42 |
43 | def duration_percent
44 | (model.duration.to_f / @total_duration * 100).round
45 | end
46 |
47 | def allocations
48 | formatted_allocations(model.allocations)
49 | end
50 |
51 | def allocations_percent
52 | (model.allocations.to_f / @total_allocations * 100).round
53 | end
54 |
55 | def from_start
56 | (model.start - @start).to_f / 100
57 | end
58 |
59 | def from_start_percent
60 | ((model.start - @start).to_f / (@finish - @start)).to_f * 100
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/app/search/rails_mini_profiler/base_search.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class BaseSearch
5 | def initialize(params = nil, **kwargs)
6 | config = self.class.config
7 | options = params&.to_h || {}
8 | options = options.merge(kwargs)
9 | @scope = options.delete(:scope) || (config[:scope] && instance_eval(&config[:scope]))
10 | @options = (options.delete(:options) || config[:options]).stringify_keys
11 | @params = options.stringify_keys.slice(*@options.keys)
12 | end
13 |
14 | def results
15 | @results ||= apply
16 | end
17 |
18 | def results?
19 | results.any?
20 | end
21 |
22 | class << self
23 | def scope(&block)
24 | config[:scope] = block
25 | end
26 |
27 | def option(name, options = {}, &block)
28 | name = name.to_s
29 | handler = options[:with] || block
30 |
31 | config[:options][name] = normalize_search_handler(handler, name)
32 |
33 | define_method(name) { @search.param name }
34 | end
35 |
36 | def results(**kwargs)
37 | new(**kwargs).results
38 | end
39 |
40 | def config
41 | @config ||= { options: {} }
42 | end
43 |
44 | private
45 |
46 | def normalize_search_handler(handler, name)
47 | case handler
48 | when Symbol
49 | ->(scope, value) { method(handler).call scope, value }
50 | when Proc
51 | handler
52 | else
53 | ->(scope, value) { scope.where name => value unless value.blank? }
54 | end
55 | end
56 | end
57 |
58 | private
59 |
60 | def apply
61 | @params.inject(@scope) do |scope, (name, value)|
62 | new_scope = instance_exec scope, value, &@options[name]
63 | new_scope || scope
64 | end
65 | end
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/app/search/rails_mini_profiler/profiled_request_search.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class ProfiledRequestSearch < BaseSearch
5 | option(:id) do |scope, value|
6 | scope.where(id: value)
7 | end
8 |
9 | option(:path) do |scope, value|
10 | scope.where('request_path LIKE ?', "%#{value}%")
11 | end
12 |
13 | option(:method) do |scope, value|
14 | scope.where(request_method: value)
15 | end
16 |
17 | option(:media_type) do |scope, value|
18 | scope.where(response_media_type: value)
19 | end
20 |
21 | option(:status) do |scope, value|
22 | value = value.map(&:to_i)
23 | min = value.min
24 | max = value.max + 100
25 | scope.where('response_status >= :lower', lower: min)
26 | .where('response_status < :upper', upper: max)
27 | end
28 |
29 | option(:duration) do |scope, value|
30 | scope.where('duration > :duration', duration: value)
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/app/search/rails_mini_profiler/trace_search.rb:
--------------------------------------------------------------------------------
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
28 |
--------------------------------------------------------------------------------
/app/views/layouts/rails_mini_profiler/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= render 'rails_mini_profiler/shared/head' %>
4 |
5 |
6 | <%= render 'rails_mini_profiler/shared/navbar' %>
7 |
8 |
9 | <%= render 'rails_mini_profiler/shared/flashes' %>
10 | <%= yield %>
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/views/layouts/rails_mini_profiler/flamegraph.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= render 'rails_mini_profiler/shared/head' %>
4 |
5 |
6 |
7 | <%= yield %>
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/views/models/_flamegraph.json.jb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | flamegraph
4 |
--------------------------------------------------------------------------------
/app/views/models/_profiled_request.jb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | profiled_request.attributes
4 |
--------------------------------------------------------------------------------
/app/views/models/_trace.jb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | trace.attributes
4 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/badge.html.erb:
--------------------------------------------------------------------------------
1 |
34 |
35 | <%= inline_svg('logo_variant.svg') %>
36 | <%= (@profiled_request.duration.to_f / 100).round %>ms
37 |
38 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/flamegraphs/show.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/flamegraphs/show.json.jb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | render 'models/flamegraph', flamegraph: @flamegraph
4 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/profiled_requests/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
5 | <%= render "rails_mini_profiler/profiled_requests/shared/header/header" %>
6 | <%= render "rails_mini_profiler/profiled_requests/shared/table/table" %>
7 |
8 | <%== pagy_nav(@pagy) if @pagy.pages > 1 %>
9 |
10 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/profiled_requests/index.json.jb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | render partial: 'models/profiled_request', collection: @profiled_requests, as: :profiled_request
4 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/profiled_requests/shared/header/_header.erb:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/profiled_requests/shared/table/_placeholder.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= inline_svg('logo_variant.svg', class: 'placeholder-image') %>
5 |
6 | No Requests found!
7 | Send some requests to your app or modify your filters
8 |
9 | <%= link_to('Clear Filters', profiled_requests_path, class: 'placeholder-link') %>
10 |
11 | |
12 |
13 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/profiled_requests/shared/table/_table.erb:
--------------------------------------------------------------------------------
1 |
4 | <%= render "rails_mini_profiler/profiled_requests/shared/table/table_head" %>
5 |
6 | <% if @profiled_requests.empty? %>
7 | <%= render "rails_mini_profiler/profiled_requests/shared/table/placeholder" %>
8 | <% else %>
9 | <% @profiled_requests.each do |profiled_request| %>
10 | <%= render "rails_mini_profiler/profiled_requests/shared/table/table_row", profiled_request: profiled_request %>
11 | <% end %>
12 | <% end %>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/profiled_requests/shared/table/_table_row.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= label_tag nil do %>
4 | <%= check_box_tag 'id[]', profiled_request.id,
5 | params.fetch(:selected, []).include?(profiled_request.id),
6 | class: "request-checkbox",
7 | data: {
8 | 'selectable-target': 'selectable',
9 | 'filters-target': 'filter',
10 | action: 'click->selectable#onSelected'
11 | },
12 | onclick: "event.stopPropagation();" %>
13 | <% end %>
14 | |
15 | <%= profiled_request.request_name %> |
16 | <%= profiled_request.request_method %> |
17 | <%= profiled_request.response_status %> |
18 | <%= profiled_request.response_media_type %> |
19 | <%= profiled_request.duration %> ms |
20 | <%= profiled_request.created_at %> |
21 |
22 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/profiled_requests/show.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 | -
8 | Method
9 | <%= @profiled_request.request_method %>
10 |
11 | -
12 | Status
13 | <%= @profiled_request.response_status %>
14 |
15 | -
16 | Response Time
17 | <%= @profiled_request.duration %>ms
18 |
19 | -
20 | Allocations
21 | <%= @profiled_request.allocations %>
22 |
23 |
24 | <%= @profiled_request.flamegraph_button %>
25 |
26 |
27 | <%= render "rails_mini_profiler/profiled_requests/show/trace_list" %>
28 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/profiled_requests/show.json.jb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | request = render 'models/profiled_request', profiled_request: @profiled_request
4 | request[:traces] = render partial: 'models/trace', collection: @traces, as: :trace
5 | request
6 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/profiled_requests/show/_trace.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= trace.label %>
3 |
4 |
5 |
9 |
10 | <%= trace.content %>
11 |
12 | <% if trace.backtrace %>
13 |
30 | <% end %>
31 |
32 |
33 |
34 |
35 | |
36 | Response Time |
37 | Allocations |
38 |
39 |
40 |
41 |
42 | Total |
43 | <%= trace.duration %>ms |
44 | <%= trace.allocations %> |
45 |
46 |
47 | Relative |
48 | <%= trace.duration_percent %>% |
49 | <%= trace.allocations_percent %>% |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/profiled_requests/show/_trace_list.erb:
--------------------------------------------------------------------------------
1 |
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 |
7 | <% @traces.each do |trace| %>
8 | <%= render "rails_mini_profiler/profiled_requests/show/trace", trace: trace %>
9 | <% end %>
10 |
11 | <% end %>
12 |
13 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/profiled_requests/show/_trace_list_placeholder.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= inline_svg('logo_variant.svg', class: 'placeholder-image') %>
5 |
6 | No Traces found!
7 | Modify filters or reload this page
8 |
9 | <%= link_to('Clear Filters', profiled_request_url(@profiled_request.id), class: 'placeholder-link') %>
10 |
11 | |
12 |
13 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/shared/_flashes.html.erb:
--------------------------------------------------------------------------------
1 | <% flash.each do |type, message| %>
2 | <% if type == "alert" %>
3 | <%= message %>
4 | <% end %>
5 | <% if type == "notice" %>
6 | <%= message %>
7 | <% end %>
8 | <% end %>
9 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/shared/_head.erb:
--------------------------------------------------------------------------------
1 |
2 | Rails Mini Profiler
3 | <%= csrf_meta_tags %>
4 | <%= csp_meta_tag %>
5 |
6 | <% if defined?(Webpacker::Engine) && RailsMiniProfiler.configuration.ui.webpacker_enabled %>
7 | <%= javascript_pack_tag "rails-mini-profiler" %>
8 | <%= stylesheet_pack_tag 'rails-mini-profiler' %>
9 | <% else %>
10 | <%= javascript_include_tag "rails_mini_profiler" %>
11 | <%= stylesheet_link_tag "rails_mini_profiler/application", media: "all" %>
12 | <%end %>
13 |
14 |
--------------------------------------------------------------------------------
/app/views/rails_mini_profiler/shared/_navbar.html.erb:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/bin/annotate:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'annotate' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("annotate", "annotate")
30 |
--------------------------------------------------------------------------------
/bin/appraisal:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'appraisal' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("appraisal", "appraisal")
30 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | # This command will automatically be run when you run "rails" with Rails gems
5 | # installed from the root of your application.
6 |
7 | ENGINE_ROOT = File.expand_path('..', __dir__)
8 | ENGINE_PATH = File.expand_path('../lib/rails/mini/profiler/engine', __dir__)
9 | APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
10 |
11 | # Set up gems listed in the Gemfile.
12 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
13 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
14 |
15 | require 'rails/all'
16 | require 'rails/engine/commands'
17 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'rake' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("rake", "rake")
30 |
--------------------------------------------------------------------------------
/bin/rspec:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'rspec' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("rspec-core", "rspec")
30 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["@commitlint/config-conventional"],
3 | rules: {
4 | "body-max-line-length": [2, "always", 120],
5 | "footer-max-line-length": [1, "always", 120],
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
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
12 |
--------------------------------------------------------------------------------
/db/migrate/20210621185018_create_rmp.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateRmp < ActiveRecord::Migration[6.0]
4 | def change
5 | create_table :rmp_profiled_requests, charset: 'utf8' do |t|
6 | t.string :user_id
7 | t.bigint :start
8 | t.bigint :finish
9 | t.integer :duration
10 | t.bigint :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 |
23 | t.index :created_at
24 | end
25 |
26 | create_table :rmp_traces, charset: 'utf8' do |t|
27 | t.belongs_to :rmp_profiled_request, null: false, foreign_key: true
28 | t.string :name
29 | t.bigint :start
30 | t.bigint :finish
31 | t.integer :duration
32 | t.bigint :allocations
33 | t.json :payload
34 | t.json :backtrace
35 |
36 | t.timestamps
37 | end
38 |
39 | create_table :rmp_flamegraphs, charset: 'utf8' do |t|
40 | t.belongs_to :rmp_profiled_request, null: false, foreign_key: true
41 | t.binary :data
42 |
43 | t.timestamps
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.1'
2 |
3 | services:
4 | postgres:
5 | image: postgres:15
6 | environment:
7 | POSTGRES_USER: dummy
8 | POSTGRES_PASSWORD: postgres
9 | volumes:
10 | - postgres-data:/var/lib/postgresql/data
11 | ports:
12 | - 5432:5432
13 | restart: unless-stopped
14 |
15 | volumes:
16 | postgres-data:
17 |
18 |
--------------------------------------------------------------------------------
/docs/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/docs/images/logo.png
--------------------------------------------------------------------------------
/docs/images/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/docs/images/overview.png
--------------------------------------------------------------------------------
/docs/images/trace-details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/docs/images/trace-details.png
--------------------------------------------------------------------------------
/docs/images/trace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/docs/images/trace.png
--------------------------------------------------------------------------------
/gemfiles/rails_6.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "~> 6.0.0"
6 |
7 | group :development do
8 | gem "annotate", "~> 3.1", github: "ctran/annotate_models"
9 | gem "appraisal", "~> 2.4"
10 | gem "httparty", "~> 0.20.0", require: false
11 | gem "puma", "~> 6.0"
12 | gem "rubyzip", "~> 2.3", require: false
13 | end
14 |
15 | group :test, :development do
16 | gem "activerecord-import", "~> 1.4"
17 | gem "jb", "~> 0.8"
18 | gem "pg", "~> 1.4"
19 | gem "pry", "~> 0.14"
20 | gem "rubocop", "~> 1.36"
21 | gem "sqlite3", "~> 1.5"
22 | gem "stackprof", "~> 0.2"
23 | end
24 |
25 | group :test do
26 | gem "rspec-rails", "~> 5.1"
27 | gem "simplecov", "~> 0.21"
28 | end
29 |
30 | gemspec path: "../"
31 |
--------------------------------------------------------------------------------
/gemfiles/rails_6.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "~> 6.1.0"
6 |
7 | group :development do
8 | gem "annotate", "~> 3.1", github: "ctran/annotate_models"
9 | gem "appraisal", "~> 2.4"
10 | gem "httparty", "~> 0.20.0", require: false
11 | gem "puma", "~> 6.0"
12 | gem "rubyzip", "~> 2.3", require: false
13 | end
14 |
15 | group :test, :development do
16 | gem "activerecord-import", "~> 1.4"
17 | gem "jb", "~> 0.8"
18 | gem "pg", "~> 1.4"
19 | gem "pry", "~> 0.14"
20 | gem "rubocop", "~> 1.36"
21 | gem "sqlite3", "~> 1.5"
22 | gem "stackprof", "~> 0.2"
23 | end
24 |
25 | group :test do
26 | gem "rspec-rails", "~> 5.1"
27 | gem "simplecov", "~> 0.21"
28 | end
29 |
30 | gemspec path: "../"
31 |
--------------------------------------------------------------------------------
/gemfiles/rails_7.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "~> 7.0.0"
6 |
7 | group :development do
8 | gem "annotate", "~> 3.1", github: "ctran/annotate_models"
9 | gem "appraisal", "~> 2.4"
10 | gem "httparty", "~> 0.20.0", require: false
11 | gem "puma", "~> 6.0"
12 | gem "rubyzip", "~> 2.3", require: false
13 | end
14 |
15 | group :test, :development do
16 | gem "activerecord-import", "~> 1.4"
17 | gem "jb", "~> 0.8"
18 | gem "pg", "~> 1.4"
19 | gem "pry", "~> 0.14"
20 | gem "rubocop", "~> 1.36"
21 | gem "sprockets-rails"
22 | gem "sqlite3", "~> 1.5"
23 | gem "stackprof", "~> 0.2"
24 | end
25 |
26 | group :test do
27 | gem "rspec-rails", "~> 5.1"
28 | gem "simplecov", "~> 0.21"
29 | end
30 |
31 | gemspec path: "../"
32 |
--------------------------------------------------------------------------------
/lib/generators/rails_mini_profiler/USAGE:
--------------------------------------------------------------------------------
1 | description:
2 | Enable rails-mini-profiler in development for your application.
3 |
--------------------------------------------------------------------------------
/lib/generators/rails_mini_profiler/install_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | # Generators for Rails Mini Profiler
5 | module Generators
6 | # A basic installation generator to help set up users apps
7 | class InstallGenerator < Rails::Generators::Base
8 | source_root File.expand_path('templates', __dir__)
9 |
10 | # Install Rails Mini Profiler to your Rails app
11 | #
12 | # Updates the routes file to mount the engine, adds an initializer and copies a migration.
13 | desc 'Install rails-mini-profiler'
14 |
15 | def install
16 | route("mount RailsMiniProfiler::Engine => '/rails_mini_profiler'")
17 | template 'rails_mini_profiler.rb.erb', 'config/initializers/rails_mini_profiler.rb'
18 | system('rails rails_mini_profiler:install:migrations')
19 | webpacker_install if defined?(Webpacker::Engine)
20 | end
21 |
22 | private
23 |
24 | def webpacker_install
25 | webpacker_config_file = Rails.root.join('config', 'webpacker.yml')
26 | unless File.exist?(webpacker_config_file)
27 | say "Webpacker is not installed. Run 'rails webpacker:install' and rerun installation to complete setup"
28 | return
29 | end
30 |
31 | run 'yarn add @rails-mini-profiler/assets'
32 | webpack_config = YAML.load_file(webpacker_config_file)[Rails.env]
33 | destination = Rails.root.join(webpack_config['source_path'],
34 | webpack_config['source_entry_path'],
35 | 'rails-mini-profiler.js')
36 | template('rails_mini_profiler.js.erb', destination)
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/generators/rails_mini_profiler/templates/rails_mini_profiler.js.erb:
--------------------------------------------------------------------------------
1 | import "@rails-mini-profiler/assets"
2 |
3 | // Import styles
4 | import "@rails-mini-profiler/assets/dist/rails-mini-profiler.css";
5 |
6 | // Import images
7 | //
8 | // You may simplify this when using a glob loader (e.g glob-import-loader)
9 | <%- Dir.chdir(RailsMiniProfiler::Engine.root.join('app/javascript/images')) do -%>
10 | <%- Dir.glob('**/*').each do |file| -%>
11 | import "@rails-mini-profiler/assets/dist/images/<%= file %>";
12 | <%- end -%>
13 | <%- end -%>
14 |
--------------------------------------------------------------------------------
/lib/generators/rails_mini_profiler/templates/rails_mini_profiler.rb.erb:
--------------------------------------------------------------------------------
1 | # Rails Mini Profiler Initializer (<%= RailsMiniProfiler::VERSION %>)
2 |
3 | # Customize to your hearts content. If you remove this file, Rails Mini Profiler will use sensible defaults.
4 | # For more information see https://github.com/hschne/rails-mini-profiler#configuration
5 | RailsMiniProfiler.configure do |config|
6 | # Customize when Rails Mini Profiler should run
7 | config.enabled = proc { |env| Rails.env.development? || env['HTTP_RMP_ENABLED'].present? }
8 |
9 | # Configure Flamegraph generation
10 | config.flamegraph_enabled = true
11 | # config.flamegraph_sample_rate = 0.5
12 |
13 | # Configure endpoints to profile
14 | config.skip_paths = []
15 |
16 | # Configure how Rails Mini Profiler stores profiling information
17 | # config.storage.database = :rmp_database
18 | # config.storage.profiled_requests_table = :rmp_profiled_requests
19 | # config.storage.traces_table = :rmp_traces
20 | # config.storage.flamegraphs_table = :rmp_flamegraphs
21 |
22 | # Configure the Rails Mini Profiler User Interface
23 | # config.ui.badge_enabled = true
24 | # config.ui.badge_position = 'top-left'
25 | # config.ui.base_controller = ApplicationController
26 | # config.ui.page_size = 25
27 | # config.ui.webpacker_enabled = true
28 |
29 | # Customize how users are detected
30 | config.user_provider = proc { |env| Rack::Request.new(env).ip }
31 | end
32 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'forwardable'
4 | require 'inline_svg'
5 | require 'pagy'
6 |
7 | require 'rails_mini_profiler/version'
8 | require 'rails_mini_profiler/engine'
9 |
10 | require 'rails_mini_profiler/models/base_model'
11 | require 'rails_mini_profiler/tracers'
12 | require 'rails_mini_profiler/configuration'
13 |
14 | require 'rails_mini_profiler/user'
15 | require 'rails_mini_profiler/request_context'
16 | require 'rails_mini_profiler/logger'
17 | require 'rails_mini_profiler/request_wrapper'
18 | require 'rails_mini_profiler/response_wrapper'
19 | require 'rails_mini_profiler/guard'
20 | require 'rails_mini_profiler/flamegraph_guard'
21 | require 'rails_mini_profiler/redirect'
22 | require 'rails_mini_profiler/badge'
23 | require 'rails_mini_profiler/middleware'
24 |
25 | # Main namespace for Rails Mini Profiler
26 | module RailsMiniProfiler
27 | class << self
28 | # Create a new configuration object
29 | #
30 | # @return [Configuration] a new configuration
31 | def configuration
32 | @configuration ||= Configuration.new
33 | end
34 |
35 | # Configure Rails Mini Profiler
36 | #
37 | # You may use this to configure where and how Rails Mini Profiler stores profiling and storage information.
38 | #
39 | # @see https://github.com/hschne/rails-mini-profiler#configuration
40 | #
41 | # @yieldreturn [Configuration] a new configuration
42 | def configure
43 | yield(configuration)
44 | end
45 |
46 | # Access storage configuration.
47 | #
48 | #
49 | # @return [Storage] a new storage configuration
50 | def storage_configuration
51 | configuration.storage
52 | end
53 |
54 | # Access the current logger
55 | #
56 | # @return [Logger] the logger instance
57 | def logger
58 | @logger ||= configuration.logger
59 | end
60 |
61 | # Authorize the current user for this request
62 | #
63 | # @param current_user [Object] the current user
64 | #
65 | # @see User#current_user
66 | def authorize!(current_user)
67 | RailsMiniProfiler::User.current_user = current_user
68 | end
69 |
70 | # Set the current user for this request
71 | #
72 | # @param current_user [Object] the current user
73 | #
74 | # @see User#current_user
75 | def current_user=(current_user)
76 | RailsMiniProfiler::User.current_user = current_user
77 | end
78 | end
79 | end
80 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/configuration.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_mini_profiler/configuration/storage'
4 | require 'rails_mini_profiler/configuration/user_interface'
5 |
6 | module RailsMiniProfiler
7 | # The main Rails Mini Profiler configuration object
8 | #
9 | # @!attribute [r] logger
10 | # @return [Logger] the current logger
11 | # @!attribute enabled
12 | # @return [Boolean] if the profiler is enabled
13 | # @!attribute flamegraph_enabled
14 | # @return [Boolean] if Flamegraph recording is enabled
15 | # @!attribute flamegraph_sample_rate
16 | # @return [Float] the sample rate in samples per millisecond
17 | # @!attribute skip_paths
18 | # @return [Array] a list of regex patterns for paths to skip
19 | # @!attribute storage
20 | # @return [Storage] the storage configuration
21 | # @!attribute tracers
22 | # @return [Array] the list of enabled tracers
23 | # @!attribute ui
24 | # @return [UserInterface] the ui configuration
25 | # @!attribute user_provider
26 | # @return [Proc] a proc to identify a user based on a rack env
27 | class Configuration
28 | attr_reader :logger
29 |
30 | attr_accessor :enabled,
31 | :flamegraph_enabled,
32 | :flamegraph_sample_rate,
33 | :skip_paths,
34 | :storage,
35 | :tracers,
36 | :ui,
37 | :user_provider
38 |
39 | def initialize(**kwargs)
40 | reset
41 | kwargs.each { |key, value| instance_variable_set("@#{key}", value) }
42 | end
43 |
44 | # Reset the configuration to default values
45 | def reset
46 | @enabled = proc { |_env| Rails.env.development? || Rails.env.test? }
47 | @flamegraph_enabled = true
48 | @flamegraph_sample_rate = 0.5
49 | @logger = RailsMiniProfiler::Logger.new(Rails.logger)
50 | @skip_paths = []
51 | @storage = Storage.new
52 | @tracers = %i[controller instantiation sequel view rmp]
53 | @ui = UserInterface.new
54 | @user_provider = proc { |env| Rack::Request.new(env).ip }
55 | end
56 |
57 | # Set the logger
58 | #
59 | # @param logger [Logger]
60 | # The logger to be used. If set to nil, the Rails default logger is used and the log level set to fatal
61 | def logger=(logger)
62 | if logger.nil?
63 | @logger.level = Logger::FATAL
64 | else
65 | @logger = logger
66 | end
67 | end
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/configuration/storage.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | # Configure how profiling data is stored within your Rails app.
5 | #
6 | # @!attribute database
7 | # @return [Symbol] which database to connect to
8 | # @!attribute profiled_requests_table
9 | # @return [Symbol] where to store profiled requests
10 | # @!attribute traces_table
11 | # @return [Symbol] where to store traces
12 | # @!attribute flamegraphs_table
13 | # @return [Symbol] where to store flamegraphs
14 | class Storage
15 | class << self
16 | # Construct a new configuration instance
17 | #
18 | # @return [Storage] a new storage configuration
19 | def configuration
20 | @configuration ||= new
21 | end
22 |
23 | # Configure how profiling data is stored
24 | #
25 | # @yieldreturn [Storage] a new storage configuration object
26 | def configure
27 | yield(configuration)
28 | configuration
29 | end
30 | end
31 |
32 | attr_accessor :database, :profiled_requests_table, :traces_table, :flamegraphs_table
33 |
34 | def initialize(**kwargs)
35 | defaults!
36 | kwargs.each { |key, value| instance_variable_set("@#{key}", value) }
37 | end
38 |
39 | # Reset the configuration to default values
40 | def defaults!
41 | @database = nil
42 | @profiled_requests_table = 'rmp_profiled_requests'
43 | @flamegraphs_table = 'rmp_flamegraphs'
44 | @traces_table = 'rmp_traces'
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/engine.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | # The Rails Mini Profiler engine
5 | #
6 | # Injects a a custom [Middleware] into an existing Rails app to record request profiling information.
7 | class Engine < ::Rails::Engine
8 | isolate_namespace RailsMiniProfiler
9 |
10 | initializer 'rails_mini_profiler.add_middleware' do |app|
11 | app.middleware.use(RailsMiniProfiler::Middleware)
12 | end
13 |
14 | config.generators do |g|
15 | g.test_framework :rspec
16 | end
17 |
18 | initializer 'rails_mini_profiler_add_static assets' do |app|
19 | app.middleware.insert_before(ActionDispatch::Static, ActionDispatch::Static, "#{root}/public")
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
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/flamegraph_guard.rb:
--------------------------------------------------------------------------------
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 | if StackProf.running?
15 | RailsMiniProfiler.logger.error('Stackprof is already running, cannot record Flamegraph')
16 | return block.call
17 | end
18 |
19 | flamegraph, result = record_flamegraph(block)
20 | unless flamegraph
21 | RailsMiniProfiler.logger.error('Failed to record Flamegraph, possibly due to concurrent requests')
22 | return result
23 | end
24 |
25 | @request_context.flamegraph = flamegraph.to_json
26 | result
27 | end
28 |
29 | private
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 |
40 | def enabled?
41 | defined?(StackProf) && StackProf.respond_to?(:run) && config_enabled?
42 | end
43 |
44 | def config_enabled?
45 | params = CGI.parse(@request.query_string).transform_values(&:first).with_indifferent_access
46 | return params[:rmp_flamegraph] if params[:rmp_flamegraph]
47 |
48 | RailsMiniProfiler.configuration.flamegraph_enabled
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/guard.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | # Encapsulates guard conditions on whether or not to run certain parts of the profiler.
5 | class Guard
6 | # @param request_context [RequestContext] the current request context
7 | # @param configuration [Configuration] the current configuration
8 | def initialize(request_context, configuration: RailsMiniProfiler.configuration)
9 | @request_context = request_context
10 | @request = request_context.request
11 | @configuration = configuration
12 | end
13 |
14 | # Whether or not to profile
15 | #
16 | # Profiling is disabled the profiler has been flat out disabled in the configuration or if the current request path
17 | # matches on of the ignored paths.
18 | #
19 | # @return [Boolean] false if no profiling should be done
20 | def profile?
21 | return false unless enabled?
22 |
23 | return false if ignored_path?
24 |
25 | true
26 | end
27 |
28 | private
29 |
30 | # Is the path of the current request an ignored one?
31 | #
32 | # @return [Boolean] true if the path is ignored. Per default, paths going to the engine itself are ignored, as are
33 | # asset requests, and the paths the user has configured.
34 | def ignored_path?
35 | return true if /#{Engine.routes.find_script_name({})}/.match?(@request.path)
36 |
37 | return true if asset_path?
38 |
39 | return true if actioncable_request?
40 |
41 | ignored_paths = @configuration.skip_paths
42 | return true if Regexp.union(ignored_paths).match?(@request.path)
43 |
44 | false
45 | end
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 |
63 | # Is the profiler enabled?
64 | #
65 | # Takes into account the current request env to decide if the profiler is enabled.
66 | #
67 | # @return [Boolean] false if the profiler is disabled
68 | def enabled?
69 | enabled = @configuration.enabled
70 | return enabled unless enabled.respond_to?(:call)
71 |
72 | enabled.call(@request.env)
73 | end
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/logger.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | # Construct a new custom logger to log from within the engine
5 | module Logger
6 | # Extends a logger with additional formatting
7 | #
8 | # @return [Logger] a customized logger
9 | def self.new(logger)
10 | logger = logger.dup
11 |
12 | logger.formatter = logger.formatter ? logger.formatter.dup : ActiveSupport::Logger::SimpleFormatter.new
13 |
14 | logger.formatter.extend Formatter
15 | logger.extend(self)
16 | end
17 |
18 | # Custom formatter to add a RailsMiniProfiler tag to log messages
19 | module Formatter
20 | def call(severity, timestamp, progname, msg)
21 | super(severity, timestamp, progname, "[RailsMiniProfiler] #{msg}")
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/middleware.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class Middleware
5 | def initialize(app)
6 | @app = app
7 | @config = RailsMiniProfiler.configuration
8 | @registry = Tracers::Registry.setup!(@config)
9 | Tracers::Subscriptions.setup!(@registry.tracers.keys) { |trace| track_trace(trace) }
10 | @trace_factory = Tracers::TraceFactory.new(@registry)
11 | end
12 |
13 | def call(env)
14 | request = RequestWrapper.new(env)
15 | request_context = RequestContext.new(request)
16 | return @app.call(env) unless Guard.new(request_context).profile?
17 |
18 | result = with_tracing(request_context) { profile(request_context) }
19 | return result unless request_context.authorized?
20 |
21 | status, headers, body = result
22 | request_context.response = ResponseWrapper.new(body, status, headers)
23 | complete!(request_context)
24 | request_context.saved? ? render_response(request_context).to_a : result
25 | ensure
26 | User.current_user = nil
27 | end
28 |
29 | def traces
30 | Thread.current[:rails_mini_profiler_traces]
31 | end
32 |
33 | def traces=(traces)
34 | Thread.current[:rails_mini_profiler_traces] = traces
35 | end
36 |
37 | def track_trace(event)
38 | return if traces.nil?
39 |
40 | trace = @trace_factory.create(event)
41 | traces.append(trace) unless trace.is_a?(RailsMiniProfiler::Tracers::NullTrace)
42 | end
43 |
44 | private
45 |
46 | def complete!(request_context)
47 | request_context.save_results!
48 | true
49 | rescue ActiveRecord::ActiveRecordError => e
50 | RailsMiniProfiler.logger.error("Could not save profile: #{e}")
51 | false
52 | end
53 |
54 | def render_response(request_context)
55 | redirect = Redirect.new(request_context).render
56 | return redirect if redirect
57 |
58 | Badge.new(request_context).render
59 | end
60 |
61 | def profile(request_context)
62 | ActiveSupport::Notifications.instrument('rails_mini_profiler.total_time') do
63 | request = request_context.request
64 | FlamegraphGuard.new(request_context).record { @app.call(request.env) }
65 | end
66 | end
67 |
68 | def with_tracing(request_context)
69 | self.traces = []
70 | result = yield
71 | request_context.traces = traces
72 | self.traces = nil
73 | result
74 | end
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/models/base_model.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | # Thin wrappers around request/response classes
5 | #
6 | # @api private
7 | module Models
8 | # A pseudo model to be used to wrap profiling information. We can't use regular models, as their connecting
9 | # to the database results in problems when profiling.
10 | class BaseModel
11 | include ActiveModel::Model
12 |
13 | def initialize(*_args, **attributes)
14 | super(attributes)
15 | end
16 |
17 | def to_h
18 | instance_variables
19 | .each_with_object({}) { |var, hash| hash[var.to_s.delete('@')] = instance_variable_get(var) }
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/redirect.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | # Renders a redirect response if the user should be redirected from the original request
5 | class Redirect
6 | include Engine.routes.url_helpers
7 |
8 | # @param request_context [RequestContext] the current request context
9 | def initialize(request_context)
10 | @request = request_context.request
11 | @profiled_request = request_context.profiled_request
12 | end
13 |
14 | # Renders a redirect to a specific resource under certain conditions
15 | #
16 | # When the user requests a Flamegraph using a parameter for a specific request, they are being served a redirect.
17 | #
18 | # @return [Boolean] false if no redirect happens
19 | # @return [Array] response with status 302 and the new location to redirect to
20 | def render
21 | params = CGI.parse(@request.query_string).transform_values(&:first).with_indifferent_access
22 | return redirect_to(flamegraph_path(@profiled_request.id)) if params[:rmp_flamegraph].present?
23 |
24 | false
25 | end
26 |
27 | private
28 |
29 | def redirect_to(location)
30 | [302, { 'Location' => location, 'Content-Type' => 'text/html' }, ['Moved Temporarily']]
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/request_wrapper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | # A convenience wrapper extending {Rack::Request}
5 | #
6 | # @api private
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
13 |
14 | body = super.read
15 | super.rewind
16 | body
17 | end
18 |
19 | # The request headers
20 | #
21 | # @return [Hash] the request headers
22 | def headers
23 | env.select { |k, _v| k.start_with? 'HTTP_' } || {}
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/response_wrapper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
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 | def body
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
26 | end
27 |
28 | def json?
29 | media_type =~ %r{application/json}
30 | end
31 |
32 | def xml?
33 | media_type =~ %r{application/xml}
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_mini_profiler/tracers/registry'
4 | require 'rails_mini_profiler/tracers/subscriptions'
5 | require 'rails_mini_profiler/tracers/trace'
6 | require 'rails_mini_profiler/tracers/tracer'
7 | require 'rails_mini_profiler/tracers/controller_tracer'
8 | require 'rails_mini_profiler/tracers/instantiation_tracer'
9 | require 'rails_mini_profiler/tracers/sequel_tracker'
10 | require 'rails_mini_profiler/tracers/sequel_tracer'
11 | require 'rails_mini_profiler/tracers/view_tracer'
12 | require 'rails_mini_profiler/tracers/rmp_tracer'
13 | require 'rails_mini_profiler/tracers/null_trace'
14 | require 'rails_mini_profiler/tracers/trace_factory'
15 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers/controller_tracer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module Tracers
5 | class ControllerTracer < Tracer
6 | class << self
7 | def subscribes_to
8 | 'process_action.action_controller'
9 | end
10 |
11 | def build_from(event)
12 | new(event).trace
13 | end
14 |
15 | def presents
16 | ControllerTracePresenter
17 | end
18 | end
19 |
20 | def trace
21 | @event[:payload] = @event[:payload]
22 | .slice(:view_runtime, :db_runtime)
23 | .reject { |_k, v| v.blank? }
24 | .transform_values { |value| value&.round(2) }
25 | super
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers/instantiation_tracer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module Tracers
5 | class InstantiationTracer < Tracer
6 | class << self
7 | def subscribes_to
8 | 'instantiation.active_record'
9 | end
10 |
11 | def presents
12 | InstantiationTracePresenter
13 | end
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers/null_trace.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module Tracers
5 | class NullTrace < Trace; end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers/registry.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module Tracers
5 | # This serves as a central store for tracers. Based on the configuration, indidual tracers are registered and are
6 | # then available to subscribe to events or render traced events.
7 | #
8 | # @api private
9 | class Registry
10 | class << self
11 | def setup!(config)
12 | new(config)
13 | end
14 | end
15 |
16 | def initialize(config)
17 | @config = config
18 | @config.tracers.each { |tracer| add(tracer) }
19 | end
20 |
21 | # Tracers that are available in the application, indexed by events they subscribe to.
22 | #
23 | # @return [Hash] a hash where keys are event names and values are the corresponding tracers
24 | def tracers
25 | @tracers ||=
26 | tracer_map
27 | .values
28 | .each_with_object({}) do |tracer, obj|
29 | subscriptions = wrap(tracer.subscribes_to)
30 | subscriptions.each { |subscription| obj[subscription] = tracer }
31 | end
32 | end
33 |
34 | # Presenters that are available in the application, indexed by events they should present.
35 | #
36 | # @return [Hash] a hash where keys are event names and values are the corresponding presenters
37 | def presenters
38 | @presenters ||=
39 | tracer_map
40 | .values
41 | .each_with_object({}) do |tracer, obj|
42 | presenters = tracer.presents
43 | if presenters.is_a?(Hash)
44 | obj.merge!(presenters)
45 | else
46 | obj[tracer.subscribes_to] = presenters
47 | end
48 | end
49 | end
50 |
51 | private
52 |
53 | def tracer_map
54 | @tracer_map ||= {}
55 | end
56 |
57 | def add(tracer)
58 | tracer = tracer.to_sym
59 | tracer = "#{tracer.to_s.camelize}Tracer"
60 | constant = "RailsMiniProfiler::Tracers::#{tracer}".safe_constantize
61 |
62 | tracer_map[tracer] = constant if constant
63 | end
64 |
65 | def wrap(object)
66 | if object.nil?
67 | []
68 | elsif object.respond_to?(:to_ary)
69 | object.to_ary || [object]
70 | else
71 | [object]
72 | end
73 | end
74 | end
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers/rmp_tracer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module Tracers
5 | class RmpTracer < Tracer
6 | class << self
7 | def subscribes_to
8 | 'rails_mini_profiler.total_time'
9 | end
10 |
11 | def presents
12 | RmpTracePresenter
13 | end
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers/sequel_tracer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module Tracers
5 | class SequelTracer < Tracer
6 | class << self
7 | def subscribes_to
8 | 'sql.active_record'
9 | end
10 |
11 | def build_from(event)
12 | new(event).trace
13 | end
14 |
15 | def presents
16 | SequelTracePresenter
17 | end
18 | end
19 |
20 | def trace
21 | return NullTrace.new if ignore?
22 |
23 | payload = @event[:payload].slice(:name, :sql, :binds, :type_casted_binds)
24 | typecasted_binds = payload[:type_casted_binds]
25 | # Sometimes, typecasted binds are a proc. Not sure why. In those instances, we extract the typecasted
26 | # values from the proc by executing call.
27 | typecasted_binds = typecasted_binds.call if typecasted_binds.respond_to?(:call)
28 | payload[:binds] = transform_binds(payload[:binds], typecasted_binds)
29 | payload.delete(:type_casted_binds)
30 | payload.reject { |_k, v| v.blank? }
31 | @event[:payload] = payload
32 | super
33 | end
34 |
35 | private
36 |
37 | def transform_binds(binds, type_casted_binds)
38 | binds.each_with_object([]).with_index do |(binding, object), i|
39 | name = binding.name
40 | value = type_casted_binds[i]
41 | object << { name: name, value: value }
42 | end
43 | end
44 |
45 | def ignore?
46 | payload = @event[:payload]
47 | !SqlTracker.new(name: payload[:name], query: payload[:sql]).track?
48 | end
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers/sequel_tracker.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module Tracers
5 | # Utility to clean up and simplify SQL Notification events.
6 | #
7 | # @api private
8 | class SqlTracker
9 | TRACKED_SQL_COMMANDS = %w[SELECT INSERT UPDATE DELETE].freeze
10 | UNTRACKED_NAMES = %w[SCHEMA].freeze
11 | UNTRACKED_TABLES = %w[
12 | SCHEMA_MIGRATIONS
13 | SQLITE_MASTER
14 | ACTIVE_MONITORING_METRICS
15 | SQLITE_TEMP_MASTER
16 | SQLITE_VERSION
17 | AR_INTERNAL_METADATA
18 | ].freeze
19 |
20 | def initialize(query:, name:)
21 | @query = query.to_s.upcase
22 | @name = name.to_s.upcase
23 | end
24 |
25 | def track?
26 | query.start_with?(*TRACKED_SQL_COMMANDS) &&
27 | !name.start_with?(*UNTRACKED_NAMES) &&
28 | !untracked_tables?
29 | end
30 |
31 | private
32 |
33 | attr_reader :query, :name
34 |
35 | def untracked_tables?
36 | UNTRACKED_TABLES.any? { |table| query.include?(table) }
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers/subscriptions.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module Tracers
5 | # Subscribe to application events. This is used during engine startup.
6 | # @api private
7 | class Subscriptions
8 | class << self
9 | # Subscribe to each individual active support event using a callback.
10 | def setup!(subscriptions, &callback)
11 | subscriptions.each do |event|
12 | subscribe(event, &callback)
13 | end
14 | end
15 |
16 | private
17 |
18 | def subscribe(*subscriptions, &callback)
19 | subscriptions.each do |subscription|
20 | ActiveSupport::Notifications.subscribe(subscription) do |event|
21 | callback.call(event)
22 | end
23 | end
24 | end
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers/trace.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module Tracers
5 | # A simplified representation of a trace.
6 | #
7 | # Is transformed into [RailsMiniProfiler::Trace] when recording has finished.
8 | #
9 | # @see https://guides.rubyonrails.org/active_support_instrumentation.html
10 | #
11 | # @!attribute id
12 | # @return [Integer] the trace ID
13 | # @!attribute name
14 | # @return [Integer] the trace type.
15 | # @!attribute start
16 | # @return [Integer] the trace start as microsecond timestamp
17 | # @!attribute finish
18 | # @return [Integer] the trace finish as microsecond timestamp
19 | # @!attribute duration
20 | # @return [Integer] the trace duration
21 | # @!attribute payload
22 | # @return [Hash] a subset of trace data
23 | # @!attribute backtrace
24 | # @return [String] the line where this trace was recorded
25 | # @!attribute allocations
26 | # @return [Integer] the number of allocations
27 | # @!attribute created_at
28 | # @return [DateTime] the creation date
29 | # @!attribute updated_at
30 | # @return [DateTime] the last updated date
31 | #
32 | # @api private
33 | class Trace < RailsMiniProfiler::Models::BaseModel
34 | attr_accessor :id, :name, :start, :finish, :duration, :payload, :backtrace, :allocations, :created_at, :updated_at
35 |
36 | def ignore?
37 | false
38 | end
39 |
40 | def transform!
41 | self
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers/trace_factory.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module Tracers
5 | # Creates traces based on the tracers registered with the application
6 | #
7 | # For example, an event with name 'sequel' will result in the lookup of a tracers that responds to that particular
8 | # event. The tracer itself will then build the trace from the event.
9 | #
10 | # @api private
11 | class TraceFactory
12 | def initialize(registry)
13 | @tracers = registry.tracers
14 | end
15 |
16 | # Create a new trace from an event
17 | #
18 | # @param event [ActiveSupport::Notifications::Event] an event from the application
19 | #
20 | # @return [Trace] a processed trace
21 | def create(event)
22 | tracer = @tracers[event.name] || Tracer
23 | tracer.build_from(event)
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers/tracer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module Tracers
5 | class Tracer
6 | TIMESTAMP_MULTIPLIER = Rails::VERSION::MAJOR < 7 ? 100 : 1
7 |
8 | class << self
9 | def subscribes_to
10 | []
11 | end
12 |
13 | def presents
14 | TracePresenter
15 | end
16 |
17 | def build_from(event)
18 | new(event).trace
19 | end
20 | end
21 |
22 | def initialize(event)
23 | @event = event_data(event)
24 | end
25 |
26 | def trace
27 | Trace.new(**@event)
28 | end
29 |
30 | private
31 |
32 | def event_data(event)
33 | # Rails 7 changes event timings and now uses CPU milliseconds as float for start and end. We multiply by 100
34 | # to convert the float with precision of 2 digits to integer, because integers are just easier to store and
35 | # process than floats.
36 | #
37 | # See https://github.com/rails/rails/commit/81d0dc90becfe0b8e7f7f26beb66c25d84b8ec7f
38 | start = (event.time.to_f * TIMESTAMP_MULTIPLIER).to_i
39 | finish = (event.end.to_f * TIMESTAMP_MULTIPLIER).to_i
40 | {
41 | name: event.name,
42 | start: start,
43 | finish: finish,
44 | duration: (event.duration.to_f * 100).to_i,
45 | allocations: event.allocations,
46 | backtrace: Rails.backtrace_cleaner.clean(caller),
47 | payload: event.payload
48 | }
49 | end
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/tracers/view_tracer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | module Tracers
5 | class ViewTracer < Tracer
6 | class << self
7 | def subscribes_to
8 | %w[render_template.action_view render_partial.action_view]
9 | end
10 |
11 | def build_from(event)
12 | new(event).trace
13 | end
14 |
15 | def presents
16 | {
17 | 'render_template.action_view' => RenderTemplateTracePresenter,
18 | 'render_partial.action_view' => RenderPartialTracePresenter
19 | }
20 | end
21 | end
22 |
23 | def trace
24 | @event[:payload].slice!(:identifier, :count)
25 | super
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/user.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | class User
5 | class << self
6 | def current_user
7 | Thread.current[:rails_mini_profiler_current_user]
8 | end
9 |
10 | def get(env)
11 | new(Thread.current[:rails_mini_profiler_current_user], env).current_user
12 | end
13 |
14 | def authorize(user)
15 | Thread.current[:rails_mini_profiler_current_user] = user
16 | end
17 |
18 | def current_user=(user)
19 | Thread.current[:rails_mini_profiler_current_user] = user
20 | end
21 | end
22 |
23 | def initialize(current_user, env)
24 | @current_user = current_user
25 | @env = env
26 | end
27 |
28 | def current_user
29 | @current_user ||= find_current_user
30 | end
31 |
32 | def find_current_user
33 | return if Rails.env.production?
34 |
35 | user = RailsMiniProfiler.configuration.user_provider.call(@env)
36 | User.current_user = user
37 | user
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/rails_mini_profiler/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RailsMiniProfiler
4 | VERSION = '0.7.3'
5 | end
6 |
--------------------------------------------------------------------------------
/lib/tasks/rails_mini_profiler_tasks.rake:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | namespace :rails_mini_profiler do
4 | desc 'Install rails_mini_profiler'
5 | task :install do
6 | system('rails g rails_mini_profiler:install install')
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@rails-mini-profiler/assets",
3 | "version": "0.7.3",
4 | "description": "Performance profiling for your Rails app, made simple",
5 | "main": "dist/rails-mini-profiler.js",
6 | "files": [
7 | "dist/**/*"
8 | ],
9 | "scripts": {
10 | "test": "jest",
11 | "lint": "eslint --ignore-path .gitignore .",
12 | "lint:scss": "stylelint --ignore-path .gitignore **/*.scss",
13 | "lint:commit": "commitlint --from main",
14 | "watch": "browser-sync start --proxy localhost:3000 --files 'app/javascript/**/*,app/views/**/*' | rollup -c -w",
15 | "build": "NODE_ENV=production rollup -c"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/hschne/rails-mini-profiler.git"
20 | },
21 | "author": "Hans Schnedlitz",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/hschne/rails-mini-profiler/issues"
25 | },
26 | "homepage": "https://github.com/hschne/rails-mini-profiler#readme",
27 | "devDependencies": {
28 | "@babel/core": "^7.15.0",
29 | "@babel/eslint-parser": "^7.15.0",
30 | "@babel/preset-env": "^7.15.0",
31 | "@commitlint/cli": "^16.1.0",
32 | "@commitlint/config-conventional": "^16.0.0",
33 | "@rollup/plugin-babel": "^5.3.0",
34 | "@rollup/plugin-commonjs": "^20.0.0",
35 | "@rollup/plugin-eslint": "^8.0.1",
36 | "@rollup/plugin-node-resolve": "^13.0.4",
37 | "@rollup/plugin-replace": "^3.0.0",
38 | "autoprefixer": "^10.3.1",
39 | "browser-sync": "^3.0.2",
40 | "eslint": "^7.32.0",
41 | "eslint-config-prettier": "^8.3.0",
42 | "less": "^4.1.1",
43 | "node-sass": "^7.0.3",
44 | "postcss": "^8.3.6",
45 | "prettier": "2.3.2",
46 | "rollup": "^2.56.1",
47 | "rollup-plugin-copy": "^3.4.0",
48 | "rollup-plugin-postcss": "^4.0.0",
49 | "rollup-plugin-scss": "^3.0.0",
50 | "rollup-plugin-terser": "^7.0.2",
51 | "sass": "^1.37.5",
52 | "stylelint": "^13.13.1",
53 | "stylelint-config-idiomatic-order": "^8.1.0",
54 | "stylelint-config-standard": "^22.0.0",
55 | "stylelint-order": "^4.1.0",
56 | "stylelint-prettier": "^1.2.0",
57 | "stylelint-scss": "^3.20.1"
58 | },
59 | "dependencies": {
60 | "@hotwired/stimulus": "^3.0.0",
61 | "tailwindcss-stimulus-components": "^3.0.0",
62 | "tippy.js": "^6.3.1"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/public/rails_mini_profiler/speedscope/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Jamie Wong
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/public/rails_mini_profiler/speedscope/favicon-16x16.f74b3187.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/public/rails_mini_profiler/speedscope/favicon-16x16.f74b3187.png
--------------------------------------------------------------------------------
/public/rails_mini_profiler/speedscope/favicon-32x32.bc503437.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/public/rails_mini_profiler/speedscope/favicon-32x32.bc503437.png
--------------------------------------------------------------------------------
/public/rails_mini_profiler/speedscope/index.html:
--------------------------------------------------------------------------------
1 | speedscope
2 |
--------------------------------------------------------------------------------
/public/rails_mini_profiler/speedscope/release.txt:
--------------------------------------------------------------------------------
1 | speedscope@1.15.0
2 | Sat Oct 22 00:19:31 HKT 2022
3 | 81a6f29ad1eb632683429084bd6c5f497667fb5e
4 |
--------------------------------------------------------------------------------
/public/rails_mini_profiler/speedscope/reset.8c46b7a1.css:
--------------------------------------------------------------------------------
1 | a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:initial}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:"";content:none}table{border-collapse:collapse;border-spacing:0}html{overflow:hidden}body,html{height:100%}body{overflow:auto}
2 | /*# sourceMappingURL=reset.8c46b7a1.css.map */
--------------------------------------------------------------------------------
/public/rails_mini_profiler/speedscope/reset.8c46b7a1.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["reset.css"],"names":[],"mappings":"AAIA,2ZAaC,QAAS,CACT,SAAU,CACV,QAAS,CACT,cAAe,CACf,YAAa,CACb,sBACD,CAEA,8EAEC,aACD,CACA,KACC,aACD,CACA,MACC,eACD,CACA,aACC,WACD,CACA,oDAEC,UAAW,CACX,YACD,CACA,MACC,wBAAyB,CACzB,gBACD,CAIA,KACI,eAEJ,CACA,UAFI,WAKJ,CAHA,KAEI,aACJ","file":"reset.8c46b7a1.css","sourceRoot":"../../assets","sourcesContent":["/* http://meyerweb.com/eric/tools/css/reset/\n v2.0 | 20110126\n License: none (public domain)\n*/\nhtml, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, big, cite, code,\ndel, dfn, em, img, ins, kbd, q, s, samp,\nsmall, strike, strong, sub, sup, tt, var,\nb, u, i, center,\ndl, dt, dd, ol, ul, li,\nfieldset, form, label, legend,\ntable, caption, tbody, tfoot, thead, tr, th, td,\narticle, aside, canvas, details, embed,\nfigure, figcaption, footer, header, hgroup,\nmenu, nav, output, ruby, section, summary,\ntime, mark, audio, video {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tfont-size: 100%;\n\tfont: inherit;\n\tvertical-align: baseline;\n}\n/* HTML5 display-role reset for older browsers */\narticle, aside, details, figcaption, figure,\nfooter, header, hgroup, menu, nav, section {\n\tdisplay: block;\n}\nbody {\n\tline-height: 1;\n}\nol, ul {\n\tlist-style: none;\n}\nblockquote, q {\n\tquotes: none;\n}\nblockquote:before, blockquote:after,\nq:before, q:after {\n\tcontent: '';\n\tcontent: none;\n}\ntable {\n\tborder-collapse: collapse;\n\tborder-spacing: 0;\n}\n\n/* Prevent overscrolling */\n/* https://stackoverflow.com/a/17899813 */\nhtml {\n overflow: hidden;\n height: 100%;\n}\nbody {\n height: 100%;\n overflow: auto;\n}"]}
--------------------------------------------------------------------------------
/rails_mini_profiler.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'lib/rails_mini_profiler/version'
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = 'rails_mini_profiler'
7 | spec.version = RailsMiniProfiler::VERSION
8 | spec.authors = ['hschne']
9 | spec.email = ['hans.schnedlitz@gmail.com']
10 |
11 | spec.summary = 'Performance profiling for your Rails app, made simple'
12 | spec.description = 'Performance profiling for your Rails app, made simple'
13 | spec.homepage = 'https://github.com/hschne/rails-mini-profiler'
14 | spec.license = 'MIT'
15 |
16 | spec.metadata = {
17 | 'bug_tracker_uri' => "#{spec.homepage}/issues",
18 | 'changelog_uri' => "#{spec.homepage}/blob/main/CHANGELOG.md",
19 | 'documentation_uri' => spec.homepage.to_s,
20 | 'homepage_uri' => spec.homepage.to_s,
21 | 'source_code_uri' => spec.homepage.to_s,
22 | 'rubygems_mfa_required' => 'true'
23 | }
24 |
25 | spec.files = Dir['{app,config,db,lib,public}/**/*', 'vendor/assets/**/*', 'LICENSE', 'README.md']
26 |
27 | spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0')
28 |
29 | spec.add_dependency 'inline_svg', '~> 1.9'
30 | spec.add_dependency 'jb', '~> 0.8'
31 | spec.add_dependency 'pagy', '>= 4.11'
32 | spec.add_dependency 'rails', '>= 6.0'
33 | end
34 |
--------------------------------------------------------------------------------
/rakelib/speedscope.rake:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | namespace :speedscope do
4 | desc 'Update Speedscope to the latest version'
5 | task :update do
6 | require_relative 'util/speedscope'
7 |
8 | Speedscope.update($stdout)
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/rakelib/util/speedscope.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'httparty'
4 | require 'zip'
5 |
6 | class Speedscope
7 | REPOSITORY = 'jlfwong/speedscope'
8 |
9 | BASE_DIRECTORY = File.expand_path('../../public/rails_mini_profiler', File.dirname(__FILE__))
10 |
11 | def self.update(stdout)
12 | new(stdout).update
13 | rescue StandardError => e
14 | stdout.puts("Error: #{e.message}")
15 | end
16 |
17 | def initialize(stdout)
18 | @stdout = stdout
19 | end
20 |
21 | def update
22 | return msg('No new version available') unless new_version_available?
23 |
24 | url = "https://github.com/#{REPOSITORY}/releases/download/v#{latest_version}/speedscope-#{latest_version}.zip"
25 | msg('Downloading latest...')
26 | body = HTTParty.get(url, follow_redirects: true).body
27 | msg('Overwriting existing files...')
28 | FileUtils.rm_f(Dir.glob(File.join(BASE_DIRECTORY, 'speedscope', '*')))
29 | overwrite_speedscope(body)
30 | msg('Done!')
31 | end
32 |
33 | private
34 |
35 | def msg(*args)
36 | @stdout.puts(*args)
37 | end
38 |
39 | def new_version_available?
40 | latest_version != current_version
41 | end
42 |
43 | def latest_version
44 | @latest_version ||= latest_release[:name].sub('v', '')
45 | end
46 |
47 | def current_version
48 | @current_version ||= File.read(File.join(BASE_DIRECTORY, 'speedscope', 'release.txt')).split("\n")[0].split('@')[-1]
49 | end
50 |
51 | def latest_release
52 | @latest_release ||=
53 | begin
54 | response = HTTParty.get('https://api.github.com/repos/jlfwong/speedscope/releases/latest', format: :plain)
55 | JSON.parse(response, symbolize_names: true)
56 | end
57 | end
58 |
59 | def overwrite_speedscope(body)
60 | exclude_regex = /#{Regexp.union(%w[perf-vertx-stacks README])}/i
61 | Zip::File.open_buffer(StringIO.new(body)) do |zip_file|
62 | zip_file.each do |entry|
63 | next if entry.name =~ exclude_regex
64 |
65 | dest_path = File.join(BASE_DIRECTORY, entry.name)
66 | entry.extract(dest_path) { true }
67 | end
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from '@rollup/plugin-babel'
2 | import eslint from '@rollup/plugin-eslint'
3 | import resolve from '@rollup/plugin-node-resolve'
4 | import commonjs from '@rollup/plugin-commonjs'
5 | import replace from '@rollup/plugin-replace'
6 | import { terser } from 'rollup-plugin-terser'
7 | import scss from 'rollup-plugin-scss'
8 | import postcss from 'rollup-plugin-postcss'
9 | import autoprefixer from 'autoprefixer'
10 | import copy from 'rollup-plugin-copy'
11 |
12 | import pkg from './package.json'
13 |
14 | const banner =
15 | `/*
16 | * Rails Mini Profiler
17 | * ${pkg.description}
18 | * ${pkg.repository.url}
19 | * v${pkg.version}
20 | * ${pkg.license} License
21 | */
22 | `
23 |
24 | const plugins = [
25 | resolve(),
26 | commonjs(),
27 | eslint({
28 | fix: true,
29 | exclude: ['./node_modules/**', './app/javascript/stylesheets/**'],
30 | }),
31 | babel({
32 | exclude: 'node_modules/**',
33 | babelHelpers: 'bundled'
34 | }),
35 | replace({
36 | 'ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
37 | 'process.env.NODE_ENV': JSON.stringify('production'),
38 | preventAssignment: true
39 | }),
40 | (process.env.NODE_ENV === 'production' && terser()),
41 | scss({
42 | outputStyle: (process.env.NODE_ENV === 'production') ? 'compressed' : 'expanded',
43 | }),
44 | postcss({
45 | plugins: [autoprefixer()],
46 | inject: false,
47 | extract: true,
48 | sourceMap: (process.env.NODE_ENV === 'production' ? false : 'inline'),
49 | minimize: (process.env.NODE_ENV === 'production')
50 | }),
51 | copy({
52 | targets: [
53 | { src: './app/javascript/images/**/*', dest: './dist/images' },
54 | { src: './app/javascript/images/**/*', dest: './vendor/assets/images' }
55 | ]
56 | })
57 |
58 | ]
59 |
60 | const input = 'app/javascript/packs/rails-mini-profiler.js'
61 |
62 | export default [
63 | {
64 | input: input,
65 | context: 'window',
66 | output: {
67 | file: pkg.main,
68 | format: 'umd',
69 | name: 'RailsMiniProfiler',
70 | sourcemap: (process.env.NODE_ENV === 'production' ? false : 'inline'),
71 | banner: banner,
72 | },
73 | plugins: plugins
74 | },
75 | {
76 | input: input,
77 | context: 'window',
78 | output: {
79 | file: "vendor/assets/javascripts/rails-mini-profiler.js",
80 | format: 'umd',
81 | name: 'RailsMiniProfiler',
82 | sourcemap: (process.env.NODE_ENV === 'production' ? false : 'inline'),
83 | banner: banner,
84 | },
85 | plugins: plugins
86 | }
87 | ]
88 |
--------------------------------------------------------------------------------
/spec/adapters/rails_mini_profiler/database_adapter_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | RSpec.describe DatabaseAdapter, type: :model do
7 | describe 'cast column' do
8 | context 'default' do
9 | it 'returns original column' do
10 | expect(described_class.cast_to_text(:column)).to equal(:column)
11 | end
12 | end
13 |
14 | context 'postgres' do
15 | it 'returns original column' do
16 | expect(ActiveRecord::Base.connection).to receive(:adapter_name).and_return('PostgreSQL')
17 |
18 | expect(described_class.cast_to_text(:column)).to eq('column::text')
19 | end
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Add your own tasks in files placed in lib/tasks ending in .rake,
4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
5 |
6 | require_relative 'config/application'
7 |
8 | Rails.application.load_tasks
9 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../stylesheets .css
3 | //= link rails_mini_profiler_manifest.js
4 | //= link rails_mini_profiler.js
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/spec/dummy/app/assets/images/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/movies.css:
--------------------------------------------------------------------------------
1 | /*
2 | Place all the styles related to the matching controller here.
3 | They will automatically be included in application.css.
4 | */
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/scaffold.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #fff;
3 | color: #333;
4 | margin: 33px;
5 | }
6 |
7 | body, p, ol, ul, td {
8 | font-family: verdana, arial, helvetica, sans-serif;
9 | font-size: 13px;
10 | line-height: 18px;
11 | }
12 |
13 | pre {
14 | background-color: #eee;
15 | padding: 10px;
16 | font-size: 11px;
17 | }
18 |
19 | a {
20 | color: #000;
21 | }
22 |
23 | a:visited {
24 | color: #666;
25 | }
26 |
27 | a:hover {
28 | color: #fff;
29 | background-color: #000;
30 | }
31 |
32 | th {
33 | padding-bottom: 5px;
34 | }
35 |
36 | td {
37 | padding: 0 5px 7px;
38 | }
39 |
40 | div.field,
41 | div.actions {
42 | margin-bottom: 10px;
43 | }
44 |
45 | #notice {
46 | color: green;
47 | }
48 |
49 | .field_with_errors {
50 | padding: 2px;
51 | background-color: red;
52 | display: table;
53 | }
54 |
55 | #error_explanation {
56 | width: 450px;
57 | border: 2px solid red;
58 | padding: 7px 7px 0;
59 | margin-bottom: 20px;
60 | background-color: #f0f0f0;
61 | }
62 |
63 | #error_explanation h2 {
64 | text-align: left;
65 | font-weight: bold;
66 | padding: 5px 5px 5px 15px;
67 | font-size: 12px;
68 | margin: -7px -7px 0;
69 | background-color: #c00;
70 | color: #fff;
71 | }
72 |
73 | #error_explanation ul li {
74 | font-size: 12px;
75 | list-style: square;
76 | }
77 |
78 | label {
79 | display: block;
80 | }
81 |
--------------------------------------------------------------------------------
/spec/dummy/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationCable
4 | class Channel < ActionCable::Channel::Base
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationCable
4 | class Connection < ActionCable::Connection::Base
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationController < ActionController::Base
4 | end
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/spec/dummy/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/movies_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class MoviesController < ApplicationController
4 | before_action :set_movie, only: %i[show edit update destroy]
5 |
6 | def index
7 | @movies = Movie.all
8 | end
9 |
10 | def show; end
11 |
12 | def new
13 | @movie = Movie.new
14 | end
15 |
16 | def edit; end
17 |
18 | def create
19 | @movie = Movie.new(movie_params)
20 |
21 | if @movie.save
22 | redirect_to @movie, notice: 'Movie was successfully created.'
23 | else
24 | render :new
25 | end
26 | end
27 |
28 | def update
29 | if @movie.update(movie_params)
30 | redirect_to @movie, notice: 'Movie was successfully updated.'
31 | else
32 | render :edit
33 | end
34 | end
35 |
36 | def destroy
37 | @movie.destroy
38 | redirect_to movies_url, notice: 'Movie was successfully destroyed.'
39 | end
40 |
41 | private
42 |
43 | # Use callbacks to share common setup or constraints between actions.
44 | def set_movie
45 | @movie = Movie.find(params[:id])
46 | end
47 |
48 | # Only allow a list of trusted parameters through.
49 | def movie_params
50 | params.require(:movie).permit(:title, :imdb_id, :popularity, :budget, :revenue, :runtime, :release_date)
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationHelper
4 | end
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/movies_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module MoviesHelper
4 | end
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/javascript/packs/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require rails-ujs
14 | //= require activestorage
15 | //= require_tree .
16 |
--------------------------------------------------------------------------------
/spec/dummy/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationJob < ActiveJob::Base
4 | # Automatically retry jobs that encountered a deadlock
5 | # retry_on ActiveRecord::Deadlocked
6 |
7 | # Most jobs are safe to ignore if the underlying records are no longer available
8 | # discard_on ActiveJob::DeserializationError
9 | end
10 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationMailer < ActionMailer::Base
4 | default from: 'from@example.com'
5 | layout 'mailer'
6 | end
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationRecord < ActiveRecord::Base
4 | self.abstract_class = true
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/spec/dummy/app/models/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/models/movie.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # == Schema Information
4 | #
5 | # Table name: movies
6 | #
7 | # id :integer not null, primary key
8 | # imdb_id :string
9 | # popularity :decimal(5, 2)
10 | # budget :decimal(, )
11 | # revenue :decimal(, )
12 | # title :string
13 | # runtime :decimal(3, )
14 | # release_date :date
15 | #
16 | class Movie < ApplicationRecord
17 | end
18 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 |
6 | <%= csrf_meta_tags %>
7 | <%= csp_meta_tag %>
8 |
9 | <%= stylesheet_link_tag 'application', media: 'all' %>
10 |
11 |
12 |
13 | <%= yield %>
14 |
15 |
16 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/movies/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_with(model: movie) do |form| %>
2 | <% if movie.errors.any? %>
3 |
4 |
<%= pluralize(movie.errors.count, "error") %> prohibited this movie from being saved:
5 |
6 |
7 | <% movie.errors.each do |error| %>
8 | - <%= error.full_message %>
9 | <% end %>
10 |
11 |
12 | <% end %>
13 |
14 |
15 | <%= form.label :title %>
16 | <%= form.text_field :title %>
17 |
18 |
19 |
20 | <%= form.label :imdb_id %>
21 | <%= form.text_field :imdb_id %>
22 |
23 |
24 |
25 | <%= form.label :popularity %>
26 | <%= form.text_field :popularity %>
27 |
28 |
29 |
30 | <%= form.label :budget %>
31 | <%= form.text_field :budget %>
32 |
33 |
34 |
35 | <%= form.label :revenue %>
36 | <%= form.text_field :revenue %>
37 |
38 |
39 |
40 | <%= form.label :runtime %>
41 | <%= form.text_field :runtime %>
42 |
43 |
44 |
45 | <%= form.label :release_date %>
46 | <%= form.date_select :release_date %>
47 |
48 |
49 |
50 | <%= form.submit %>
51 |
52 | <% end %>
53 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/movies/_movie.json.jb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | {
4 | id: movie.id,
5 | imdb_id: movie.imdb_id,
6 | popularity: movie.popularity,
7 | revenue: movie.revenue,
8 | title: movie.title,
9 | runtime: movie.runtime
10 | }
11 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/movies/edit.html.erb:
--------------------------------------------------------------------------------
1 | Editing Movie
2 |
3 | <%= render 'form', movie: @movie %>
4 |
5 | <%= link_to 'Show', @movie %> |
6 | <%= link_to 'Back', movies_path %>
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/movies/index.html.erb:
--------------------------------------------------------------------------------
1 | <%= notice %>
2 |
3 | Movies
4 |
5 |
6 |
7 |
8 | Title |
9 | Imdb |
10 | Popularity |
11 | Budget |
12 | Revenue |
13 | Runtime |
14 | Release date |
15 | |
16 |
17 |
18 |
19 |
20 | <% @movies.each do |movie| %>
21 |
22 | <%= movie.title %> |
23 | <%= movie.imdb_id %> |
24 | <%= movie.popularity %> |
25 | <%= movie.budget %> |
26 | <%= movie.revenue %> |
27 | <%= movie.runtime %> |
28 | <%= movie.release_date %> |
29 | <%= link_to 'Show', movie %> |
30 | <%= link_to 'Edit', edit_movie_path(movie) %> |
31 | <%= link_to 'Destroy', movie, method: :delete, data: { confirm: 'Are you sure?' } %> |
32 |
33 | <% end %>
34 |
35 |
36 |
37 |
38 |
39 | <%= link_to 'New Movie', new_movie_path %>
40 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/movies/index.json.jb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | render partial: 'movie', collection: @movies, as: :movie
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/movies/new.html.erb:
--------------------------------------------------------------------------------
1 | New Movie
2 |
3 | <%= render 'form', movie: @movie %>
4 |
5 | <%= link_to 'Back', movies_path %>
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/movies/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= notice %>
2 |
3 |
4 | Title:
5 | <%= @movie.title %>
6 |
7 |
8 |
9 | Imdb:
10 | <%= @movie.imdb_id %>
11 |
12 |
13 |
14 | Popularity:
15 | <%= @movie.popularity %>
16 |
17 |
18 |
19 | Budget:
20 | <%= @movie.budget %>
21 |
22 |
23 |
24 | Revenue:
25 | <%= @movie.revenue %>
26 |
27 |
28 |
29 | Runtime:
30 | <%= @movie.runtime %>
31 |
32 |
33 |
34 | Release date:
35 | <%= @movie.release_date %>
36 |
37 |
38 | <%= link_to 'Edit', edit_movie_path(@movie) %> |
39 | <%= link_to 'Back', movies_path %>
40 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/movies/show.json.jb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | render 'movie', movie: @movie
4 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | APP_PATH = File.expand_path('../config/application', __dir__)
5 | require_relative '../config/boot'
6 | require 'rails/commands'
7 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require_relative '../config/boot'
5 | require 'rake'
6 | Rake.application.run
7 |
--------------------------------------------------------------------------------
/spec/dummy/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require 'fileutils'
5 |
6 | # path to your application root.
7 | APP_ROOT = File.expand_path('..', __dir__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | FileUtils.chdir APP_ROOT do
14 | # This script is a way to set up or update your development environment automatically.
15 | # This script is idempotent, so that you can run it at any time and get an expectable outcome.
16 | # Add necessary setup steps to this file.
17 |
18 | puts '== Installing dependencies =='
19 | system! 'gem install bundler --conservative'
20 | system('bundle check') || system!('bundle install')
21 |
22 | # puts "\n== Copying sample files =="
23 | # unless File.exist?('config/database.yml')
24 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
25 | # end
26 |
27 | puts "\n== Preparing database =="
28 | system! 'bin/rails db:prepare'
29 |
30 | puts "\n== Removing old logs and tempfiles =="
31 | system! 'bin/rails log:clear tmp:clear'
32 |
33 | puts "\n== Restarting application server =="
34 | system! 'bin/rails restart'
35 | end
36 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This file is used by Rack-based servers to start the application.
4 |
5 | require_relative 'config/environment'
6 |
7 | run Rails.application
8 | Rails.application.load_server
9 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'boot'
4 |
5 | require 'rails/all'
6 |
7 | # Require the gems listed in Gemfile, including any gems
8 | # you've limited to :test, :development, or :production.
9 | Bundler.require(*Rails.groups)
10 | require 'rails_mini_profiler'
11 |
12 | module Dummy
13 | class Application < Rails::Application
14 | config.load_defaults Rails::VERSION::MAJOR.to_f.to_s
15 |
16 | # Configuration for the application, engines, and railties goes here.
17 | #
18 | # These settings can be overridden in specific environments using the files
19 | # in config/environments, which are processed later.
20 | #
21 | config.time_zone = 'Europe/Vienna'
22 | config.active_record.default_timezone = :local
23 | # config.eager_load_paths << Rails.root.join("extras")
24 |
25 | config.database = ENV.fetch('DATABASE', :sqlite).to_sym
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Set up gems listed in the Gemfile.
4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
5 |
6 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
7 | $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: test
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: dummy_production
11 |
--------------------------------------------------------------------------------
/spec/dummy/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | dAq9Q4G5eJXUFjT8fO+S3YP3U91ebX6yjoIOwOROCjcYHUTrjZLW8kNdmq/fFqj7SHyDsWPY1z3QalkFAlIfUQfLGE8q774JmUt6MkIyhcV4EwLru7OgDdLz81ugbtJFEEzBhXpLBNA6/RkoPjCGCSQv2aEAzoXKVl/jsbLlqqYMg95gUwY1rpxXgqCiZDoqY1lwPWJ3YzPk91GfDvyXOEidK7f/zYbDyKUIFJ4J8cIRpSXR1PMgLBgWmpJMkFPCEYephZRBudhXj7wfAoh771LUMm2flwM1rCIXtg8GIc1ZOvwcQGqFdwuiRk3EQZ5q1f7eAvfuiWe54LXl2yrZXHUsYj2viZhynZgEFufHXOgBoX+V/LxKTelmOBulIvkyqqKn8CwiQmN4bwb4DGY4vstQb8w0b+zZdtrk--4zK2itXWOmJuwe1V--F6DIE4BidiTTpoJiccoGNg==
--------------------------------------------------------------------------------
/spec/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite. Versions 3.8.0 and up are supported.
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 |
7 | <% if Rails.application.config.database == :postgres %>
8 | default: &default
9 | adapter: postgresql
10 | host: '127.0.0.1'
11 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
12 | username: dummy
13 | password: postgres
14 | development:
15 | <<: *default
16 | database: rmp_dummy_development
17 | test:
18 | <<: *default
19 | database: rmp_dummy_test
20 | production:
21 | <<: *default
22 | database: rmp_dummy_production
23 | <% else %>
24 | default: &default
25 | adapter: sqlite3
26 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
27 | timeout: 5000
28 | development:
29 | <<: *default
30 | database: db/development.sqlite3
31 | test:
32 | <<: *default
33 | database: db/test.sqlite3
34 | production:
35 | <<: *default
36 | database: db/production.sqlite3
37 | <% end %>
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Load the Rails application.
4 | require_relative 'application'
5 |
6 | # Initialize the Rails application.
7 | Rails.application.initialize!
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'active_support/core_ext/integer/time'
4 |
5 | # The test environment is used exclusively to run your application's
6 | # test suite. You never need to work with it otherwise. Remember that
7 | # your test database is "scratch space" for the test suite and is wiped
8 | # and recreated between test runs. Don't rely on the data there!
9 |
10 | Rails.application.configure do
11 | # Settings specified here will take precedence over those in config/application.rb.
12 |
13 | config.cache_classes = true
14 |
15 | # Do not eager load code on boot. This avoids loading your whole application
16 | # just for the purpose of running a single test. If you are using a tool that
17 | # preloads Rails for running tests, you may have to set it to true.
18 | config.eager_load = false
19 |
20 | # Configure public file server for tests with Cache-Control for performance.
21 | config.public_file_server.enabled = true
22 | config.public_file_server.headers = {
23 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
24 | }
25 |
26 | # Show full error reports and disable caching.
27 | config.consider_all_requests_local = true
28 | config.action_controller.perform_caching = false
29 | config.cache_store = :null_store
30 |
31 | # Raise exceptions instead of rendering exception templates.
32 | config.action_dispatch.show_exceptions = false
33 |
34 | # Disable request forgery protection in test environment.
35 | config.action_controller.allow_forgery_protection = false
36 |
37 | # Store uploaded files on the local file system in a temporary directory.
38 | config.active_storage.service = :test
39 |
40 | config.action_mailer.perform_caching = false
41 |
42 | # Tell Action Mailer not to deliver emails to the real world.
43 | # The :test delivery method accumulates sent emails in the
44 | # ActionMailer::Base.deliveries array.
45 | config.action_mailer.delivery_method = :test
46 |
47 | # Print deprecation notices to the stderr.
48 | config.active_support.deprecation = :stderr
49 |
50 | # Raise exceptions for disallowed deprecations.
51 | config.active_support.disallowed_deprecation = :raise
52 |
53 | # Tell Active Support which deprecation messages to disallow.
54 | config.active_support.disallowed_deprecation_warnings = []
55 |
56 | # Raises error for missing translations.
57 | # config.i18n.raise_on_missing_translations = true
58 |
59 | # Annotate rendered view with file names.
60 | # config.action_view.annotate_rendered_view_with_filenames = true
61 | end
62 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # ActiveSupport::Reloader.to_prepare do
5 | # ApplicationController.renderer.defaults.merge!(
6 | # http_host: 'example.org',
7 | # https: false
8 | # )
9 | # end
10 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Version of your assets, change this if you want to expire all your assets.
6 | Rails.application.config.assets.version = '1.0'
7 |
8 | # Add additional assets to the asset load path.
9 | # Rails.application.config.assets.paths << Emoji.images_path
10 |
11 | # Precompile additional assets.
12 | # application.js, application.css, and all non-JS/CSS in the app/assets
13 | # folder are already added.
14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
6 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) }
7 |
8 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code
9 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'".
10 | Rails.backtrace_cleaner.remove_silencers! if ENV['BACKTRACE']
11 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # Define an application-wide content security policy
5 | # For further information see the following documentation
6 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
7 |
8 | # Rails.application.config.content_security_policy do |policy|
9 | # policy.default_src :self, :https
10 | # policy.font_src :self, :https, :data
11 | # policy.img_src :self, :https, :data
12 | # policy.object_src :none
13 | # policy.script_src :self, :https
14 | # policy.style_src :self, :https
15 |
16 | # # Specify URI for violation reports
17 | # # policy.report_uri "/csp-violation-report-endpoint"
18 | # end
19 |
20 | # If you are using UJS then enable automatic nonce generation
21 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
22 |
23 | # Set the nonce only to specific directives
24 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
25 |
26 | # Report CSP violations to a specified URI
27 | # For further information see the following documentation:
28 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
29 | # Rails.application.config.content_security_policy_report_only = true
30 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Specify a serializer for the signed and encrypted cookie jars.
6 | # Valid options are :json, :marshal, and :hybrid.
7 | Rails.application.config.action_dispatch.cookies_serializer = :json
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Configure sensitive parameters which will be filtered from the log file.
6 | Rails.application.config.filter_parameters += %i[
7 | passw secret token _key crypt salt certificate otp ssn
8 | ]
9 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # Add new inflection rules using the following format. Inflections
5 | # are locale specific, and you may define rules for as many different
6 | # locales as you wish. All of these examples are active by default:
7 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
8 | # inflect.plural /^(ox)$/i, '\1en'
9 | # inflect.singular /^(ox)en/i, '\1'
10 | # inflect.irregular 'person', 'people'
11 | # inflect.uncountable %w( fish sheep )
12 | # end
13 |
14 | # These inflection rules are supported but not enabled by default:
15 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
16 | # inflect.acronym 'RESTful'
17 | # end
18 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # Add new mime types for use in respond_to blocks:
5 | # Mime::Type.register "text/richtext", :rtf
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/permissions_policy.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Define an application-wide HTTP permissions policy. For further
3 | # information see https://developers.google.com/web/updates/2018/06/feature-policy
4 | #
5 | # Rails.application.config.permissions_policy do |f|
6 | # f.camera :none
7 | # f.gyroscope :none
8 | # f.microphone :none
9 | # f.usb :none
10 | # f.fullscreen :self
11 | # f.payment :self, "https://secure.example.com"
12 | # end
13 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/rails_mini_profiler.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RailsMiniProfiler.configure do |config|
4 | config.storage = RailsMiniProfiler::Storage.new
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # This file contains settings for ActionController::ParamsWrapper which
6 | # is enabled by default.
7 |
8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
9 | ActiveSupport.on_load(:action_controller) do
10 | wrap_parameters format: [:json]
11 | end
12 |
13 | # To enable root element in JSON for ActiveRecord objects.
14 | # ActiveSupport.on_load(:active_record) do
15 | # self.include_root_in_json = true
16 | # end
17 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/spec/dummy/config/puma.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Puma can serve each request in a thread from an internal thread pool.
4 | # The `threads` method setting takes two numbers: a minimum and maximum.
5 | # Any libraries that use thread pools should be configured to match
6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
7 | # and maximum; this matches the default thread size of Active Record.
8 | #
9 | max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5)
10 | min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count }
11 | threads min_threads_count, max_threads_count
12 |
13 | # Specifies the `worker_timeout` threshold that Puma will use to wait before
14 | # terminating a worker in development environments.
15 | #
16 | worker_timeout 3600 if ENV.fetch('RAILS_ENV', 'development') == 'development'
17 |
18 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
19 | #
20 | port ENV.fetch('PORT', 3000)
21 |
22 | # Specifies the `environment` that Puma will run in.
23 | #
24 | environment ENV.fetch('RAILS_ENV', 'development')
25 |
26 | # Specifies the `pidfile` that Puma will use.
27 | pidfile ENV.fetch('PIDFILE', 'tmp/pids/server.pid')
28 |
29 | # Specifies the number of `workers` to boot in clustered mode.
30 | # Workers are forked web server processes. If using threads and workers together
31 | # the concurrency of the application would be max `threads` * `workers`.
32 | # Workers do not work on JRuby or Windows (both of which do not support
33 | # processes).
34 | #
35 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
36 |
37 | # Use the `preload_app!` method when specifying a `workers` number.
38 | # This directive tells Puma to first boot the application and load code
39 | # before forking the application. This takes advantage of Copy On Write
40 | # process behavior so workers use less memory.
41 | #
42 | # preload_app!
43 |
44 | # Allow puma to be restarted by `rails restart` command.
45 | plugin :tmp_restart
46 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.routes.draw do
4 | mount RailsMiniProfiler::Engine => '/rails_mini_profiler'
5 | resources :movies
6 | end
7 |
--------------------------------------------------------------------------------
/spec/dummy/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket
23 |
24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20210702114107_add_movies.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AddMovies < ActiveRecord::Migration[6.1]
4 | def change
5 | create_table :movies, force: :cascade do |t|
6 | t.string :imdb_id
7 | t.decimal :popularity, precision: 5, scale: 2
8 | t.decimal :budget
9 | t.decimal :revenue
10 | t.string :title
11 | t.decimal :runtime, precision: 3
12 | t.date :release_date
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/dummy/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | return if Movie.any?
4 |
5 | require 'csv'
6 | movies = CSV.read("#{File.dirname(__FILE__)}/movies.csv", headers: true).map(&:to_h)
7 | .each { |attrs| attrs['release_date'] = Date.strptime(attrs['release_date'], '%m/%d/%Y') }
8 | .map { |attrs| Movie.new(attrs) }
9 |
10 | Movie.import(movies)
11 |
--------------------------------------------------------------------------------
/spec/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/spec/dummy/lib/assets/.keep
--------------------------------------------------------------------------------
/spec/dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/spec/dummy/log/.keep
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/spec/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/spec/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/spec/dummy/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/spec/dummy/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/spec/dummy/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/spec/dummy/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/spec/dummy/public/favicon.ico
--------------------------------------------------------------------------------
/spec/dummy/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/spec/dummy/storage/.keep
--------------------------------------------------------------------------------
/spec/dummy/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/spec/dummy/tmp/.keep
--------------------------------------------------------------------------------
/spec/dummy/tmp/development_secret.txt:
--------------------------------------------------------------------------------
1 | 03be70680939db04419ff891147d9c8ee15e80624edfe4a52c63314c2fcf35d2b3e3abbcc4100024422817e2e1a3625e59ce012712ad7a76a1ed2ee931630d76
--------------------------------------------------------------------------------
/spec/dummy/tmp/pids/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/spec/dummy/tmp/pids/.keep
--------------------------------------------------------------------------------
/spec/dummy/tmp/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hschne/rails-mini-profiler/d5dbcbbf553073d12e541ebcb5e0d4b43dff2a77/spec/dummy/tmp/storage/.keep
--------------------------------------------------------------------------------
/spec/helpers/rails_mini_profiler/profiled_request_helper_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | RSpec.describe ProfiledRequestsHelper do
7 | describe 'formatted duration' do
8 | context 'less than 1 ms' do
9 | it 'shows decimal ' do
10 | expect(helper.formatted_duration(99)).to eq(0.99)
11 | end
12 | end
13 |
14 | context 'more than 1 ms' do
15 | it 'returns rounded ' do
16 | expect(helper.formatted_duration(111)).to eq(1)
17 | end
18 | end
19 | end
20 |
21 | describe 'formatted allocations' do
22 | context 'less than 1000' do
23 | it 'returns plain number' do
24 | expect(helper.formatted_allocations(100)).to eq('100')
25 | end
26 | end
27 |
28 | context 'with thousands' do
29 | it 'returns formatted number' do
30 | expect(helper.formatted_allocations(5_500)).to eq('5.5 k')
31 | end
32 | end
33 |
34 | context 'with millions' do
35 | it 'returns formatted number' do
36 | expect(helper.formatted_allocations(5_500_500)).to eq('5.5 M')
37 | end
38 | end
39 |
40 | context 'with billions' do
41 | it 'returns formatted number' do
42 | expect(helper.formatted_allocations(5_500_500_500)).to eq('5.5 B')
43 | end
44 | end
45 |
46 | context 'with trillions' do
47 | it 'returns formatted number' do
48 | expect(helper.formatted_allocations(5_500_500_500_500)).to eq('5.5 T')
49 | end
50 | end
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/spec/lib/rails_mini_profiler/badge_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | RSpec.describe Badge do
7 | describe 'render' do
8 | let(:env) { {} }
9 | let(:request) { RequestWrapper.new(env) }
10 | let(:response) { ResponseWrapper.new }
11 | let(:profiled_request) { ProfiledRequest.new(id: 1) }
12 | let(:configuration) { Configuration.new }
13 | let(:request_context) { RequestContext.new(request) }
14 |
15 | subject { Badge.new(request_context, configuration: configuration) }
16 |
17 | before(:each) do
18 | request_context.response = original_response
19 | request_context.profiled_request = profiled_request
20 | end
21 |
22 | context 'with non-html response' do
23 | let(:original_response) do
24 | ResponseWrapper.new(
25 | 'content',
26 | 200,
27 | { 'Content-Type' => 'application/json' }
28 | )
29 | end
30 |
31 | it('should return original response') { expect(subject.render).to eq(original_response) }
32 | end
33 |
34 | context 'with html response' do
35 | let(:original_response) do
36 | ResponseWrapper.new(
37 | 'content',
38 | 200,
39 | { 'Content-Type' => 'text/html' }
40 | )
41 | end
42 |
43 | context 'missing body' do
44 | it('should return original content') do
45 | new_body = subject.render.body
46 | expect(new_body).to eq('content')
47 | end
48 | end
49 |
50 | context 'with badge disabled' do
51 | let(:configuration) { Configuration.new(ui: UserInterface.new(badge_enabled: false)) }
52 |
53 | let(:original_response) do
54 | ResponseWrapper.new(
55 | 'content',
56 | 200,
57 | { 'Content-Type' => 'text/html' }
58 | )
59 | end
60 |
61 | it 'should not inject badge' do
62 | new_body = subject.render.body
63 | expect(new_body).to eq('content')
64 | end
65 | end
66 |
67 | context 'with body tag' do
68 | let(:original_response) do
69 | ResponseWrapper.new(
70 | 'content',
71 | 200,
72 | { 'Content-Type' => 'text/html' }
73 | )
74 | end
75 |
76 | it 'should inject badge' do
77 | new_body = subject.render.body
78 | expect(new_body).to match(/rails-mini-profiler-badge/)
79 | end
80 | end
81 | end
82 | end
83 | end
84 | end
85 |
--------------------------------------------------------------------------------
/spec/lib/rails_mini_profiler/guard_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | RSpec.describe Guard do
7 | describe 'profile?' do
8 | let(:env) { {} }
9 | let(:request) { RequestWrapper.new(env) }
10 | let(:configuration) { Configuration.new }
11 | let(:request_context) { RequestContext.new(request) }
12 |
13 | subject { Guard.new(request_context, configuration: configuration) }
14 |
15 | context 'with path' do
16 | let(:env) { { 'PATH_INFO' => '/' } }
17 |
18 | it('should be true') { expect(subject.profile?).to be(true) }
19 | end
20 |
21 | context 'with no ignored path' do
22 | let(:env) { { 'PATH_INFO' => '/ignored' } }
23 |
24 | it('should be true') { expect(subject.profile?).to be(true) }
25 | end
26 |
27 | context 'with profiler mount path' do
28 | let(:env) { { 'PATH_INFO' => "/#{Engine.routes.find_script_name({})}/1" } }
29 |
30 | it('should be false') { expect(subject.profile?).to be(false) }
31 | end
32 |
33 | context 'with pack path' do
34 | let(:env) { { 'PATH_INFO' => '/packs/js/abc' } }
35 |
36 | it('should be false') { expect(subject.profile?).to be(false) }
37 | end
38 |
39 | context 'with asset path' do
40 | let(:env) { { 'PATH_INFO' => '/assets/images/abc' } }
41 |
42 | it('should be false') { expect(subject.profile?).to be(false) }
43 | end
44 |
45 | context 'with actioncable path' do
46 | let(:env) { { 'PATH_INFO' => ActionCable.server.config.mount_path } }
47 |
48 | it('should be false') { expect(subject.profile?).to be(false) }
49 | end
50 |
51 | context 'with ignored path match' do
52 | let(:env) { { 'PATH_INFO' => '/ignored' } }
53 |
54 | it('should be false') do
55 | configuration.skip_paths = [/ignored/]
56 |
57 | expect(subject.profile?).to be(false)
58 | end
59 | end
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/spec/lib/rails_mini_profiler/redirect_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | RSpec.describe Redirect do
7 | describe 'render' do
8 | let(:env) { {} }
9 | let(:request) { RequestWrapper.new(env) }
10 | let(:configuration) { Configuration.new }
11 | let(:profiled_request) { ProfiledRequest.new(id: 1) }
12 | let(:request_context) { RequestContext.new(request) }
13 |
14 | subject { Redirect.new(request_context) }
15 |
16 | before(:each) { request_context.profiled_request = profiled_request }
17 |
18 | context 'with flamegraph parameter' do
19 | let(:env) { { 'QUERY_STRING' => 'rmp_flamegraph=true' } }
20 |
21 | it('should redirect to flamegraph path') do
22 | expect(subject.render.first).to eq(302)
23 | expect(subject.render.last).to eq(['Moved Temporarily'])
24 | end
25 | end
26 |
27 | context 'without parameter' do
28 | it('should return false') do
29 | expect(subject.render).to be(false)
30 | end
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/spec/lib/rails_mini_profiler/tracers/controller_tracer_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | module Tracers
7 | RSpec.describe ControllerTracer do
8 | describe 'trace' do
9 | let(:payload) { { view_runtime: 10, db_runtime: 5, ignore: 100 } }
10 | let(:event) { OpenStruct.new(payload: payload) }
11 |
12 | subject { ControllerTracer.new(event) }
13 |
14 | it('should remove payload fields') do
15 | trace = subject.trace
16 | expected = { view_runtime: 10, db_runtime: 5 }
17 | expect(trace.payload).to eq(expected)
18 | end
19 |
20 | context 'with blank values' do
21 | let(:payload) { { view_runtime: '', db_runtime: 5 } }
22 |
23 | it('should remove blank values') do
24 | trace = subject.trace
25 | expected = { db_runtime: 5 }
26 | expect(trace.payload).to eq(expected)
27 | end
28 | end
29 |
30 | context 'with numbers' do
31 | let(:payload) { { view_runtime: 10.105 } }
32 |
33 | it('should round to two digits') do
34 | trace = subject.trace
35 | expected = { view_runtime: 10.11 }
36 | expect(trace.payload).to eq(expected)
37 | end
38 | end
39 | end
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/spec/lib/rails_mini_profiler/tracers/registry_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | module Tracers
7 | RSpec.describe Registry do
8 | let(:configuration) { Configuration.new }
9 |
10 | subject { described_class.new(configuration) }
11 |
12 | describe 'tracers' do
13 | it('should return a name to tracer class mapping') do
14 | result = {
15 | 'instantiation.active_record' => RailsMiniProfiler::Tracers::InstantiationTracer,
16 | 'process_action.action_controller' => RailsMiniProfiler::Tracers::ControllerTracer,
17 | 'render_partial.action_view' => RailsMiniProfiler::Tracers::ViewTracer,
18 | 'rails_mini_profiler.total_time' => RailsMiniProfiler::Tracers::RmpTracer,
19 | 'render_template.action_view' => RailsMiniProfiler::Tracers::ViewTracer,
20 | 'sql.active_record' => RailsMiniProfiler::Tracers::SequelTracer
21 | }
22 | expect(subject.tracers).to eq(result)
23 | end
24 | end
25 |
26 | describe 'presenters' do
27 | it('should return a name to presenter class mapping') do
28 | result = {
29 | 'instantiation.active_record' => RailsMiniProfiler::InstantiationTracePresenter,
30 | 'process_action.action_controller' => RailsMiniProfiler::ControllerTracePresenter,
31 | 'rails_mini_profiler.total_time' => RailsMiniProfiler::RmpTracePresenter,
32 | 'render_partial.action_view' => RailsMiniProfiler::RenderPartialTracePresenter,
33 | 'render_template.action_view' => RailsMiniProfiler::RenderTemplateTracePresenter,
34 | 'sql.active_record' => RailsMiniProfiler::SequelTracePresenter
35 | }
36 |
37 | expect(subject.presenters).to eq(result)
38 | end
39 | end
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/spec/lib/rails_mini_profiler/tracers/sequel_tracer_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | module Tracers
7 | RSpec.describe SequelTracer do
8 | describe 'trace' do
9 | let(:payload) { { name: 'name', sql: 'select', binds: [], type_casted_binds: [], ignore: 'ignore' } }
10 | let(:event) { OpenStruct.new(payload: payload) }
11 |
12 | subject { SequelTracer.new(event) }
13 |
14 | context 'with invalid query' do
15 | let(:payload) { { name: 'name', sql: 'bad' } }
16 |
17 | it('should return null trace') do
18 | trace = subject.trace
19 | expect(trace).to be_a(NullTrace)
20 | end
21 | end
22 |
23 | it('should remove payload fields') do
24 | trace = subject.trace
25 | expected = { name: 'name', sql: 'select', binds: [] }
26 | expect(trace.payload).to eq(expected)
27 | end
28 |
29 | context('with callable binds') do
30 | let(:payload) do
31 | {
32 | name: 'name',
33 | sql: 'select',
34 | binds: [],
35 | type_casted_binds: -> { [] }
36 | }
37 | end
38 |
39 | it('should create binds') do
40 | trace = subject.trace
41 | expected = { name: 'name', sql: 'select', binds: [] }
42 | expect(trace.payload).to eq(expected)
43 | end
44 | end
45 |
46 | context('with binds') do
47 | let(:payload) do
48 | {
49 | name: 'name',
50 | sql: 'select',
51 | binds: [OpenStruct.new(name: 'id', value_before_cast: '1')],
52 | type_casted_binds: [1]
53 | }
54 | end
55 |
56 | it('should use type casted binds') do
57 | trace = subject.trace
58 | expected = { name: 'name', sql: 'select', binds: [{ name: 'id', value: 1 }] }
59 | expect(trace.payload).to eq(expected)
60 | end
61 | end
62 | end
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/spec/lib/rails_mini_profiler/tracers/sequel_tracker_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | module Tracers
7 | RSpec.describe SqlTracker do
8 | describe 'trace' do
9 | let(:query) { 'select' }
10 | let(:name) { 'name' }
11 |
12 | subject { SqlTracker.new(query: query, name: name) }
13 |
14 | context 'with untracked command' do
15 | let(:query) { 'ignore' }
16 |
17 | it('should not track') do
18 | expect(subject.track?).to eq(false)
19 | end
20 | end
21 |
22 | context 'with untracked name' do
23 | let(:name) { 'schema' }
24 |
25 | it('should not track') do
26 | expect(subject.track?).to eq(false)
27 | end
28 | end
29 |
30 | context 'with untracked table' do
31 | let(:query) { 'select * from sqlite_version' }
32 |
33 | it('should not track') do
34 | expect(subject.track?).to eq(false)
35 | end
36 | end
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/spec/lib/rails_mini_profiler/tracers/trace_factory_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | module Tracers
7 | RSpec.describe TraceFactory do
8 | let(:configuration) { Configuration.new }
9 |
10 | let(:registry) { Registry.new(configuration) }
11 |
12 | subject { described_class.new(registry) }
13 |
14 | describe 'create' do
15 | it('should return a trace with unknown event') do
16 | event = OpenStruct.new(name: 'unknown')
17 |
18 | expect(subject.create(event)).to be_a(Trace)
19 | end
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/spec/lib/rails_mini_profiler/tracers/tracer_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | module Tracers
7 | RSpec.describe Tracer do
8 | describe 'trace' do
9 | subject do
10 | ActiveSupport::Notifications.subscribe('wait') do |*args|
11 | @event = ActiveSupport::Notifications::Event.new(*args)
12 | end
13 |
14 | ActiveSupport::Notifications.instrument('wait') do
15 | sleep 0.001
16 | end
17 |
18 | Tracer.new(@event)
19 | end
20 |
21 | it('stores starts and ends as milliseconds') do
22 | expect(subject.trace.duration).to be_within(5).of(@event.duration * 100)
23 | end
24 |
25 | it('stores start and ends as microseconds') do
26 | expect(subject.trace.start).to be_within(100).of(@event.time.to_f * Tracer::TIMESTAMP_MULTIPLIER)
27 | expect(subject.trace.finish).to be_within(100).of(@event.end.to_f * Tracer::TIMESTAMP_MULTIPLIER)
28 | end
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/spec/lib/rails_mini_profiler/tracers/view_tracer_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | module Tracers
7 | RSpec.describe ViewTracer do
8 | describe 'trace' do
9 | let(:payload) { { identifier: 'id', count: 5, ignore: 100 } }
10 | let(:event) { OpenStruct.new(payload: payload) }
11 |
12 | subject { ViewTracer.new(event) }
13 |
14 | it('should remove payload fields') do
15 | trace = subject.trace
16 | expected = { identifier: 'id', count: 5 }
17 | expect(trace.payload).to eq(expected)
18 | end
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/spec/rails_mini_profiler_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe RailsMiniProfiler do
4 | it 'has a version number' do
5 | expect(RailsMiniProfiler::VERSION).not_to be nil
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/requests/rails_mini_profiler/flamegraph_request_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | RSpec.describe 'Flamegraphs', type: :request do
7 | let(:user_id) { '127.0.0.1' }
8 |
9 | describe 'GET /show' do
10 | it 'returns http success' do
11 | profiled_request = ProfiledRequest.create(user_id: user_id)
12 | Flamegraph.create(profiled_request: profiled_request, data: { id: 1 })
13 |
14 | get flamegraph_path(profiled_request.id)
15 |
16 | expect(response).to have_http_status(:success)
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/spec/routing/rails_mini_profiler/profiled_requests_routing_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | RSpec.describe ProfiledRequestsController, type: :routing do
7 | describe 'routing' do
8 | it 'routes to #index' do
9 | expect(get: profiled_requests_path)
10 | .to route_to(controller: 'rails_mini_profiler/profiled_requests', action: 'index')
11 | end
12 |
13 | it 'routes to #show' do
14 | expect(get: profiled_request_path(1))
15 | .to route_to(controller: 'rails_mini_profiler/profiled_requests', action: 'show', id: '1')
16 | end
17 |
18 | it 'routes to #destroy' do
19 | expect(delete: profiled_request_path(1))
20 | .to route_to(controller: 'rails_mini_profiler/profiled_requests', action: 'destroy', id: '1')
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/search/rails_mini_profiler/trace_search_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'rails_helper'
4 |
5 | module RailsMiniProfiler
6 | RSpec.describe TraceSearch, type: :model do
7 | describe 'search' do
8 | let(:profiled_request) { ProfiledRequest.create(request_path: '/index') }
9 |
10 | context 'by name' do
11 | it 'returns traces with matching name' do
12 | trace = Trace.create(name: 'sql', profiled_request: profiled_request)
13 | Trace.create(name: 'other', profiled_request: profiled_request)
14 |
15 | results = described_class.results(scope: Trace.all, name: 'sql')
16 |
17 | expect(results).to eq([trace])
18 | end
19 | end
20 |
21 | context ' by allocations' do
22 | it 'returns traces with greater allocation' do
23 | request = Trace.create(allocations: 101, profiled_request: profiled_request)
24 | Trace.create(allocations: 100, profiled_request: profiled_request)
25 |
26 | results = described_class.results(scope: Trace.all, allocations: 100)
27 |
28 | expect(results).to eq([request])
29 | end
30 | end
31 |
32 | context 'by duration' do
33 | it 'returns traces with greater duration' do
34 | request = Trace.create(duration: 10_001, profiled_request: profiled_request)
35 | Trace.create(duration: 10_000, profiled_request: profiled_request)
36 |
37 | results = described_class.results(scope: Trace.all, duration: 10_000)
38 |
39 | expect(results).to eq([request])
40 | end
41 | end
42 |
43 | context 'by payload' do
44 | it 'returns traces with matching payload' do
45 | profiled_request.traces.create(payload: { sample: 'one' })
46 | trace = profiled_request.traces.create(payload: { sample: 'two here' })
47 |
48 | results = described_class.results(scope: Trace.all, payload: 'two')
49 |
50 | expect(results).to eq([trace])
51 | end
52 | end
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/spec/support/spec_helpers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SpecHelpers
4 | def response_json
5 | JSON.parse(response.body)
6 | end
7 | end
8 |
--------------------------------------------------------------------------------