class NewRelic::Agent::Transaction::AbstractSegment

Constants

CALLBACK
INSPECT_IGNORE
SEGMENT

Attributes

children_time[RW]
duration[R]

This class is the base class for all segments. It is responsible for timing, naming, and defining lifecycle callbacks. One of the more complex responsibilities of this class is computing exclusive duration. One of the reasons for this complexity is that exclusive time will be computed using time ranges or by recording an aggregate value for a segments children time. The reason for this is that computing exclusive duration using time ranges is expensive and it’s only necessary if a segment’s children run concurrently, or a segment ends after its parent. We will use the optimized exclusive duration calculation in all other cases.

end_time[R]

This class is the base class for all segments. It is responsible for timing, naming, and defining lifecycle callbacks. One of the more complex responsibilities of this class is computing exclusive duration. One of the reasons for this complexity is that exclusive time will be computed using time ranges or by recording an aggregate value for a segments children time. The reason for this is that computing exclusive duration using time ranges is expensive and it’s only necessary if a segment’s children run concurrently, or a segment ends after its parent. We will use the optimized exclusive duration calculation in all other cases.

exclusive_duration[R]

This class is the base class for all segments. It is responsible for timing, naming, and defining lifecycle callbacks. One of the more complex responsibilities of this class is computing exclusive duration. One of the reasons for this complexity is that exclusive time will be computed using time ranges or by recording an aggregate value for a segments children time. The reason for this is that computing exclusive duration using time ranges is expensive and it’s only necessary if a segment’s children run concurrently, or a segment ends after its parent. We will use the optimized exclusive duration calculation in all other cases.

guid[R]

This class is the base class for all segments. It is responsible for timing, naming, and defining lifecycle callbacks. One of the more complex responsibilities of this class is computing exclusive duration. One of the reasons for this complexity is that exclusive time will be computed using time ranges or by recording an aggregate value for a segments children time. The reason for this is that computing exclusive duration using time ranges is expensive and it’s only necessary if a segment’s children run concurrently, or a segment ends after its parent. We will use the optimized exclusive duration calculation in all other cases.

llm_event[RW]
name[RW]
noticed_error[R]
parent[RW]
range_recorded[W]
record_metrics[W]
record_on_finish[W]
record_scoped_metric[W]
start_time[R]

This class is the base class for all segments. It is responsible for timing, naming, and defining lifecycle callbacks. One of the more complex responsibilities of this class is computing exclusive duration. One of the reasons for this complexity is that exclusive time will be computed using time ranges or by recording an aggregate value for a segments children time. The reason for this is that computing exclusive duration using time ranges is expensive and it’s only necessary if a segment’s children run concurrently, or a segment ends after its parent. We will use the optimized exclusive duration calculation in all other cases.

starting_segment_key[R]

This class is the base class for all segments. It is responsible for timing, naming, and defining lifecycle callbacks. One of the more complex responsibilities of this class is computing exclusive duration. One of the reasons for this complexity is that exclusive time will be computed using time ranges or by recording an aggregate value for a segments children time. The reason for this is that computing exclusive duration using time ranges is expensive and it’s only necessary if a segment’s children run concurrently, or a segment ends after its parent. We will use the optimized exclusive duration calculation in all other cases.

transaction[RW]
transaction_name[RW]

Public Class Methods

new(name = nil, start_time = nil) click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 30
def initialize(name = nil, start_time = nil)
  @name = name
  @starting_segment_key = NewRelic::Agent::Tracer.current_segment_key
  @transaction_name = nil
  @transaction = nil
  @guid = NewRelic::Agent::GuidGenerator.generate_guid
  @parent = nil
  @params = nil
  @start_time = start_time if start_time
  @end_time = nil
  @duration = 0.0
  @exclusive_duration = 0.0
  @children_timings = []
  @children_time = 0.0
  @active_children = 0
  @range_recorded = false
  @concurrent_children = false
  @record_metrics = true
  @record_scoped_metric = true
  @record_on_finish = false
  @noticed_error = nil
  @code_filepath = nil
  @code_function = nil
  @code_lineno = nil
  @code_namespace = nil
  invoke_callback
end

Private Class Methods

set_segment_callback(callback_proc) click to toggle source

Setting and invoking a segment callback

Each individual segment class such as ‘ExternalRequestSegment` allows for exactly one instance of a `Proc` (meaning a proc or lambda) to be set as a callback. A callback can be set on a segment class by calling `.set_segment_callback` with a proc or lambda as the only argument. If set, the callback will be invoked with `#call` at segment class initialization time.

Example usage:

