Module: NewRelic::Agent::MethodTracer::ClassMethods

Defined in:
lib/new_relic/agent/method_tracer.rb

Instance Method Summary collapse

Instance Method Details

#add_method_tracer(method_name, metric_name_code = nil, options = {}) ⇒ Object

Add a method tracer to the specified method.

Common Options

  • :push_scope => false specifies this method tracer should not keep track of the caller; it will not show up in controller breakdown pie charts.

  • :metric => false specifies that no metric will be recorded. Instead the call will show up in transaction traces as well as traces shown in Developer Mode.

Uncommon Options

  • :scoped_metric_only => true indicates that the unscoped metric should not be recorded. Normally two metrics are potentially created on every invocation: the aggregate method where statistics for all calls of that metric are stored, and the “scoped metric” which records the statistics for invocations in a particular scope–generally a controller action. This option indicates that only the second type should be recorded. The effect is similar to :metric => false but in addition you will also see the invocation in breakdown pie charts.

  • :deduct_call_time_from_parent => false indicates that the method invocation time should never be deducted from the time reported as ‘exclusive’ in the caller. You would want to use this if you are tracing a recursive method or a method that might be called inside another traced method.

  • :code_header and :code_footer specify ruby code that is inserted into the tracer before and after the call.

  • :force = true will ensure the metric is captured even if called inside an untraced execution call. (See NewRelic::Agent#disable_all_tracing)

Overriding the metric name

metric_name_code is a string that is eval’d to get the name of the metric associated with the call, so if you want to use interpolaion evaluated at call time, then single quote the value like this:

add_method_tracer :foo, 'Custom/#{self.class.name}/foo'

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

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

Examples

Instrument foo only for custom views–will not show up in transaction traces or caller breakdown graphs:

add_method_tracer :foo, :push_scope => false

Instrument foo just for transaction traces only:

add_method_tracer :foo, :metric => false

Instrument foo so it shows up in transaction traces and caller breakdown graphs for actions:

add_method_tracer :foo

which is equivalent to:

add_method_tracer :foo, 'Custom/#{self.class.name}/foo', :push_scope => true, :metric => true

Instrument the class method foo with the metric name ‘Custom/People/fetch’:

class << self
  add_method_tracer :foo, 'Custom/People/fetch'
end


231
232
233
234
235
236
237
238
239
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
# File 'lib/new_relic/agent/method_tracer.rb', line 231

def add_method_tracer(method_name, metric_name_code=nil, options = {})
  # for backward compatibility:
  if !options.is_a?(Hash)
    options = {:push_scope => options} 
  end
  # in case they omit the metric name code
  if metric_name_code.is_a?(Hash)
    options.merge(metric_name_code)
  end
  if (unrecognized = options.keys - [:force, :metric, :push_scope, :deduct_call_time_from_parent, :code_header, :code_footer, :scoped_metric_only]).any?
    fail "Unrecognized options in add_method_tracer_call: #{unrecognized.join(', ')}"
  end
  # options[:push_scope] true if we are noting the scope of this for
  # stats collection as well as the transaction tracing
  options[:push_scope] = true if options[:push_scope].nil?
  # options[:metric] true if you are tracking stats for a metric, otherwise
  # it's just for transaction tracing.
  options[:metric] = true if options[:metric].nil?
  options[:force] = false if options[:force].nil?
  options[:deduct_call_time_from_parent] = false if options[:deduct_call_time_from_parent].nil? && !options[:metric]
  options[:deduct_call_time_from_parent] = true if options[:deduct_call_time_from_parent].nil?
  options[:code_header] ||= ""
  options[:code_footer] ||= ""
  options[:scoped_metric_only] ||= false
  
  klass = (self === Module) ? "self" : "self.class"
  # Default to the class where the method is defined.
  metric_name_code = "Custom/#{self.name}/#{method_name.to_s}" unless metric_name_code
  
  unless method_defined?(method_name) || private_method_defined?(method_name)
    NewRelic::Control.instance.log.warn("Did not trace #{self.name}##{method_name} because that method does not exist")
    return
  end
  
  traced_method_name = _traced_method_name(method_name, metric_name_code)
  if method_defined? traced_method_name
    NewRelic::Control.instance.log.warn("Attempt to trace a method twice with the same metric: Method = #{method_name}, Metric Name = #{metric_name_code}")
    return
  end
  
  fail "Can't add a tracer where push_scope is false and metric is false" if options[:push_scope] == false && !options[:metric]
  
  header = ""
  if !options[:force]
    header << "return #{_untraced_method_name(method_name, metric_name_code)}(*args, &block) unless NewRelic::Agent.is_execution_traced?\n"
  end
  header << options[:code_header] if options[:code_header]
  if options[:push_scope] == false
    code = <<-CODE
  def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
    #{header}
    t0 = Time.now.to_f
    stats = NewRelic::Agent.instance.stats_engine.get_stats_no_scope "#{metric_name_code}"
    begin
      #{"NewRelic::Agent.instance.push_trace_execution_flag(true)\n" if options[:force]}
      #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)\n
    ensure
      #{"NewRelic::Agent.instance.pop_trace_execution_flag\n" if options[:force] }
      duration = Time.now.to_f - t0
      stats.trace_call(duration)
      #{options[:code_footer]}
    end
  end
CODE
  else
    code = <<-CODE
def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
  #{options[:code_header]}
  result = #{klass}.trace_execution_scoped("#{metric_name_code}", 
            :metric => #{options[:metric]},
            :forced => #{options[:force]}, 
            :deduct_call_time_from_parent => #{options[:deduct_call_time_from_parent]}, 
            :scoped_metric_only => #{options[:scoped_metric_only]}) do
    #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
  end
  #{options[:code_footer]}
  result
end
CODE
  end
  
  class_eval code, __FILE__, __LINE__
  
  alias_method _untraced_method_name(method_name, metric_name_code), method_name
  alias_method method_name, _traced_method_name(method_name, metric_name_code)
  
  NewRelic::Control.instance.log.debug("Traced method: class = #{self.name}, method = #{method_name}, "+
  "metric = '#{metric_name_code}', options: #{options.inspect}")
end

#remove_method_tracer(method_name, metric_name_code) ⇒ Object

For tests only because tracers must be removed in reverse-order from when they were added, or else other tracers that were added to the same method may get removed as well.



324
325
326
327
328
329
330
331
332
333
# File 'lib/new_relic/agent/method_tracer.rb', line 324

def remove_method_tracer(method_name, metric_name_code) # :nodoc:
  return unless NewRelic::Control.instance.agent_enabled?
  
  if method_defined? "#{_traced_method_name(method_name, metric_name_code)}"
    alias_method method_name, "#{_untraced_method_name(method_name, metric_name_code)}"
    undef_method "#{_traced_method_name(method_name, metric_name_code)}"
  else
    raise "No tracer for '#{metric_name_code}' on method '#{method_name}'"
  end
end