scout_apm_ruby icon indicating copy to clipboard operation
scout_apm_ruby copied to clipboard

Support for ActiveSupport::Notifications

Open vojtad opened this issue 8 months ago • 3 comments

Rails and other gems provide lots of instrumentation data through ActiveSupport::Notifications subscriptions.

For now I am mainly interested in instrumenting ViewComponent which already has instrumentation built in and provides all the data using ActiveSupport::Notifications. The notifications from ViewComponent are documented here. Right now, when we use lots of components all their time is recorded under View without any granularity visible in Scout UI.

Basic code to consume notifications looks like this:

ActiveSupport::Notifications.subscribe("render.view_component") do |event| # or !render.view_component
  event.name    # => "render.view_component"
  event.duration      # => 10 (in milliseconds)
  event.allocations   # => 1826 (objects)
  event.payload # => { name: "MyComponent", identifier: "/Users/mona/project/app/components/my_component.rb" }
end

Look at ActiveSupport::Notifications::Event to find all information that is available.

It would be great if Scout agent would provide a way to easily ingest data from ActiveSupport::Notifications. It could probably even subscribe to some of them by default when gems are detected (like ViewComponent).

I would be happy to try to implement proof of concept with some guidance or try any testing version you would have.

From what I can tell and from discussion with @lancetarn on Discord I adding children ScoutApm::Layers under the View one as components are rendered could be possible. Is this the way to go or should I explore anything else?

vojtad avatar May 05 '25 20:05 vojtad

I've managed to make this work with existing Scout Agent API with a simple monkey patch for ActiveSupport::Notifications. Screenshots from our production env (without the monkey patch) and from my development env (with the monkey patch):

production development

Here is the monkey patch. It allows for easy integration of other notifications as well. It seems to integrate well with the UI for both Time Breakdown and Memory Allocation Breakdown.

# frozen_string_literal: true

module ScoutApmNotifications
  INSTRUMENTED_NOTIFICATIONS = { 'render.view_component' => 'ViewComponent' }.freeze

  module ActiveSupportNotificationsInstrumenterMonkeyPatch
    def instrument(name, payload = {})
      # ignore Rails private events
      # https://github.com/rails/rails/blob/528b62e3869288b5f3ffbd1491f10bad00958c08/actionview/lib/action_view/template.rb#L384
      # https://x.com/rafaelfranca/status/1289978377550442496
      should_instrument = !name.start_with?('!')

      # only instrument notifications we are interested in
      should_instrument &&= INSTRUMENTED_NOTIFICATIONS.key?(name)

      return super unless should_instrument

      scout_apm_type = INSTRUMENTED_NOTIFICATIONS[name]
      scout_apm_name = payload[:name]

      ::ScoutApm::Tracer.instrument(scout_apm_type, scout_apm_name) do
        super
      end
    end
  end

  class Instrument
    def self.setup
      ::ActiveSupport::Notifications.class_eval do
        class << self
          def instrument(name, payload = {})
            # skip check for notifier.listening? in ActiveSupport::Notifications.instrument to receive everything
            # https://github.com/rails/rails/blob/3235827585d87661942c91bc81f64f56d710f0b2/activesupport/lib/active_support/notifications.rb#L208
            instrumenter.instrument(name, payload) { yield payload if block_given? }
          end
        end
      end

      ActiveSupport::Notifications::Instrumenter.prepend(ActiveSupportNotificationsInstrumenterMonkeyPatch)
    end
  end
end

ScoutApmNotifications::Instrument.setup

vojtad avatar May 05 '25 22:05 vojtad

That is so fancy! Thank you for the suggestion and the POC. I imagine we would want to make instrumented notifications configurable; looks like that wouldn't be too difficult. Not sure if there are any other dragons lurking there. We can certainly bring it to the team.

lancetarn avatar May 06 '25 02:05 lancetarn

Thanks. I didn't find any dragons lurking anywhere for now. I will deploy this to production and keep you posted if I find any issues. Looking forward to Scout implementing this officially in the future.

vojtad avatar May 06 '25 09:05 vojtad