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

Included in:
NewRelic::Agent::MethodTracer::ClassMethods
Defined in:
lib/new_relic/agent/method_tracer.rb

Overview

contains methods refactored out of the #add_method_tracer method

Constant Summary collapse

ALLOWED_KEYS =
[:force, :metric, :push_scope, :deduct_call_time_from_parent, :code_header, :code_footer, :scoped_metric_only].freeze
DEFAULT_SETTINGS =
{:push_scope => true, :metric => true, :force => false, :code_header => "", :code_footer => "", :scoped_metric_only => false}.freeze

Instance Method Summary collapse

Instance Method Details

#any_unrecognized_keys?(expected, given) ⇒ Boolean

used to verify that the keys passed to NewRelic::Agent::MethodTracer::ClassMethods#add_method_tracer are valid. checks the expected list against the list actually provided

Returns:

  • (Boolean)


270
271
272
# File 'lib/new_relic/agent/method_tracer.rb', line 270

def any_unrecognized_keys?(expected, given)
  unrecognized_keys(expected, given).any?
end

#assemble_code_header(method_name, metric_name_code, options) ⇒ Object

Returns a code snippet to be eval’d that skips tracing when the agent is not tracing execution. turns instrumentation into effectively one method call overhead when the agent is disabled



348
349
350
351
352
# File 'lib/new_relic/agent/method_tracer.rb', line 348

def assemble_code_header(method_name, metric_name_code, options)
  unless options[:force]
    "return #{_untraced_method_name(method_name, metric_name_code)}(*args, &block) unless NewRelic::Agent.is_execution_traced?\n"
  end.to_s + options[:code_header].to_s
end

#check_for_illegal_keys!(options) ⇒ Object

raises an error when the NewRelic::Agent::MethodTracer::ClassMethods#add_method_tracer method is called with improper keys. This aids in debugging new instrumentation by failing fast



278
279
280
281
282
# File 'lib/new_relic/agent/method_tracer.rb', line 278

def check_for_illegal_keys!(options)
  if any_unrecognized_keys?(ALLOWED_KEYS, options)
    raise "Unrecognized options in add_method_tracer_call: #{unrecognized_keys(ALLOWED_KEYS, options).join(', ')}"
  end
end

#check_for_push_scope_and_metric(options) ⇒ Object

validity checking - add_method_tracer must receive either push scope or metric, or else it would record no data. Raises an error if this is the case



297
298
299
300
301
# File 'lib/new_relic/agent/method_tracer.rb', line 297

def check_for_push_scope_and_metric(options)
  unless options[:push_scope] || options[:metric]
    raise "Can't add a tracer where push_scope is false and metric is false"
  end
end

#code_to_eval(method_name, metric_name_code, options) ⇒ Object

Decides which code snippet we should be eval’ing in this context, based on the options.



395
396
397
398
399
400
401
402
# File 'lib/new_relic/agent/method_tracer.rb', line 395

def code_to_eval(method_name, metric_name_code, options)
  options = validate_options(options)
  if options[:push_scope]
    method_with_push_scope(method_name, metric_name_code, options)
  else
    method_without_push_scope(method_name, metric_name_code, options)
  end
end

#default_metric_name_code(method_name) ⇒ Object

Default to the class where the method is defined.

Example:

Foo.default_metric_name_code('bar') #=> "Custom/#{Foo.name}/bar"


321
322
323
# File 'lib/new_relic/agent/method_tracer.rb', line 321

def default_metric_name_code(method_name)
  "Custom/#{self.name}/#{method_name.to_s}"
end

#method_with_push_scope(method_name, metric_name_code, options) ⇒ Object

returns an eval-able string that contains the tracing code for a fully traced metric including scoping



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/new_relic/agent/method_tracer.rb', line 376

def method_with_push_scope(method_name, metric_name_code, options)
  klass = (self === Module) ? "self" : "self.class"

  "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"
end

#method_without_push_scope(method_name, metric_name_code, options) ⇒ Object

returns an eval-able string that contains the traced method code used if the agent is not creating a scope for use in scoped metrics.



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/new_relic/agent/method_tracer.rb', line 357

def method_without_push_scope(method_name, metric_name_code, options)
  "def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
    #{assemble_code_header(method_name, metric_name_code, options)}
    t0 = Time.now
    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 - t0).to_f
      stats.trace_call(duration)
      #{options[:code_footer]}
    end
  end"
end

#newrelic_method_exists?(method_name) ⇒ Boolean

Checks to see if the method we are attempting to trace actually exists or not. #add_method_tracer can’t do anything if the method doesn’t exist.

Returns:

  • (Boolean)


328
329
330
331
332
# File 'lib/new_relic/agent/method_tracer.rb', line 328

def newrelic_method_exists?(method_name)
  exists = 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") unless exists
  exists
end

#set_deduct_call_time_based_on_metric(options) ⇒ Object

Sets the options for deducting call time from parents. This defaults to true if we are recording a metric, but can be overridden by the user if desired.

has the effect of not allowing overlapping times, and should generally be true



290
291
292
# File 'lib/new_relic/agent/method_tracer.rb', line 290

def set_deduct_call_time_based_on_metric(options)
  {:deduct_call_time_from_parent => !!options[:metric]}.merge(options)
end

#traced_method_exists?(method_name, metric_name_code) ⇒ Boolean

Checks to see if we have already traced a method with a given metric by checking to see if the traced method exists. Warns the user if methods are being double-traced to help with debugging custom instrumentation.

Returns:

  • (Boolean)


338
339
340
341
342
# File 'lib/new_relic/agent/method_tracer.rb', line 338

def traced_method_exists?(method_name, metric_name_code)
  exists = method_defined?(_traced_method_name(method_name, metric_name_code))
  NewRelic::Control.instance.log.warn("Attempt to trace a method twice with the same metric: Method = #{method_name}, Metric Name = #{metric_name_code}") if exists
  exists
end

#unrecognized_keys(expected, given) ⇒ Object

used to verify that the keys passed to NewRelic::Agent::MethodTracer::ClassMethods#add_method_tracer are valid. Returns a list of keys that were unexpected



262
263
264
# File 'lib/new_relic/agent/method_tracer.rb', line 262

def unrecognized_keys(expected, given)
  given.keys - expected
end

#validate_options(options) ⇒ Object

Checks the provided options to make sure that they make sense. Raises an error if the options are incorrect to assist with debugging, so that errors occur at class construction time rather than instrumentation run time

Raises:

  • (TypeError)


309
310
311
312
313
314
315
# File 'lib/new_relic/agent/method_tracer.rb', line 309

def validate_options(options)
  raise TypeError.new("provided options must be a Hash") unless options.is_a?(Hash)
  check_for_illegal_keys!(options)
  options = set_deduct_call_time_based_on_metric(DEFAULT_SETTINGS.merge(options))
  check_for_push_scope_and_metric(options)
  options
end