Module: NewRelic::Agent::Instrumentation::ControllerInstrumentation

Included in:
Rack, Sinatra
Defined in:
lib/new_relic/agent/instrumentation/controller_instrumentation.rb

Overview

NewRelic instrumentation for controllers

This instrumentation is applied to the action controller by default if the agent is actively collecting statistics. It will collect statistics for the given action.

In cases where you don’t want to instrument the top level action, but instead have other methods which are dispatched to by your action, and you want to treat these as distinct actions, then what you need to do is use #perform_action_with_newrelic_trace

Defined Under Namespace

Modules: ClassMethods, ClassMethodsShim, Shim

Constant Summary collapse

@@newrelic_java_classes_missing =
false

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(clazz) ⇒ Object

:nodoc:



27
28
29
# File 'lib/new_relic/agent/instrumentation/controller_instrumentation.rb', line 27

def self.included(clazz) # :nodoc:
  clazz.extend(ClassMethods)
end

Instance Method Details

#newrelic_metric_path(action_name_override = nil) ⇒ Object

Must be implemented in the controller class: Determine the path that is used in the metric name for the called controller action. Of the form controller_path/action_name



166
167
168
# File 'lib/new_relic/agent/instrumentation/controller_instrumentation.rb', line 166

def newrelic_metric_path(action_name_override = nil) # :nodoc:
  raise "Not implemented!"
end

#perform_action_with_newrelic_profile(metric_name, path, args) ⇒ Object

Experimental



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/new_relic/agent/instrumentation/controller_instrumentation.rb', line 328

def perform_action_with_newrelic_profile(metric_name, path, args)
  agent = NewRelic::Agent.instance
  stats_engine = agent.stats_engine
  NewRelic::Agent.trace_execution_scoped [metric_name, "Controller"] do
    stats_engine.transaction_name = metric_name
    NewRelic::Agent.disable_all_tracing do
      available_params = self.respond_to?(:params) ? params : {} 
      # Not sure if we need to get the params and request...
      filtered_params = (respond_to? :filter_parameters) ? filter_parameters(available_params) : available_params
      available_request = (respond_to? :request) ? request : nil
      agent.transaction_sampler.notice_transaction(path, available_request, filtered_params)

      # turn on profiling
      profile = RubyProf.profile do
        if block_given?
          yield
        else
          perform_action_without_newrelic_trace(*args)
        end
      end
      agent.transaction_sampler.notice_profile profile
    end
  end
end

#perform_action_with_newrelic_trace(*args, &block) ⇒ Object

Yield to the given block with NewRelic tracing. Used by default instrumentation on controller actions in Rails and Merb. But it can also be used in custom instrumentation of controller methods and background tasks.

Here’s a more verbose version of the example shown in ClassMethods#add_transaction_tracer using this method instead of #add_transaction_tracer.

Below is a controller with an invoke_operation action which dispatches to more specific operation methods based on a parameter (very dangerous, btw!). With this instrumentation, the invoke_operation action is ignored but the operation methods show up in RPM as if they were first class controller actions

MyController < ActionController::Base
  include NewRelic::Agent::Instrumentation::ControllerInstrumentation
  # dispatch the given op to the method given by the service parameter.
  def invoke_operation
    op = params['operation']
    perform_action_with_newrelic_trace(:name => op) do
      send op, params['message']
    end
  end
  # Ignore the invoker to avoid double counting
  newrelic_ignore :only => 'invoke_operation'
end

By passing a block in combination with specific arguments, you can invoke this directly to capture high level information in several contexts:

  • Pass :category => :controller and :name => actionname to treat the block as if it were a controller action, invoked inside a real action. :name is the name of the method, and is used in the metric name.

