require 'new_relic/agent/instrumentation/metric_frame/pop'
# A struct holding the information required to measure a controller
# action. This is put on the thread local. Handles the issue of
# re-entrancy, or nested action calls.
#
# This class is not part of the public API. Avoid making calls on it directly.
#
module NewRelic
module Agent
module Instrumentation
class MetricFrame
# helper module refactored out of the `pop` method
include Pop
attr_accessor :start # A Time instance for the start time, never nil
attr_accessor :apdex_start # A Time instance used for calculating the apdex score, which
# might end up being @start, or it might be further upstream if
# we can find a request header for the queue entry time
attr_accessor :exception,
:filtered_params, :force_flag,
:jruby_cpu_start, :process_cpu_start, :database_metric_name
# Give the current metric frame a request context. Use this to
# get the URI and referer. The request is interpreted loosely
# as a Rack::Request or an ActionController::AbstractRequest.
attr_accessor :request
# Return the currently active metric frame, or nil. Call with +true+
# to create a new metric frame if one is not already on the thread.
def self.current(create_if_empty=nil)
f = Thread.current[:newrelic_metric_frame]
return f if f || !create_if_empty
Thread.current[:newrelic_metric_frame] = new
end
# This is the name of the model currently assigned to database
# measurements, overriding the default.
def self.database_metric_name
current && current.database_metric_name
end
def self.referer
current && current.referer
end
def self.agent
NewRelic::Agent.instance
end
@@java_classes_loaded = false
if defined? JRuby
begin
require 'java'
java_import 'java.lang.management.ManagementFactory'
java_import 'com.sun.management.OperatingSystemMXBean'
@@java_classes_loaded = true
rescue => e
end
end
attr_reader :depth
def initialize
@start = Time.now
@path_stack = [] # stack of [controller, path] elements
@jruby_cpu_start = jruby_cpu_time
@process_cpu_start = process_cpu
Thread.current[:last_metric_frame] = self
end
def agent
NewRelic::Agent.instance
end
def transaction_sampler
agent.transaction_sampler
end
def sql_sampler
agent.sql_sampler
end
private :agent
private :transaction_sampler
private :sql_sampler
# Indicate that we are entering a measured controller action or task.
# Make sure you unwind every push with a pop call.
def push(m)
transaction_sampler.notice_first_scope_push(start)
sql_sampler.notice_first_scope_push(start)
@path_stack.push NewRelic::MetricParser::MetricParser.for_metric_named(m)
end
# Indicate that you don't want to keep the currently saved transaction
# information
def self.abort_transaction!
current.abort_transaction! if current
end
# For the current web transaction, return the path of the URI minus the host part and query string, or nil.
def uri
@uri ||= self.class.uri_from_request(@request) unless @request.nil?
end
# For the current web transaction, return the full referer, minus the host string, or nil.
def referer
@referer ||= self.class.referer_from_request(@request)
end
# Call this to ensure that the current transaction is not saved
def abort_transaction!
transaction_sampler.ignore_transaction
end
# This needs to be called after entering the call to trace the
# controller action, otherwise the controller action blames
# itself. It gets reset in the normal #pop call.
def start_transaction
agent.stats_engine.start_transaction metric_name
# Only push the transaction context info once, on entry:
if @path_stack.size == 1
transaction_sampler.notice_transaction(metric_name, uri, filtered_params)
sql_sampler.notice_transaction(metric_name, uri, filtered_params)
end
end
def current_metric
@path_stack.last
end
# Return the path, the part of the metric after the category
def path
@path_stack.last.last
end
# Unwind one stack level. It knows if it's back at the outermost caller and
# does the appropriate wrapup of the context.
def pop
metric = @path_stack.pop
log_underflow if metric.nil?
if @path_stack.empty?
handle_empty_path_stack(metric)
else
set_new_scope!(current_stack_metric)
end
end
# If we have an active metric frame, notice the error and increment the error metric.
# Options:
# * :request => Request object to get the uri and referer
# * :uri => The request path, minus any request params or query string.
# * :referer => The URI of the referer
# * :metric => The metric name associated with the transaction
# * :request_params => Request parameters, already filtered if necessary
# * :custom_params => Custom parameters
# Anything left over is treated as custom params
def self.notice_error(e, options={})
request = options.delete(:request)
if request
options[:referer] = referer_from_request(request)
options[:uri] = uri_from_request(request)
end
if current
current.notice_error(e, options)
else
agent.error_collector.notice_error(e, options)
end
end
# Do not call this. Invoke the class method instead.
def notice_error(e, options={}) # :nodoc:
params = custom_parameters
options[:referer] = referer if referer
options[:request_params] = filtered_params if filtered_params
options[:uri] = uri if uri
options[:metric] = metric_name
options.merge!(custom_parameters)
if exception != e
result = agent.error_collector.notice_error(e, options)
self.exception = result if result
end
end
# Add context parameters to the metric frame. This information will be passed in to errors
# and transaction traces. Keys and Values should be strings, numbers or date/times.
def self.add_custom_parameters(p)
current.add_custom_parameters(p) if current
end
def self.custom_parameters
(current && current.custom_parameters) ? current.custom_parameters : {}
end
def self.set_user_attributes(attributes)
current.set_user_attributes(attributes) if current
end
def self.user_attributes
(current) ? current.user_attributes : {}
end
def record_apdex()
return unless recording_web_transaction? && NewRelic::Agent.is_execution_traced?
t = Time.now
self.class.record_apdex(current_metric, t - start, t - apdex_start, !exception.nil?)
end
def metric_name
return nil if @path_stack.empty?
current_metric.name
end
# Return the array of metrics to record for the current metric frame.
def recorded_metrics
metrics = [ metric_name ]
metrics += current_metric.summary_metrics if @path_stack.size == 1
metrics
end
# Yield to a block that is run with a database metric name context. This means
# the Database instrumentation will use this for the metric name if it does not
# otherwise know about a model. This is re-entrant.
#
# * model is the DB model class
# * method is the name of the finder method or other method to identify the operation with.
#
def with_database_metric_name(model, method)
previous = @database_metric_name
model_name = case model
when Class
model.name
when String
model
else
model.to_s
end
@database_metric_name = "ActiveRecord/#{model_name}/#{method}"
yield
ensure
@database_metric_name=previous
end
def custom_parameters
@custom_parameters ||= {}
end
def user_attributes
@user_atrributes ||= {}
end
def queue_time
start - apdex_start
end
def add_custom_parameters(p)
custom_parameters.merge!(p)
end
def set_user_attributes(attributes)
user_attributes.merge!(attributes)
end
def self.recording_web_transaction?
c = Thread.current[:newrelic_metric_frame]
if c
c.recording_web_transaction?
end
end
def recording_web_transaction?
current_metric && current_metric.is_web_transaction?
end
def is_web_transaction?(metric)
0 == metric.index("Controller")
end
# Make a safe attempt to get the referer from a request object, generally successful when
# it's a Rack request.
def self.referer_from_request(request)
if request && request.respond_to?(:referer)
request.referer.to_s.split('?').first
end
end
# Make a safe attempt to get the URI, without the host and query string.
def self.uri_from_request(request)
approximate_uri = case
when request.respond_to?(:fullpath) then request.fullpath
when request.respond_to?(:path) then request.path
when request.respond_to?(:request_uri) then request.request_uri
when request.respond_to?(:uri) then request.uri
when request.respond_to?(:url) then request.url
end
return approximate_uri[%r{^(https?://.*?)?(/[^?]*)}, 2] || '/' if approximate_uri # '
end
def self.record_apdex(current_metric, action_duration, total_duration, is_error)
summary_stat = agent.stats_engine.get_custom_stats("Apdex", NewRelic::ApdexStats)
controller_stat = agent.stats_engine.get_custom_stats(current_metric.apdex_metric_path, NewRelic::ApdexStats)
update_apdex(summary_stat, total_duration, is_error)
update_apdex(controller_stat, action_duration, is_error)
end
# Record an apdex value for the given stat. when `failed`
# the apdex should be recorded as a failure regardless of duration.
def self.update_apdex(stat, duration, failed)
duration = duration.to_f
apdex_t = Agent.config[:apdex_t]
case
when failed
stat.record_apdex_f
when duration <= apdex_t
stat.record_apdex_s
when duration <= 4 * apdex_t
stat.record_apdex_t
else
stat.record_apdex_f
end
end
private
def process_cpu
return nil if defined? JRuby
p = Process.times
p.stime + p.utime
end
def jruby_cpu_time # :nodoc:
return nil unless @@java_classes_loaded
threadMBean = ManagementFactory.getThreadMXBean()
java_utime = threadMBean.getCurrentThreadUserTime() # ns
-1 == java_utime ? 0.0 : java_utime/1e9
end
end
end
end
end