diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b1f2816be7f..b918e909c8c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -24,6 +24,11 @@ Metrics/AbcSize: - lib/new_relic/cli/commands/deployments.rb - test/**/* +# Offense count: 1 +Metrics/CollectionLiteralLength: + Exclude: + - 'lib/new_relic/agent/configuration/default_source.rb' + # Offense count: 7 Minitest/AssertRaisesCompoundBody: Exclude: diff --git a/lib/new_relic/agent/configuration/default_source.rb b/lib/new_relic/agent/configuration/default_source.rb index 7eed49ece08..9dc59e1dd13 100644 --- a/lib/new_relic/agent/configuration/default_source.rb +++ b/lib/new_relic/agent/configuration/default_source.rb @@ -1657,6 +1657,13 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil) :allowed_from_server => false, :description => 'Controls auto-instrumentation of Stripe at startup. May be one of: `enabled`, `disabled`.' }, + :'instrumentation.view_component' => { + :default => 'enabled', + :public => true, + :type => String, + :allowed_from_server => false, + :description => 'Controls auto-instrumentation of ViewComponent at startup. May be one of: `enabled`, `disabled`.' + }, :'stripe.user_data.include' => { default: NewRelic::EMPTY_ARRAY, public: true, diff --git a/lib/new_relic/agent/instrumentation/view_component.rb b/lib/new_relic/agent/instrumentation/view_component.rb new file mode 100644 index 00000000000..7ce21735924 --- /dev/null +++ b/lib/new_relic/agent/instrumentation/view_component.rb @@ -0,0 +1,30 @@ +# 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 'view_component/instrumentation' +require_relative 'view_component/chain' +require_relative 'view_component/prepend' + +DependencyDetection.defer do + named :'view_component' + + depends_on do + NewRelic::Agent.config[:'instrumentation.view_component'] == 'enabled' + end + + depends_on do + defined?(ViewComponent) && + ViewComponent::Base.method_defined?(:render_in) + end + + executes do + NewRelic::Agent.logger.info('Installing view_component instrumentation') + + if use_prepend? + prepend_instrument ViewComponent::Base, NewRelic::Agent::Instrumentation::ViewComponent::Prepend + else + chain_instrument NewRelic::Agent::Instrumentation::ViewComponent::Chain + end + end +end diff --git a/lib/new_relic/agent/instrumentation/view_component/chain.rb b/lib/new_relic/agent/instrumentation/view_component/chain.rb new file mode 100644 index 00000000000..41a2f21f032 --- /dev/null +++ b/lib/new_relic/agent/instrumentation/view_component/chain.rb @@ -0,0 +1,21 @@ +# 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 + +module NewRelic::Agent::Instrumentation + module ViewComponent::Chain + def self.instrument! + ::ViewComponent.class_eval do + include NewRelic::Agent::Instrumentation::ViewComponent + + alias_method(:render_in_without_tracing, :render_in) + + def render_in(*args) + render_in_with_tracing(*args) do + render_in_without_tracing(*args) + end + end + end + end + end +end diff --git a/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb b/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb new file mode 100644 index 00000000000..28739e4b5c2 --- /dev/null +++ b/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb @@ -0,0 +1,40 @@ +# 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 + +module NewRelic::Agent::Instrumentation + module ViewComponent + INSTRUMENTATION_NAME = 'view_component' + + def render_in_with_tracing(*args) + NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME) + + component = self.class.name + identifier = self.class.identifier + + segment = NewRelic::Agent::Tracer.start_segment( + name: metric_name(identifier, component) + ) + + begin + NewRelic::Agent::Tracer.capture_segment_error(segment) { yield } + ensure + NewRelic::Agent::Transaction::Segment.finish(segment) + end + end + + def metric_name(identifier, component) + "View/#{metric_path(identifier)}/#{component}" + end + + def metric_path(identifier) + if identifier.nil? + 'component' + elsif identifier && (parts = identifier.split('/')).size > 1 + parts[-2..-1].join('/') # Get filepath by assuming the Rails' structure: app/components/home/example_component.rb + else + NewRelic::Agent::UNKNOWN_METRIC + end + end + end +end diff --git a/lib/new_relic/agent/instrumentation/view_component/prepend.rb b/lib/new_relic/agent/instrumentation/view_component/prepend.rb new file mode 100644 index 00000000000..190c6b9344c --- /dev/null +++ b/lib/new_relic/agent/instrumentation/view_component/prepend.rb @@ -0,0 +1,13 @@ +# 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 + +module NewRelic::Agent::Instrumentation + module ViewComponent::Prepend + include NewRelic::Agent::Instrumentation::ViewComponent + + def render_in(*args) + render_in_with_tracing(*args) { super } + end + end +end diff --git a/test/multiverse/suites/view_component/config/newrelic.yml b/test/multiverse/suites/view_component/config/newrelic.yml new file mode 100644 index 00000000000..2eae61572bf --- /dev/null +++ b/test/multiverse/suites/view_component/config/newrelic.yml @@ -0,0 +1,19 @@ +--- +development: + error_collector: + enabled: true + apdex_t: 0.5 + monitor_mode: true + license_key: bootstrap_newrelic_admin_license_key_000 + instrumentation: + view_component: <%= $render_in %> + app_name: test + log_level: debug + host: 127.0.0.1 + api_host: 127.0.0.1 + transaction_trace: + record_sql: obfuscated + enabled: true + stack_trace_threshold: 0.5 + transaction_threshold: 1.0 + capture_params: false diff --git a/test/multiverse/suites/view_component/view_component_instrumentation_test.rb b/test/multiverse/suites/view_component/view_component_instrumentation_test.rb new file mode 100644 index 00000000000..e2e3609a5ff --- /dev/null +++ b/test/multiverse/suites/view_component/view_component_instrumentation_test.rb @@ -0,0 +1,15 @@ +# 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 + +class ViewComponentInstrumentationTest < Minitest::Test + def setup + @stats_engine = NewRelic::Agent.instance.stats_engine + end + + def teardown + NewRelic::Agent.instance.stats_engine.clear_stats + end + + # Add tests here +end