Skip to content

Commit

Permalink
Merge pull request #1742 from newrelic/active-support-instrumentation
Browse files Browse the repository at this point in the history
Add Active Support instrumentation
  • Loading branch information
kaylareopelle authored Jan 20, 2023
2 parents c28a618 + 3f89c2d commit f367f6b
Show file tree
Hide file tree
Showing 8 changed files with 371 additions and 12 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# New Relic Ruby Agent Release Notes

## Upcoming Release

This upcoming release of the agent adds instrumentation for Active Support caching operations.

- **Add Active Support Instrumentation**

Instrumentation is now automatically provided for all [Active Support caching](https://guides.rubyonrails.org/caching_with_rails.html) operations. Whenever a caching operation is performed, a New Relic segment is created that contains timing information as well as parameters for the cache key, store, and other relevant data. [PR#1742](https://github.com/newrelic/newrelic-ruby-agent/pull/1742)

| Configuration name | Default | Behavior |
| ----- | ----- | ----- |
| `disable_active_support` | `false` | If `true`, disables Active Support instrumentation. |

## 8.15.0

Version 8.15.0 of the agent confirms compatibility with Ruby 3.2.0, adds instrumentation for concurrent-ruby, and confirms Sinatra 3 compatibility with Padrino 0.15.2. It also enables batching and compression for Infinite Tracing.
Expand Down
16 changes: 10 additions & 6 deletions lib/new_relic/agent/configuration/default_source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1137,33 +1137,37 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil)
:default => false,
:public => true,
:type => Boolean,
:dynamic_name => true,
:allowed_from_server => false,
:description => 'If `true`, disables Action Cable instrumentation.'
},
:disable_activejob => {
:default => false,
:public => true,
:type => Boolean,
:dynamic_name => true,
:allowed_from_server => false,
:description => 'If `true`, disables ActiveJob instrumentation.'
:description => 'If `true`, disables Active Job instrumentation.'
},
:disable_active_storage => {
:default => false,
:public => true,
:type => Boolean,
:dynamic_name => true,
:allowed_from_server => false,
:description => 'If `true`, disables ActiveStorage instrumentation.'
:description => 'If `true`, disables Active Storage instrumentation.'
},
:disable_active_support => {
:default => false,
:public => true,
:type => Boolean,
:allowed_from_server => false,
:description => 'If `true`, disables Active Support instrumentation.'
},
:disable_activerecord_instrumentation => {
:default => value_of(:skip_ar_instrumentation),
:documentation_default => false,
:public => true,
:type => Boolean,
:allowed_from_server => false,
:description => 'If `true`, disables active record instrumentation.'
:description => 'If `true`, disables Active Record instrumentation.'
},
:disable_active_record_notifications => {
:default => false,
Expand Down
23 changes: 17 additions & 6 deletions lib/new_relic/agent/instrumentation/active_support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,22 @@
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
# frozen_string_literal: true

# This is a helper file that will allow apps using ActiveSupport without Rails
# to still leverage all ActiveSupport based instrumentation functionality
# offered by the agent that would otherwise be gated by the detection of Rails.
require 'new_relic/agent/instrumentation/active_support_subscriber'

# ActiveSupport notifications custom events
if !defined?(Rails) && defined?(ActiveSupport::Notifications) && defined?(ActiveSupport::IsolatedExecutionState)
require_relative 'rails_notifications/custom_events'
DependencyDetection.defer do
named :active_support

depends_on do
defined?(ActiveSupport) &&
!NewRelic::Agent::Instrumentation::ActiveSupportSubscriber.subscribed?
end

executes do
NewRelic::Agent.logger.info('Installing ActiveSupport instrumentation')
end

executes do
ActiveSupport::Notifications.subscribe(/\.active_support$/,
NewRelic::Agent::Instrumentation::ActiveSupportSubscriber.new)
end
end
74 changes: 74 additions & 0 deletions lib/new_relic/agent/instrumentation/active_support_subscriber.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
# frozen_string_literal: true

require 'new_relic/agent/instrumentation/notifications_subscriber'

module NewRelic
module Agent
module Instrumentation
class ActiveSupportSubscriber < NotificationsSubscriber
def start(name, id, payload)
return unless state.is_execution_traced?

start_segment(name, id, payload)
rescue => e
log_notification_error(e, name, 'start')
end

def finish(name, id, payload)
return unless state.is_execution_traced?

finish_segment(id, payload)
rescue => e
log_notification_error(e, name, 'finish')
end

def start_segment(name, id, payload)
segment = Tracer.start_segment(name: metric_name(name, payload))

add_segment_params(segment, payload)
push_segment(id, segment)
end

def add_segment_params(segment, payload)
segment.params[:key] = payload[:key]
segment.params[:store] = payload[:store]
segment.params[:hit] = payload[:hit] if payload.key?(:hit)
segment.params[:super_operation] = payload[:super_operation] if payload.key?(:super_operation)
segment
end

def finish_segment(id, payload)
if segment = pop_segment(id)
if exception = exception_object(payload)
segment.notice_error(exception)
end
segment.finish
end
end

def metric_name(name, payload)
store = payload[:store]
method = method_from_name(name)
"Ruby/ActiveSupport/#{store}/#{method}"
end

PATTERN = /\Acache_([^\.]*)\.active_support\z/
UNKNOWN = "unknown".freeze

METHOD_NAME_MAPPING = Hash.new do |h, k|
if PATTERN =~ k
h[k] = $1
else
h[k] = UNKNOWN
end
end

def method_from_name(name)
METHOD_NAME_MAPPING[name]
end
end
end
end
end
12 changes: 12 additions & 0 deletions lib/new_relic/agent/instrumentation/custom_events.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
# frozen_string_literal: true

# This is a helper file that will allow apps using ActiveSupport without Rails
# to still leverage all ActiveSupport based instrumentation functionality
# offered by the agent that would otherwise be gated by the detection of Rails.

# ActiveSupport notifications custom events
if !defined?(Rails) && defined?(ActiveSupport::Notifications) && defined?(ActiveSupport::IsolatedExecutionState)
require_relative 'rails_notifications/custom_events'
end
3 changes: 3 additions & 0 deletions newrelic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ common: &default_settings
# If true, disables Active Storage instrumentation.
# disable_active_storage: false

# If true, disables Active Support instrumentation.
# disable_active_support: false

# If true, disables Active Job instrumentation.
# disable_activejob: false

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
# frozen_string_literal: true

require_relative '../../../test_helper'
require 'new_relic/agent/instrumentation/active_support_subscriber'

if defined?(ActiveSupport)
require_relative 'rails/active_support_subscriber'
else
puts "Skipping tests in #{__FILE__} because Active Support is unavailable"
end
Loading

0 comments on commit f367f6b

Please sign in to comment.