When invoked directly, pass in a block to measure with some combination of options:

  • :category => :controller indicates that this is a controller action and will appear with all the other actions. This is the default.

  • :category => :task indicates that this is a background task and will show up in RPM with other background tasks instead of in the controllers list

  • :category => :rack if you are instrumenting a rack middleware call. The :name is optional, useful if you have more than one potential transaction in the #call.

  • :category => :uri indicates that this is a web transaction whose name is a normalized URI, where ‘normalized’ means the URI does not have any elements with data in them such as in many REST URIs.

  • :params => {...} to provide information about the context of the call, used in transaction trace display, for example: :params => { :account => @account.name, :file => file.name }

  • :name => action_name is used to specify the action name used as part of the metric name

  • :force => true indicates you should capture all metrics even if the #newrelic_ignore directive was specified

  • :class_name => aClass.name is used to override the name of the class when used inside the metric name. Default is the current class.

  • :path => metric_path is deprecated in the public API. It allows you to set the entire metric after the category part. Overrides all the other options.

If a single argument is passed in, it is treated as a metric path. This form is deprecated.



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/new_relic/agent/instrumentation/controller_instrumentation.rb', line 240

def perform_action_with_newrelic_trace(*args, &block)

  # Skip instrumentation based on the value of 'do_not_trace' and if 
  # we aren't calling directly with a block.
  if !block_given? && _is_filtered?('do_not_trace')
    # Tell the dispatcher instrumentation that we ignored this action and it shouldn't
    # be counted for the overall HTTP operations measurement.
    Thread.current[:newrelic_ignore_controller] = true
    # Also ignore all instrumentation in the call sequence
    NewRelic::Agent.disable_all_tracing do
      return perform_action_without_newrelic_trace(*args)
    end
  end
  agent = NewRelic::Agent.instance
  stats_engine = agent.stats_engine
  
  # reset this in case we came through a code path where the top level controller is ignored
  Thread.current[:newrelic_ignore_controller] = nil
  apdex_start = (Thread.current[:started_on] || Thread.current[:newrelic_dispatcher_start] || Time.now).to_f
  force = false
  # If a block was passed in, then the arguments represent options for the instrumentation,
  # not app method arguments.
  if block_given? && args.any?
    force = args.last.is_a?(Hash) && args.last[:force]
    category, path, available_params = _convert_args_to_path(args)
  else
    category = 'Controller'
    path = newrelic_metric_path
    available_params = self.respond_to?(:params) ? self.params : {} 
  end
  metric_name = category + '/' + path 

  return perform_action_with_newrelic_profile(metric_name, path, args, &block) if NewRelic::Control.instance.profiling?

  filtered_params = (respond_to? :filter_parameters) ? filter_parameters(available_params) : available_params

  agent.ensure_worker_thread_started
  
  NewRelic::Agent.trace_execution_scoped [metric_name, "Controller"], :force => force do
    # Last one (innermost) to set the transaction name on the call stack wins.
    stats_engine.transaction_name = metric_name
    available_request = (respond_to? :request) ? request : nil
    agent.transaction_sampler.notice_transaction(path, available_request, filtered_params)
    
    jruby_cpu_start = _jruby_cpu_time
    process_cpu_start = _process_cpu
    failed = false
    
    begin
      # run the action
      if block_given?
        yield
      else
        perform_action_without_newrelic_trace(*args)
      end
    rescue Exception => e
      NewRelic::Agent.instance.error_collector.notice_error(e, nil, metric_name, filtered_params)
      failed = true
      raise e
    ensure
      if NewRelic::Agent.is_execution_traced?
        cpu_burn = nil
        if process_cpu_start
          cpu_burn = _process_cpu - process_cpu_start
        elsif jruby_cpu_start
          cpu_burn = _jruby_cpu_time - jruby_cpu_start
          stats_engine.get_stats_no_scope(NewRelic::Metrics::USER_TIME).record_data_point(cpu_burn)
        end
        agent.transaction_sampler.notice_transaction_cpu_time(cpu_burn) if cpu_burn
        
        # do the apdex bucketing
        #
        unless _is_filtered?('ignore_apdex')
          ending = Time.now.to_f
          # this uses the start of the dispatcher or the mongrel
          # thread: causes apdex to show too little capacity
          apdex_overall(apdex_start, ending, failed)
          # this uses the start time of the controller action:
          # does not include capacity problems since those aren't
          # per controller
          apdex_controller(apdex_start, ending, failed, path)
        end
      end
    end
  end
end