MiniProfiler/rack-mini-profiler

View on GitHub
lib/mini_profiler_rails/railtie.rb

Summary

Maintainability
C
1 day
Test Coverage
# frozen_string_literal: true

require 'fileutils'
require_relative './railtie_methods'

module Rack::MiniProfilerRails
  extend Rack::MiniProfilerRailsMethods

  # call direct if needed to do a defer init
  def self.initialize!(app)

    raise "MiniProfilerRails initialized twice. Set `require: false' for rack-mini-profiler in your Gemfile" if defined?(@already_initialized) && @already_initialized

    c = Rack::MiniProfiler.config

    # By default, only show the MiniProfiler in development mode.
    # To use the MiniProfiler in production, call Rack::MiniProfiler.authorize_request
    # from a hook in your ApplicationController
    #
    # Example:
    #   before_action { Rack::MiniProfiler.authorize_request if current_user.is_admin? }
    #
    # NOTE: this must be set here with = and not ||=
    #  The out of the box default is "true"
    c.pre_authorize_cb = lambda { |env|
      !Rails.env.test?
    }

    c.skip_paths ||= []

    if serves_static_assets?(app)
      c.skip_paths << app.config.assets.prefix
      wp_assets_path = get_webpacker_assets_path()
      c.skip_paths << wp_assets_path if wp_assets_path
    end

    unless Rails.env.development? || Rails.env.test?
      c.authorization_mode = :allow_authorized
    end

    if Rails.logger
      c.logger = Rails.logger
    end

    # The file store is just so much less flaky
    # If the user has not changed from the default memory store then switch to the file store, otherwise keep what the user set
    if c.storage == Rack::MiniProfiler::MemoryStore && c.storage_options.nil?
      base_path = Rails.application.config.paths['tmp'].first rescue "#{Rails.root}/tmp"
      tmp       = base_path + '/miniprofiler'

      c.storage_options = { path: tmp }
      c.storage = Rack::MiniProfiler::FileStore
    end

    # Quiet the SQL stack traces
    c.backtrace_remove = Rails.root.to_s + "/"
    c.backtrace_includes =  [/^\/?(app|config|lib|test)/]
    c.skip_schema_queries = (Rails.env.development? || Rails.env.test?)

    # Install the Middleware
    app.middleware.insert(0, Rack::MiniProfiler)
    c.enable_advanced_debugging_tools = Rails.env.development?

    if ::Rack::MiniProfiler.patch_rails?
      # Attach to various Rails methods
      ActiveSupport.on_load(:action_controller) do
        ::Rack::MiniProfiler.profile_method(ActionController::Base, :process) { |action| "Executing action: #{action}" }
      end

      ActiveSupport.on_load(:action_view) do
        ::Rack::MiniProfiler.profile_method(ActionView::Template, :render) { |x, y| "Rendering: #{@virtual_path}" }
      end
    else
      subscribe("start_processing.action_controller") do |name, start, finish, id, payload|
        next if !should_measure?

        current = Rack::MiniProfiler.current
        controller_name = payload[:controller].sub(/Controller\z/, '').downcase
        description = "Executing: #{controller_name}##{payload[:action]}"
        Thread.current[get_key(payload)] = current.current_timer
        Rack::MiniProfiler.current.current_timer = current.current_timer.add_child(description)
      end

      subscribe("process_action.action_controller") do |name, start, finish, id, payload|
        next if !should_measure?

        key = get_key(payload)
        parent_timer = Thread.current[key]
        next if !parent_timer

        Thread.current[key] = nil
        Rack::MiniProfiler.current.current_timer.record_time
        Rack::MiniProfiler.current.current_timer = parent_timer
      end

      subscribe("render_partial.action_view") do |name, start, finish, id, payload|
        render_notification_handler(shorten_identifier(payload[:identifier]), finish, start)
      end

      subscribe("render_template.action_view") do |name, start, finish, id, payload|
        render_notification_handler(shorten_identifier(payload[:identifier]), finish, start)
      end

      if Rack::MiniProfiler.subscribe_sql_active_record
        # we don't want to subscribe if we've already patched a DB driver
        # otherwise we would end up with 2 records for every query
        subscribe("sql.active_record") do |name, start, finish, id, payload|
          next if !should_measure?
          next if payload[:name] =~ /SCHEMA/ && Rack::MiniProfiler.config.skip_schema_queries

          Rack::MiniProfiler.record_sql(
            payload[:sql],
            (finish - start) * 1000,
            Rack::MiniProfiler.binds_to_params(payload[:binds])
          )
        end

        subscribe("instantiation.active_record") do |name, start, finish, id, payload|
          next if !should_measure?

          Rack::MiniProfiler.report_reader_duration(
            (finish - start) * 1000,
            payload[:record_count],
            payload[:class_name]
          )
        end
      end
    end
    @already_initialized = true
  end

  def self.create_engine
    return if defined?(Rack::MiniProfilerRails::Engine)
    klass = Class.new(::Rails::Engine) do
      engine_name 'rack-mini-profiler'
      config.assets.paths << File.expand_path('../../html', __FILE__)
      config.assets.precompile << 'rack-mini-profiler.js'
      config.assets.precompile << 'rack-mini-profiler.css'
    end
    Rack::MiniProfilerRails.const_set("Engine", klass)
  end

  def self.subscribe(event, &blk)
    if ActiveSupport::Notifications.respond_to?(:monotonic_subscribe)
      ActiveSupport::Notifications.monotonic_subscribe(event) { |*args| blk.call(*args) }
    else
      ActiveSupport::Notifications.subscribe(event) do |name, start, finish, id, payload|
        blk.call(name, start.to_f, finish.to_f, id, payload)
      end
    end
  end

  def self.get_key(payload)
    "mini_profiler_parent_timer_#{payload[:controller]}_#{payload[:action]}".to_sym
  end

  def self.shorten_identifier(identifier)
    identifier.split('/').last(2).join('/')
  end

  def self.serves_static_assets?(app)
    config = app.config

    if !config.respond_to?(:assets) || !config.assets.respond_to?(:prefix)
      return false
    end

    if ::Rails.version >= "5.0.0"
      ::Rails.configuration.public_file_server.enabled
    elsif ::Rails.version >= "4.2.0"
      ::Rails.configuration.serve_static_files
    else
      ::Rails.configuration.serve_static_assets
    end
  end

  class Railtie < ::Rails::Railtie

    initializer "rack_mini_profiler.configure_rails_initialization" do |app|
      Rack::MiniProfilerRails.initialize!(app)
    end

    # Suppress compression when Rack::Deflater is lower in the middleware
    # stack than Rack::MiniProfiler
    config.after_initialize do |app|
      middlewares = app.middleware.middlewares
      if Rack::MiniProfiler.config.suppress_encoding.nil? &&
          middlewares.include?(Rack::Deflater) &&
          middlewares.include?(Rack::MiniProfiler) &&
          middlewares.index(Rack::Deflater) > middlewares.index(Rack::MiniProfiler)
        Rack::MiniProfiler.config.suppress_encoding = true
      end
    end

    # TODO: Implement something better here
    # config.after_initialize do
    #
    #   class ::ActionView::Helpers::AssetTagHelper::JavascriptIncludeTag
    #     alias_method :asset_tag_orig, :asset_tag
    #     def asset_tag(source,options)
    #       current = Rack::MiniProfiler.current
    #       return asset_tag_orig(source,options) unless current
    #       wrapped = ""
    #       unless current.mpt_init
    #         current.mpt_init = true
    #         wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
    #       end
    #       name = source.split('/')[-1]
    #       wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
    #       wrapped
    #     end
    #   end

    #   class ::ActionView::Helpers::AssetTagHelper::StylesheetIncludeTag
    #     alias_method :asset_tag_orig, :asset_tag
    #     def asset_tag(source,options)
    #       current = Rack::MiniProfiler.current
    #       return asset_tag_orig(source,options) unless current
    #       wrapped = ""
    #       unless current.mpt_init
    #         current.mpt_init = true
    #         wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
    #       end
    #       name = source.split('/')[-1]
    #       wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
    #       wrapped
    #     end
    #   end

    # end

  end
end