callback = -> { puts 'Hello, World! }
ExternalRequestSegment.set_segment_callback(callback)
ExternalRequestSegment.new(library, uri, procedure)

A callback set on a segment class will only be called when that specific segment class is initialized. Other segment classes will not be impacted.

Great caution should be taken in the defining of the callback block to not have the block perform anything too time consuming or resource intensive in order to keep the New Relic Ruby agent operating normally.

Given that callbacks are user defined, they must be set entirely at the user’s own risk. It is recommended that each callback use conditional logic that only performs work for certain qualified segments. It is recommended that each callback be thoroughly tested in non-production environments before being introduced to production environments.

# File lib/new_relic/agent/transaction/abstract_segment.rb, line 375
def self.set_segment_callback(callback_proc)
  unless callback_proc.is_a?(Proc)
    NewRelic::Agent.logger.error("#{self}.#{__method__}: expected an argument of type Proc, " \
                                 "got #{callback_proc.class}")
    return
  end

  NewRelic::Agent.record_api_supportability_metric(:set_segment_callback)
  instance_variable_set(CALLBACK, callback_proc)
end

Public Instance Methods

all_code_information_present?() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 128
def all_code_information_present?
  @code_filepath && @code_function && @code_lineno && @code_namespace
end
children_time_ranges?() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 111
def children_time_ranges?
  !@children_timings.empty?
end
code_attributes() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 132
def code_attributes
  return ::NewRelic::EMPTY_HASH unless all_code_information_present?

  @code_attributes ||= {'code.filepath' => @code_filepath,
                        'code.function' => @code_function,
                        'code.lineno' => @code_lineno,
                        'code.namespace' => @code_namespace}
end
code_information=(info = {}) click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 119
def code_information=(info = {})
  return unless info[:filepath]

  @code_filepath = info[:filepath]
  @code_function = info[:function]
  @code_lineno = info[:lineno]
  @code_namespace = info[:namespace]
end
concurrent_children?() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 115
def concurrent_children?
  @concurrent_children
end
finalize() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 93
def finalize
  force_finish unless finished?
  record_exclusive_duration
  record_metrics if record_metrics?
end
finish() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 65
def finish
  @end_time = Process.clock_gettime(Process::CLOCK_REALTIME)
  @duration = end_time - start_time

  return unless transaction

  run_complete_callbacks
  finalize if record_on_finish?
rescue => e
  NewRelic::Agent.logger.error("Exception finishing segment: #{name}", e)
end
finished?() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 77
def finished?
  !!@end_time
end
inspect() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 143
def inspect
  ivars = (instance_variables - INSPECT_IGNORE).inject([]) do |memo, var_name|
    memo << "#{var_name}=#{instance_variable_get(var_name).inspect}"
  end
  sprintf('#<%s:0x%x %s>', self.class.name, object_id, ivars.join(', '))
end
notice_error(exception, options = {}) click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 164
def notice_error(exception, options = {})
  if Agent.config[:high_security]
    NewRelic::Agent.logger.debug( \
      "Segment: #{name} ignores notice_error for " \
      "error: #{exception.inspect} because :high_security is enabled"
    )
  else
    NewRelic::Agent.instance.error_collector.notice_segment_error(self, exception, options)
  end
end
noticed_error_attributes() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 175
def noticed_error_attributes
  return unless @noticed_error

  @noticed_error.attributes_from_notice_error
end
params() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 99
def params
  @params ||= {}
end
params?() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 103
def params?
  !!@params
end
record_metrics?() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 81
def record_metrics?
  @record_metrics
end
record_on_finish?() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 89
def record_on_finish?
  @record_on_finish
end
record_scoped_metric?() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 85
def record_scoped_metric?
  @record_scoped_metric
end
set_noticed_error(noticed_error) click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 154
def set_noticed_error(noticed_error)
  if @noticed_error
    NewRelic::Agent.logger.debug( \
      "Segment: #{name} overwriting previously noticed " \
      "error: #{@noticed_error.inspect} with: #{noticed_error.inspect}"
    )
  end
  @noticed_error = noticed_error
end
start() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 58
def start
  @start_time ||= Process.clock_gettime(Process::CLOCK_REALTIME)
  return unless transaction

  parent&.child_start(self)
end
time_range() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 107
def time_range
  @start_time.to_f..@end_time.to_f
end
transaction_assigned() click to toggle source

callback for subclasses to override

# File lib/new_relic/agent/transaction/abstract_segment.rb, line 151
def transaction_assigned
end

Protected Instance Methods

child_complete(segment) click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 196
def child_complete(segment)
  @active_children -= 1
  record_child_time(segment)

  if finished?
    transaction.async = true
    parent&.descendant_complete(self, segment)
  end
end
child_start(segment) click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 189
def child_start(segment)
  @active_children += 1
  @concurrent_children ||= @active_children > 1

  transaction.async = true if @concurrent_children
end
descendant_complete(child, descendant) click to toggle source

When a child segment completes after its parent, we need to propagate the information about the descendant further up the tree so that ancestors can properly account for exclusive time. Once we’ve reached an ancestor whose end time is greater than or equal to the descendant’s we can stop the propagation. We pass along the direct child so we can make any corrections needed for exclusive time calculation.

# File lib/new_relic/agent/transaction/abstract_segment.rb, line 212
def descendant_complete(child, descendant)
  add_child_timing(descendant)

  # If this child's time was previously added to this segment's
  # aggregate children time, we need to re-record it using a time range
  # for proper exclusive time calculation
  unless child.range_recorded?
    self.children_time -= child.duration
    record_child_time_as_range(child)
  end

  if parent && finished? && descendant.end_time >= end_time
    parent.descendant_complete(self, descendant)
  end
end
range_recorded?() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 185
def range_recorded?
  @range_recorded
end

Private Instance Methods

add_child_timing(segment) click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 230
def add_child_timing(segment)
  @children_timings << [segment.start_time, segment.end_time]
end
force_finish() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 234
def force_finish
  finish
  NewRelic::Agent.logger.send(transaction.async? ? :debug : :warn, "Segment: #{name} was unfinished at " \
    "the end of transaction. Timing information for this segment's " \
    "parent #{parent&.name} in #{transaction.best_name} may be inaccurate.")
end
invoke_callback() click to toggle source

for segment callback usage info, see self.set_segment_callback

# File lib/new_relic/agent/transaction/abstract_segment.rb, line 336
def invoke_callback
  return unless self.class.instance_variable_defined?(CALLBACK)

  NewRelic::Agent.logger.debug("Invoking callback for #{self.class.name}...")
  self.class.instance_variable_get(CALLBACK).call
rescue Exception => e
  NewRelic::Agent.logger.error("Error encountered while invoking callback for #{self.class.name}: " +
                               "#{e.class} - #{e.message}")
end
metric_cache() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 278
def metric_cache
  transaction.metrics
end
overlapping_duration() click to toggle source

Child segments operating concurrently with this segment may have start and end times that overlap with this segment’s own times. The amount of overlap needs to be removed from the ‘children_time` total when calculating an `@exclusive_duration` value to be added to the transaction’s total time.

If there aren’t any child segments, return 0.0. Otherwise, take the ‘@children_timings` array of arrays (each array holds a child segment’s start time and end time), sort it by the first elements (start times), and use the start and finish times to create Range objects. Combine all of the child segment ranges that overlap with one another into new bigger ranges. Then take those bigger ranges and calculate how much overlap there is between them and this segment’s own time range. Keep a running sum of all of the overlap amounts and then return it.

# File lib/new_relic/agent/transaction/abstract_segment.rb, line 307
def overlapping_duration
  sum = 0.0
  return sum unless children_time_ranges?

  @children_timings.sort_by!(&:first)
  range = Range.new(*@children_timings.first)
  (1..(@children_timings.size - 1)).each do |i|
    possible = Range.new(*@children_timings[i])

    if ranges_intersect?(range, possible)
      range = range.begin..possible.end
    else
      sum += range_overlap(range)
      range = possible
    end
  end

  sum += range_overlap(range)
end
range_overlap(range) click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 286
def range_overlap(range)
  return 0.0 unless ranges_intersect?(range, time_range)

  [range.end, time_range.end].min - [range.begin, time_range.begin].max
end
ranges_intersect?(r1, r2) click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 282
def ranges_intersect?(r1, r2)
  r1.begin > r2.begin ? r2.cover?(r1.begin) : r1.cover?(r2.begin)
end
record_child_time(child) click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 255
def record_child_time(child)
  if concurrent_children? || finished? && end_time < child.end_time
    record_child_time_as_range(child)
  else
    record_child_time_as_number(child)
  end
end
record_child_time_as_number(child) click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 268
def record_child_time_as_number(child)
  self.children_time += child.duration
end
record_child_time_as_range(child) click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 263
def record_child_time_as_range(child)
  add_child_timing(child)
  child.range_recorded = true
end
record_exclusive_duration() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 272
def record_exclusive_duration
  @exclusive_duration = duration - children_time - overlapping_duration
  transaction.total_time += @exclusive_duration
  params[:exclusive_duration_millis] = @exclusive_duration * 1000 if transaction.async?
end
record_metrics() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 247
def record_metrics
  raise NotImplementedError, 'Subclasses must implement record_metrics'
end
run_complete_callbacks() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 241
def run_complete_callbacks
  segment_complete
  parent&.child_complete(self)
  transaction.segment_complete(self)
end
segment_complete() click to toggle source

callback for subclasses to override

# File lib/new_relic/agent/transaction/abstract_segment.rb, line 252
def segment_complete
end
transaction_state() click to toggle source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 327
def transaction_state
  @transaction_state ||= if @transaction
    transaction.state
  else
    Tracer.state
  end
end