module NewRelic::Agent::MethodTracer::ClassMethods

Defines methods used at the class level, for adding instrumentation @api public

Public Instance Methods

add_method_tracer(method_name, metric_name = nil, options = {}) click to toggle source

Add a method tracer to the specified method.

By default, this will cause invocations of the traced method to be recorded in transaction traces, and in a metric named after the class and method. It will also make the method show up in transaction-level breakdown charts and tables.

Overriding the metric name

metric_name is a String or Proc. If a Proc is given, it is bound to the object that called the traced method. For example:

add_method_tracer :foo, -> { "Custom/#{self.class.name}/foo" }

This would name the metric according to the class of the runtime instance, as opposed to the class where foo is defined.

If not provided, the metric name will be Custom/ClassName/method_name.

@param method_name [Symbol] the name of the method to trace @param metric_name [String,Proc,Array] the metric name to record calls to

the traced method under. This may be either a String, or a Proc
to be evaluated at call-time in order to determine the metric
name dynamically.
This method also accepts an array of Strings/Procs, in which case the
first metric given will be scoped, while the remaining metrics will be
recorded as though passed with :push_scope => false. If an Array of
metric names is given with :push_scope => false, all metrics will be
unscoped.

@param [Hash] options additional options controlling how the method is

traced.

@option options [Boolean] :push_scope (true) If false, the traced method will

not appear in transaction traces or breakdown charts, and it will
only be visible in custom dashboards.

@option options [Boolean] :metric (true) If false, the traced method will

only appear in transaction traces, but no metrics will be recorded
for it.

@option options [Proc] :code_header (”) Ruby code to be inserted and run

before the tracer begins timing.

@option options [Proc] :code_footer (”) Ruby code to be inserted and run

after the tracer stops timing.

@example

add_method_tracer :foo

# With a custom metric name
add_method_tracer :foo, "Custom/MyClass/foo"
add_method_tracer :bar, -> { "Custom/#{self.class.name}/bar" }

# Instrument foo only for custom dashboards (not in transaction
# traces or breakdown charts)
add_method_tracer :foo, 'Custom/foo', :push_scope => false

# Instrument foo in transaction traces only
add_method_tracer :foo, 'Custom/foo', :metric => false

@api public

# File lib/new_relic/agent/method_tracer.rb, line 246
def add_method_tracer(method_name, metric_name = nil, options = {})
  ::NewRelic::Agent.add_or_defer_method_tracer(self, method_name, metric_name, options)
end

Private Instance Methods

_nr_add_method_tracer_now(method_name, metric_name, options) click to toggle source
# File lib/new_relic/agent/method_tracer.rb, line 266
def _nr_add_method_tracer_now(method_name, metric_name, options)
  NewRelic::Agent.record_api_supportability_metric(:add_method_tracer)
  return unless newrelic_method_exists?(method_name)

  remove_method_tracer(method_name) if method_traced?(method_name)

  options = _nr_validate_method_tracer_options(method_name, options)

  visibility = NewRelic::Helper.instance_method_visibility(self, method_name)

  scoped_metric, unscoped_metrics = _nr_scoped_unscoped_metrics(metric_name, method_name, push_scope: options[:push_scope])

  _nr_define_traced_method(method_name, scoped_metric: scoped_metric, unscoped_metrics: unscoped_metrics,
    code_header: options[:code_header], code_footer: options[:code_footer],
    record_metrics: options[:metric], visibility: visibility,
    code_information: options[:code_information])

  prepend(_nr_traced_method_module)

  ::NewRelic::Agent.logger.debug("Traced method: class = #{_nr_derived_class_name}," +
                                 "method = #{method_name}, " +
                                 "metric = '#{metric_name}'")
end
_nr_define_traced_method(method_name, scoped_metric: nil, unscoped_metrics: [], code_header: nil, code_footer: nil, record_metrics: true, visibility: :public, code_information: {}) click to toggle source
Calls superclass method
# File lib/new_relic/agent/method_tracer.rb, line 303
def _nr_define_traced_method(method_name, scoped_metric: nil, unscoped_metrics: [],
  code_header: nil, code_footer: nil, record_metrics: true,
  visibility: :public, code_information: {})

  _nr_traced_method_module.module_eval do
    define_method(method_name) do |*args, &block|
      return super(*args, &block) unless NewRelic::Agent.tl_is_execution_traced?

      scoped_metric_eval, unscoped_metrics_eval = nil, []

      scoped_metric_eval = case scoped_metric
        when Proc
          instance_exec(*args, &scoped_metric)
        when String
          scoped_metric
      end

      unscoped_metrics_eval = unscoped_metrics.map do |metric|
        metric.kind_of?(Proc) ? instance_exec(*args, &metric) : metric.to_s
      end

      instance_exec(&code_header) if code_header.kind_of?(Proc)

      # NOTE: Calling ::NewRelic::Agent::MethodTracer.trace_execution_scoped and
      # .trace_execution_unscoped below relies on the fact that MethodTracer is included
      # in Module on agent startup. If/when this changes, these methods should be
      # explicitly namespaced and extended in MethodTracer.

      # If tracing multiple metrics on this method, nest one unscoped trace inside the scoped trace.
      begin
        if scoped_metric_eval
          ::NewRelic::Agent::MethodTracer.trace_execution_scoped(scoped_metric_eval,
            metric: record_metrics,
            internal: true,
            code_information: code_information) do
            if unscoped_metrics_eval.empty?
              super(*args, &block)
            else
              ::NewRelic::Agent::MethodTracer.trace_execution_unscoped(unscoped_metrics_eval, internal: true) do
                super(*args, &block)
              end
            end
          end
        elsif !unscoped_metrics_eval.empty?
          ::NewRelic::Agent::MethodTracer.trace_execution_unscoped(unscoped_metrics_eval, internal: true) do
            super(*args, &block)
          end
        end
      ensure
        instance_exec(&code_footer) if code_footer.kind_of?(Proc)
      end
    end

    send(visibility, method_name)
    ruby2_keywords(method_name) if respond_to?(:ruby2_keywords, true)
  end
end
_nr_scoped_unscoped_metrics(metric_name, method_name, push_scope: true) click to toggle source

See add_method_tracer; if multiple metric names are given, the first is treated as scoped, the rest unscoped. If options is false, all given metrics are unscoped.

# File lib/new_relic/agent/method_tracer.rb, line 293
def _nr_scoped_unscoped_metrics(metric_name, method_name, push_scope: true)
  if metric_name.is_a?(Array) && push_scope
    [metric_name.shift, metric_name]
  elsif push_scope
    [metric_name || _nr_default_metric_name(method_name), []]
  else
    [nil, Array(metric_name)]
  end
end