class NewRelic::Agent::Transaction::AbstractSegment
Constants
- CALLBACK
- INSPECT_IGNORE
- SEGMENT
Attributes
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.
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.
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.
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.
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.
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.
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.
Public Class Methods
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 @thread_id = Thread.current.object_id @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
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 376 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
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.
Public Instance Methods
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 129 def all_code_information_present? @code_filepath && @code_function && @code_lineno && @code_namespace end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 112 def children_time_ranges? !@children_timings.empty? end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 133 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
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 120 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
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 116 def concurrent_children? @concurrent_children end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 94 def finalize force_finish unless finished? record_exclusive_duration record_metrics if record_metrics? end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 66 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
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 78 def finished? !!@end_time end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 144 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
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 165 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
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 176 def noticed_error_attributes return unless @noticed_error @noticed_error.attributes_from_notice_error end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 100 def params @params ||= {} end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 104 def params? !!@params end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 82 def record_metrics? @record_metrics end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 90 def record_on_finish? @record_on_finish end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 86 def record_scoped_metric? @record_scoped_metric end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 155 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
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 59 def start @start_time ||= Process.clock_gettime(Process::CLOCK_REALTIME) return unless transaction parent&.child_start(self) end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 108 def time_range @start_time.to_f..@end_time.to_f end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 152 def transaction_assigned end
callback for subclasses to override
Protected Instance Methods
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 197 def child_complete(segment) @active_children -= 1 record_child_time(segment) if finished? transaction.async = true parent&.descendant_complete(self, segment) end end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 190 def child_start(segment) @active_children += 1 @concurrent_children ||= @active_children > 1 transaction.async = true if @concurrent_children end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 213 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
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.
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 186 def range_recorded? @range_recorded end
Private Instance Methods
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 231 def add_child_timing(segment) @children_timings << [segment.start_time, segment.end_time] end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 235 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
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 337 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
for segment callback usage info, see self.set_segment_callback
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 279 def metric_cache transaction.metrics end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 308 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
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.
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 287 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
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 283 def ranges_intersect?(r1, r2) r1.begin > r2.begin ? r2.cover?(r1.begin) : r1.cover?(r2.begin) end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 256 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
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 269 def record_child_time_as_number(child) self.children_time += child.duration end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 264 def record_child_time_as_range(child) add_child_timing(child) child.range_recorded = true end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 273 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
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 248 def record_metrics raise NotImplementedError, 'Subclasses must implement record_metrics' end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 242 def run_complete_callbacks segment_complete parent&.child_complete(self) transaction.segment_complete(self) end
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 253 def segment_complete end
callback for subclasses to override
Source
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 328 def transaction_state @transaction_state ||= if @transaction transaction.state else Tracer.state end end