-
Notifications
You must be signed in to change notification settings - Fork 386
/
Copy pathspan.rb
235 lines (203 loc) · 7.13 KB
/
span.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# 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,
:metastruct,
:metrics,
:name,
:parent_id,
:resource,
:service,
:links,
:events,
:type,
:start_time,
:status,
:trace_id
attr_writer \
:duration
# Create a new span manually. Call the <tt>start()</tt> method to start the time
# measurement and then <tt>stop()</tt> 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,
metastruct: {},
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 || {}
@metastruct = Tracing::Metadata::Metastruct.new(metastruct)
@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
start_time = @start_time
end_time = @end_time
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
@meta['events'] = @events.map(&:to_hash).to_json unless @events.empty?
h = {
error: @status,
meta: @meta,
metrics: @metrics,
meta_struct: @metastruct.to_h,
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
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: [', "]\n") do
q.breakable
q.seplist @metrics.each do |key, value|
q.text "#{key} => #{value}"
end
end
q.group(2, 'Metastruct: ') do
q.breakable
q.pp metastruct
end
end
end
private
# Used for serialization
# @return [Integer] in nanoseconds since Epoch
def start_time_nano
return unless (start_time = @start_time)
start_time.to_i * 1000000000 + start_time.nsec
end
# Used for serialization
# @return [Integer] in nanoseconds since Epoch
def duration_nano
duration = self.duration
return unless duration
(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