module TingYun::Instrumentation::Support::MethodInstrumentation::ClassMethods

Constants

DEFAULT_SETTINGS

Public Instance Methods

add_method_tracer(method_name, metric_name = nil, opt={}) 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 that is eval'd to get the name of the metric associated with the call, so if you want to use interpolation evaluated at call time, then single quote the value like this:

add_method_tracer :foo, 'tingyun/#{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 tingyun/ClassName/method_name.

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

the traced method under. This may be either a static string, or Ruby
code to be evaluated at call-time in order to determine the metric
name dynamically.

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

traced.

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

not appear in transaction traces(the components) or breakdown charts, and it will
only be visible in custom dashboards(the generals).

@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 [String] :before_code ('') Ruby code to be inserted and run

before the tracer begins timing.

@option options [String] :after_code ('') 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, 'tingyun/#{self.class.name}/foo'

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

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

@api public

# File lib/ting_yun/instrumentation/support/method_instrumentation.rb, line 188
def add_method_tracer(method_name, metric_name = nil, opt={})
  return unless method_exists?(method_name)
  metric_name ||= default_metric_name(method_name)
  return if traced_method_exists?(method_name, metric_name)

  traced_method = tingyun_eval(method_name, metric_name,opt)

  visibility = TingYun::Helper.instance_method_visibility self, method_name
  class_eval traced_method, __FILE__, __LINE__

  alias_method define_untrace_method_name(method_name, metric_name), method_name
  alias_method method_name, define_trace_method_name(method_name, metric_name)
  send visibility, method_name
  send visibility, define_trace_method_name(method_name, metric_name)
  ::TingYun::Agent.logger.debug("Traced method: class = #{self.name},"+
                                    "method = #{method_name}, "+
                                    "metric = '#{metric_name}'")
end
default_metric_name(method_name) click to toggle source

Example:

Foo.default_metric_name('bar') #=> "tingyun/#{Foo.name}/bar"
# File lib/ting_yun/instrumentation/support/method_instrumentation.rb, line 63
def default_metric_name(method_name)
  "tingyun/#{self.name}/#{method_name.to_s}"
end
define_method_with_scope(method_name,metric_name,options) click to toggle source
# File lib/ting_yun/instrumentation/support/method_instrumentation.rb, line 105
def define_method_with_scope(method_name,metric_name,options)
  "def #{define_trace_method_name(method_name,metric_name)}(*args, &block)
      #{options[:before_code]}

      result = ::TingYun::Agent::MethodTracerHelpers.trace_execution_scoped(\"#{metric_name}\",
              :metric => #{options[:metric]}) do
        #{define_untrace_method_name(method_name, metric_name)}(*args, &block)
      end
      #{options[:after_code]}
      result
   end"
end
define_method_without_scope(method_name,metric_name,options) click to toggle source
# File lib/ting_yun/instrumentation/support/method_instrumentation.rb, line 118
def define_method_without_scope(method_name,metric_name,options)
  "def #{define_trace_method_name(method_name,metric_name)}(*args, &block)
      return #{define_untrace_method_name(method_name, metric_name)}(*args, &block) unless TingYun::Agent::TransactionState.tl_get.execution_traced?\n
  #{options[:before_code]}
      t0 = Time.now
      begin
        #{define_untrace_method_name(method_name, metric_name)}(*args, &block)\n
      ensure
        duration = (Time.now - t0).to_f
        ::TingYun::Agent.record_metric(\"#{metric_name}\", duration)
        #{options[:after_code]}
      end
   end"
end
method_exists?(method_name) click to toggle source
# File lib/ting_yun/instrumentation/support/method_instrumentation.rb, line 55
def method_exists?(method_name)
  exists = method_defined?(method_name) || private_method_defined?(method_name)
  ::TingYun::Agent.logger.error("Did not trace #{self.name}##{method_name} because that method does not exist") unless exists
  exists
end
tingyun_eval(method_name, metric_name, options) click to toggle source
# File lib/ting_yun/instrumentation/support/method_instrumentation.rb, line 87
def tingyun_eval(method_name, metric_name, options)
  options = validate_options(method_name, options)
  define_method_with_scope(method_name, metric_name, options)
end
tingyun_evalV2(method_name, metric_name, options) click to toggle source
# File lib/ting_yun/instrumentation/support/method_instrumentation.rb, line 78
def tingyun_evalV2(method_name, metric_name, options)
  options = validate_options(method_name, options)
  if options[:scope]
    define_method_with_scope(method_name, metric_name, options)
  else
    define_method_without_scope(method_name,metric_name, options)
  end
end
traced_method_exists?(method_name, metric_name) click to toggle source

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.

# File lib/ting_yun/instrumentation/support/method_instrumentation.rb, line 72
def traced_method_exists?(method_name, metric_name)
  exists = method_defined?(define_trace_method_name(method_name, metric_name))
  ::TingYun::Agent.logger.error("Attempt to trace a method twice with the same metric: Method = #{method_name}, Metric Name = #{metric_name}") if exists
  exists
end
validate_options(method_name, options) click to toggle source
# File lib/ting_yun/instrumentation/support/method_instrumentation.rb, line 94
def validate_options(method_name, options)
  unless options.is_a?(Hash)
    raise TypeError.new("Error adding method tracer to #{method_name}: provided options must be a Hash")
  end
  options = DEFAULT_SETTINGS.merge(options)
  unless options[:scope] || options[:metric]
    raise "Can't add a tracer where push_scope is false and metric is false"
  end
  options
end

Private Instance Methods

_sanitize_name(name) click to toggle source

makes sure that method names do not contain characters that might break the interpreter, for example ! or ? characters that are not allowed in the middle of method names

# File lib/ting_yun/instrumentation/support/method_instrumentation.rb, line 224
def _sanitize_name(name)
  name.to_s.tr_s('^a-zA-Z0-9', '_')
end
define_trace_method_name(method_name, metric_name) click to toggle source

given a method and a metric, this method returns the traced alias of the method name

# File lib/ting_yun/instrumentation/support/method_instrumentation.rb, line 212
def define_trace_method_name(method_name, metric_name)
  "#{_sanitize_name(method_name)}_with_trace_#{_sanitize_name(metric_name)}"
end
define_untrace_method_name(method_name, metric_name) click to toggle source

given a method and a metric, this method returns the untraced alias of the method name

# File lib/ting_yun/instrumentation/support/method_instrumentation.rb, line 217
def define_untrace_method_name(method_name, metric_name)
  "#{_sanitize_name(method_name)}_without_trace_#{_sanitize_name(metric_name)}"
end