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.
Public Class Methods
# 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
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
# 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
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 111 def children_time_ranges? !@children_timings.empty? end
# 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
# 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
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 115 def concurrent_children? @concurrent_children end
# 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
# 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
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 77 def finished? !!@end_time end
# 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
# 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
# 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
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 99 def params @params ||= {} end
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 103 def params? !!@params end
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 81 def record_metrics? @record_metrics end
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 89 def record_on_finish? @record_on_finish end
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 85 def record_scoped_metric? @record_scoped_metric end
# 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
# 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
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 107 def time_range @start_time.to_f..@end_time.to_f end
callback for subclasses to override
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 151 def transaction_assigned end
Protected Instance Methods
# 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
# 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
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
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 185 def range_recorded? @range_recorded end
Private Instance Methods
# 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
# 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
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
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 278 def metric_cache transaction.metrics 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.
# 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
# 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
# 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
# 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
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 268 def record_child_time_as_number(child) self.children_time += child.duration end
# 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
# 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
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 247 def record_metrics raise NotImplementedError, 'Subclasses must implement record_metrics' end
# 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
callback for subclasses to override
# File lib/new_relic/agent/transaction/abstract_segment.rb, line 252 def segment_complete end
# 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