在我们的案例中使用roadie-rails时会出现什么问题?

时间:2017-07-03 01:23:55

标签: ruby-on-rails ruby email html-email

最初发布为 https://github.com/Mange/roadie-rails/issues/75

我们发现每日电子邮件作业的性能问题 通过使用NewRelic自定义仪器,
我们发现大部分时间花在了Roadies

示例工作者的NewRelic数据的屏幕截图:

Time spent for one email job worker

集成代码:

# frozen_string_literal: true

require "rails"
require "action_controller"
require "contracts"
require "memoist"
require "roadie"
require "roadie-rails"

require "new_relic/agent/method_tracer"

module Shared::MailerMixins
  module WithRoadieIntegration
    # I don't want to include the constants into the class as well
    module Concern
      def self.included(base)
        base.extend ClassMethods
      end

      include ::NewRelic::Agent::MethodTracer

      def mail(*args, &block)
        super.tap do |m|
          options = roadie_options
          next unless options

          trace_execution_scoped(
            [
              [
                "WithRoadieIntegration",
                "Roadie::Rails::MailInliner.new(m, options).execute",
              ].join("/"),
            ],
          ) do
            Roadie::Rails::MailInliner.new(m, options).execute
          end
        end
      end

      private

      def roadie_options
        ::Rails.application.config.roadie.tap do |options|
          options.asset_providers = [UserAssetsProvider.new]
          options.external_asset_providers = [UserAssetsProvider.new]
          options.keep_uninlinable_css = false
          options.url_options = url_options.slice(*[
            :host,
            :port,
            :path,
            :protocol,
            :scheme,
          ])
        end
      end
      add_method_tracer(
        :roadie_options,
        "WithRoadieIntegration/roadie_options",
      )
    end

    class UserAssetsProvider
      extend(
        ::Memoist,
      )
      include(
        ::Contracts::Core,
        ::Contracts::Builtin,
      )

      include ::NewRelic::Agent::MethodTracer

      ABSOLUTE_ASSET_PATH_REGEXP = /\A#{Regexp.escape("//")}.+#{Regexp.escape("/assets/")}/i

      Contract String => Maybe[Roadie::Stylesheet]
      def find_stylesheet(name)
        return nil unless file_exists?(name)

        Roadie::Stylesheet.new("whatever", stylesheet_content(name))
      end
      add_method_tracer(
        :find_stylesheet,
        "UserAssetsProvider/find_stylesheet",
      )

      Contract String => Roadie::Stylesheet
      def find_stylesheet!(name)
        stylesheet = find_stylesheet(name)

        if stylesheet.nil?
          raise Roadie::CssNotFound.new(
            name,
            "does not exists",
            self,
          )
        end

        stylesheet
      end
      add_method_tracer(
        :find_stylesheet!,
        "UserAssetsProvider/find_stylesheet!",
      )

      private

      def file_exists?(name)
        if assets_precompiled?
          File.exists?(local_file_path(name))
        else
          sprockets_asset(name)
        end
      end
      memoize :file_exists?

      # If on-the-fly asset compilation is disabled, we must be precompiling assets.
      def assets_precompiled?
        !Rails.configuration.assets.compile
      rescue
        false
      end

      def local_file_path(name)
        asset_path = asset_path(name)
        if asset_path.match(ABSOLUTE_ASSET_PATH_REGEXP)
          asset_path.gsub!(ABSOLUTE_ASSET_PATH_REGEXP, "assets/")
        end

        File.join(Rails.public_path, asset_path)
      end
      memoize :local_file_path
      add_method_tracer(
        :local_file_path,
        "UserAssetsProvider/local_file_path",
      )

      def sprockets_asset(name)
        asset_path = asset_path(name)
        if asset_path.match(ABSOLUTE_ASSET_PATH_REGEXP)
          asset_path.gsub!(ABSOLUTE_ASSET_PATH_REGEXP, "")
        end

        # Strange thing is since rails 4.2
        # name is passed in like
        # `/assets/mailer-a9c96bd713d0b091297b82053ccd9155b933c00a53595812d755825d1747f42d.css`
        # Before any processing
        # And since `sprockets_asset` is used for preview
        # We just "fix" the name by removing the
        #
        # Regexp taken from gem `asset_sync`
        # https://github.com/AssetSync/asset_sync/blob/v1.2.1/lib/asset_sync/storage.rb#L142
        #
        # Modified to match what we need here (we need `.css` suffix)
        if asset_path =~ /-[0-9a-fA-F]{32,}\.css$/
          asset_path.gsub!(/-[0-9a-fA-F]{32,}\.css$/, ".css")
        end

        Rails.application.assets.find_asset(asset_path)
      end
      add_method_tracer(
        :sprockets_asset,
        "UserAssetsProvider/sprockets_asset",
      )

      def asset_path(name)
        name.gsub(%r{^[/]?assets/}, "")
      end

      Contract String => String
      def stylesheet_content(name)
        if assets_precompiled?
          File.read(local_file_path(name))
        else
          # This will compile and return the asset
          sprockets_asset(name).to_s
        end.strip
      end
      memoize :stylesheet_content
      add_method_tracer(
        :stylesheet_content,
        "UserAssetsProvider/stylesheet_content",
      )
    end
  end
end

1 个答案:

答案 0 :(得分:0)

我想报告我自己的发现

使用NewRelic数据,我们认为大部分时间花在了上 Roadies::Inliner/selector_elements => Roadie::Inliner/elements_matching_selector

似乎具有更多样式规则的样式表将使样式内联需要更长时间

enter image description here

基准代码将类似于:

# frozen_string_literal: true

require "benchmark/ips"

class TestMailer < ::ActionMailer::Base
  def show(benchmark_file_path:)
    return mail(
      from: "somewhere@test.com",
      to: ["somewhere@test.com"],
      subject: "some subject",
      # This is trying to workaround a strange bug in `mail` gem
      # https://github.com/mikel/mail/issues/912#issuecomment-156186383
      content_type: "text/html",
    ) do |format|
      format.html do
        render(
          file: benchmark_file_path,
          layout: false,
        )
      end
    end
  end
end

Benchmark.ips do |x|
  x.warmup  = 5
  x.time    = 60

  options = Roadie::Rails::Options.new(
    # Use your own provider or use built-in providers
    # I use a custom provider which can be used inside a rails app, 
    # See https://github.com/Mange/roadie for built-in providers
    #
    # options.asset_providers = [UserAssetsProvider.new]
    # options.external_asset_providers = [UserAssetsProvider.new]
    options.keep_uninlinable_css = false
  )

  # Need to prepare html_file yourself with 
  # different stylesheet tag pointing to two different stylesheet files

  x.report("fat") do
    message = ::TestMailer.
      show(
        benchmark_file_path: "benchmark-fat-stylesheet.html",
      ).message.tap do |m|

        Roadie::Rails::MailInliner.new(m, options).execute
    end 

    if message.body.to_s =~ /stylesheet/
      raise "stylesheet not processed"
    end
  end

  x.report("slim") do
    message = ::TestMailer.
      show(
        benchmark_file_path: "benchmark-slim-stylesheet.html",
      ).message

    if message.body.to_s =~ /stylesheet/
      raise "stylesheet not processed"
    end
  end

  # Compare the iterations per second of the various reports!
  x.compare!
end