# frozen_string_literal: true require_relative '../core/utils/safe_dup' require_relative 'utils' require_relative 'metadata/ext' require_relative 'metadata' module Datadog module Tracing # Represents a logical unit of work in the system. Each trace consists of one or more spans. # Each span consists of a start time and a duration. For example, a span can describe the time # spent on a distributed call on a separate machine, or the time spent in a small component # within a larger operation. Spans can be nested within each other, and in those instances # will have a parent-child relationship. # @public_api class Span include Metadata attr_accessor \ :end_time, :id, :meta, :metrics, :name, :parent_id, :resource, :service, :links, :events, :type, :start_time, :status, :trace_id attr_writer \ :duration # Create a new span manually. Call the start() method to start the time # measurement and then stop() once the timing operation is over. # # * +service+: the service name for this span # * +resource+: the resource this span refers, or +name+ if it's missing. # +nil+ can be used as a placeholder, when the resource value is not yet known at +#initialize+ time. # * +type+: the type of the span (such as +http+, +db+ and so on) # * +parent_id+: the identifier of the parent span # * +trace_id+: the identifier of the root span for this trace # * +service_entry+: whether it is a service entry span. # * +events+: the list of events that occurred while a span was active. def initialize( name, duration: nil, end_time: nil, id: nil, meta: nil, metrics: nil, parent_id: 0, resource: name, service: nil, start_time: nil, status: 0, type: nil, trace_id: nil, service_entry: nil, links: nil, events: nil ) @name = Core::Utils::SafeDup.frozen_or_dup(name) @service = Core::Utils::SafeDup.frozen_or_dup(service) @resource = Core::Utils::SafeDup.frozen_or_dup(resource) @type = Core::Utils::SafeDup.frozen_or_dup(type) @id = id || Tracing::Utils.next_id @parent_id = parent_id || 0 @trace_id = trace_id || Tracing::Utils.next_id @meta = meta || {} @metrics = metrics || {} @status = status || 0 # start_time and end_time track wall clock. In Ruby, wall clock # has less accuracy than monotonic clock, so if possible we look to only use wall clock # to measure duration when a time is supplied by the user, or if monotonic clock # is unsupported. @start_time = start_time @end_time = end_time # duration_start and duration_end track monotonic clock, and may remain nil in cases where it # is known that we have to use wall clock to measure duration. @duration = duration @service_entry = service_entry @links = links || [] @events = events || [] # Mark with the service entry span metric, if applicable set_metric(Metadata::Ext::TAG_TOP_LEVEL, 1.0) if service_entry end # Return whether the duration is started or not def started? !@start_time.nil? end # Return whether the duration is stopped or not. def stopped? !@end_time.nil? end alias :finished? :stopped? def duration return @duration if @duration return @end_time - @start_time if @start_time && @end_time end def set_error(e) @status = Metadata::Ext::Errors::STATUS set_error_tags(e) end # Spans with the same ID are considered the same span def ==(other) other.instance_of?(Span) && @id == other.id end # Return a string representation of the span. def to_s "Span(name:#{@name},sid:#{@id},tid:#{@trace_id},pid:#{@parent_id})" end # Return the hash representation of the current span. # TODO: Change this to reflect attributes when serialization # isn't handled by this method. def to_hash h = { error: @status, meta: @meta, metrics: @metrics, name: @name, parent_id: @parent_id, resource: @resource, service: @service, span_id: @id, trace_id: @trace_id, type: @type, span_links: @links.map(&:to_hash) } if stopped? h[:start] = start_time_nano h[:duration] = duration_nano end h[:meta]['events'] = @events.map(&:to_hash).to_json unless @events.empty? h end # Return a human readable version of the span def pretty_print(q) start_time = (self.start_time.to_f * 1e9).to_i end_time = (self.end_time.to_f * 1e9).to_i q.group 0 do q.breakable q.text "Name: #{@name}\n" q.text "Span ID: #{@id}\n" q.text "Parent ID: #{@parent_id}\n" q.text "Trace ID: #{@trace_id}\n" q.text "Type: #{@type}\n" q.text "Service: #{@service}\n" q.text "Resource: #{@resource}\n" q.text "Error: #{@status}\n" q.text "Start: #{start_time}\n" q.text "End: #{end_time}\n" q.text "Duration: #{duration.to_f}\n" q.group(2, 'Tags: [', "]\n") do q.breakable q.seplist @meta.each do |key, value| q.text "#{key} => #{value}" end end q.group(2, 'Metrics: [', ']') do q.breakable q.seplist @metrics.each do |key, value| q.text "#{key} => #{value}" end end end end private # Used for serialization # @return [Integer] in nanoseconds since Epoch def start_time_nano @start_time.to_i * 1000000000 + @start_time.nsec end # Used for serialization # @return [Integer] in nanoseconds since Epoch def duration_nano (duration * 1e9).to_i end # https://docs.datadoghq.com/tracing/visualization/#service-entry-span # A span is a service entry span when it is the entrypoint method for a request to a service. # You can visualize this within Datadog APM when the color of the immediate parent on a flame graph is a different # color. Services are also listed on the right when viewing a flame graph. # # @return [Boolean] `true` if the span is a serivce entry span def service_entry? @service_entry == true end end end end