Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/opencensus/trace/span.py: 65%
194 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-06 06:04 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-06 06:04 +0000
1# Copyright 2017, OpenCensus Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15try:
16 from collections.abc import MutableMapping
17 from collections.abc import Sequence
18except ImportError:
19 from collections import MutableMapping
20 from collections import Sequence
22import threading
23from collections import OrderedDict, deque
24from datetime import datetime
25from itertools import chain
27from opencensus.common import utils
28from opencensus.trace import attributes as attributes_module
29from opencensus.trace import base_span
30from opencensus.trace import link as link_module
31from opencensus.trace import stack_trace as stack_trace_module
32from opencensus.trace import status as status_module
33from opencensus.trace import time_event
34from opencensus.trace.span_context import generate_span_id
35from opencensus.trace.tracers import base
37# https://github.com/census-instrumentation/opencensus-specs/blob/master/trace/TraceConfig.md # noqa
38MAX_NUM_ATTRIBUTES = 32
39MAX_NUM_ANNOTATIONS = 32
40MAX_NUM_MESSAGE_EVENTS = 128
41MAX_NUM_LINKS = 32
44class BoundedList(Sequence):
45 """An append only list with a fixed max size."""
46 def __init__(self, maxlen):
47 self.dropped = 0
48 self._dq = deque(maxlen=maxlen)
49 self._lock = threading.Lock()
51 def __repr__(self):
52 return ("{}({}, maxlen={})"
53 .format(
54 type(self).__name__,
55 list(self._dq),
56 self._dq.maxlen
57 ))
59 def __getitem__(self, index):
60 return self._dq[index]
62 def __len__(self):
63 return len(self._dq)
65 def __iter__(self):
66 return iter(self._dq)
68 def append(self, item):
69 with self._lock:
70 if len(self._dq) == self._dq.maxlen:
71 self.dropped += 1
72 self._dq.append(item)
74 def extend(self, seq):
75 with self._lock:
76 to_drop = len(seq) + len(self._dq) - self._dq.maxlen
77 if to_drop > 0:
78 self.dropped += to_drop
79 self._dq.extend(seq)
81 @classmethod
82 def from_seq(cls, maxlen, seq):
83 seq = tuple(seq)
84 if len(seq) > maxlen:
85 raise ValueError
86 bounded_list = cls(maxlen)
87 bounded_list._dq = deque(seq, maxlen=maxlen)
88 return bounded_list
91class BoundedDict(MutableMapping):
92 """A dict with a fixed max capacity."""
93 def __init__(self, maxlen):
94 self.maxlen = maxlen
95 self.dropped = 0
96 self._dict = OrderedDict()
97 self._lock = threading.Lock()
99 def __repr__(self):
100 return ("{}({}, maxlen={})"
101 .format(
102 type(self).__name__,
103 dict(self._dict),
104 self.maxlen
105 ))
107 def __getitem__(self, key):
108 return self._dict[key]
110 def __setitem__(self, key, value):
111 with self._lock:
112 if key in self._dict:
113 del self._dict[key]
114 elif len(self._dict) == self.maxlen:
115 del self._dict[next(iter(self._dict.keys()))]
116 self.dropped += 1
117 self._dict[key] = value
119 def __delitem__(self, key):
120 del self._dict[key]
122 def __iter__(self):
123 return iter(self._dict)
125 def __len__(self):
126 return len(self._dict)
128 @classmethod
129 def from_map(cls, maxlen, mapping):
130 mapping = OrderedDict(mapping)
131 if len(mapping) > maxlen:
132 raise ValueError
133 bounded_dict = cls(maxlen)
134 bounded_dict._dict = mapping
135 return bounded_dict
138class SpanKind(object):
139 UNSPECIFIED = 0
140 SERVER = 1
141 CLIENT = 2
144class Span(base_span.BaseSpan):
145 """A span is an individual timed event which forms a node of the trace
146 tree. Each span has its name, span id and parent id. The parent id
147 indicates the causal relationships between the individual spans in a
148 single distributed trace. Span that does not have a parent id is called
149 root span. All spans associated with a specific trace also share a common
150 trace id. Spans do not need to be continuous, there can be gaps between
151 two spans.
153 :type name: str
154 :param name: The name of the span.
156 :type parent_span: :class:`~opencensus.trace.span.Span`
157 :param parent_span: (Optional) Parent span.
159 :type attributes: dict
160 :param attributes: Collection of attributes associated with the span.
161 Attribute keys must be less than 128 bytes.
162 Attribute values must be less than 16 kilobytes.
164 :type start_time: str
165 :param start_time: (Optional) Start of the time interval (inclusive)
166 during which the trace data was collected from the
167 application.
169 :type end_time: str
170 :param end_time: (Optional) End of the time interval (inclusive) during
171 which the trace data was collected from the application.
173 :type span_id: int
174 :param span_id: Identifier for the span, unique within a trace.
176 :type stack_trace: :class: `~opencensus.trace.stack_trace.StackTrace`
177 :param stack_trace: (Optional) A call stack appearing in a trace
179 :type annotations: list(:class:`opencensus.trace.time_event.Annotation`)
180 :param annotations: (Optional) The list of span annotations.
182 :type message_events:
183 list(:class:`opencensus.trace.time_event.MessageEvent`)
184 :param message_events: (Optional) The list of span message events.
186 :type links: list
187 :param links: (Optional) Links associated with the span. You can have up
188 to 128 links per Span.
190 :type status: :class: `~opencensus.trace.status.Status`
191 :param status: (Optional) An optional final status for this span.
193 :type same_process_as_parent_span: bool
194 :param same_process_as_parent_span: (Optional) A highly recommended but not
195 required flag that identifies when a
196 trace crosses a process boundary.
197 True when the parent_span belongs to
198 the same process as the current span.
200 :type context_tracer: :class:`~opencensus.trace.tracers.context_tracer.
201 ContextTracer`
202 :param context_tracer: The tracer that holds a stack of spans. If this is
203 not None, then when exiting a span, use the end_span
204 method in the tracer class to finish a span. If no
205 tracer is passed in, then just finish the span using
206 the finish method in the Span class.
208 :type span_kind: int
209 :param span_kind: (Optional) Highly recommended flag that denotes the type
210 of span (valid values defined by :class:
211 `opencensus.trace.span.SpanKind`)
212 """
214 def __init__(
215 self,
216 name,
217 parent_span=None,
218 attributes=None,
219 start_time=None,
220 end_time=None,
221 span_id=None,
222 stack_trace=None,
223 annotations=None,
224 message_events=None,
225 links=None,
226 status=None,
227 same_process_as_parent_span=None,
228 context_tracer=None,
229 span_kind=SpanKind.UNSPECIFIED):
230 self.name = name
231 self.parent_span = parent_span
232 self.start_time = start_time
233 self.end_time = end_time
235 if span_id is None:
236 span_id = generate_span_id()
238 if attributes is None:
239 self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES)
240 else:
241 self.attributes = BoundedDict.from_map(
242 MAX_NUM_ATTRIBUTES, attributes)
244 # Do not manipulate spans directly using the methods in Span Class,
245 # make sure to use the Tracer.
246 if parent_span is None:
247 parent_span = base.NullContextManager()
249 if annotations is None:
250 self.annotations = BoundedList(MAX_NUM_ANNOTATIONS)
251 else:
252 self.annotations = BoundedList.from_seq(MAX_NUM_LINKS, annotations)
254 if message_events is None:
255 self.message_events = BoundedList(MAX_NUM_MESSAGE_EVENTS)
256 else:
257 self.message_events = BoundedList.from_seq(
258 MAX_NUM_LINKS, message_events)
260 if links is None:
261 self.links = BoundedList(MAX_NUM_LINKS)
262 else:
263 self.links = BoundedList.from_seq(MAX_NUM_LINKS, links)
265 if status is None:
266 self.status = status_module.Status.as_ok()
267 else:
268 self.status = status
270 self.span_id = span_id
271 self.stack_trace = stack_trace
272 self.same_process_as_parent_span = same_process_as_parent_span
273 self._child_spans = []
274 self.context_tracer = context_tracer
275 self.span_kind = span_kind
276 for callback in Span._on_create_callbacks:
277 callback(self)
279 _on_create_callbacks = []
281 @staticmethod
282 def on_create(callback):
283 Span._on_create_callbacks.append(callback)
285 @property
286 def children(self):
287 """The child spans of the current span."""
288 return self._child_spans
290 def span(self, name='child_span'):
291 """Create a child span for the current span and append it to the child
292 spans list.
294 :type name: str
295 :param name: (Optional) The name of the child span.
297 :rtype: :class: `~opencensus.trace.span.Span`
298 :returns: A child Span to be added to the current span.
299 """
300 child_span = Span(name, parent_span=self)
301 self._child_spans.append(child_span)
302 return child_span
304 def add_attribute(self, attribute_key, attribute_value):
305 """Add attribute to span.
307 :type attribute_key: str
308 :param attribute_key: Attribute key.
310 :type attribute_value:str
311 :param attribute_value: Attribute value.
312 """
313 self.attributes[attribute_key] = attribute_value
315 def add_annotation(self, description, **attrs):
316 """Add an annotation to span.
318 :type description: str
319 :param description: A user-supplied message describing the event.
320 The maximum length for the description is 256 bytes.
322 :type attrs: kwargs
323 :param attrs: keyworded arguments e.g. failed=True, name='Caching'
324 """
325 self.annotations.append(time_event.Annotation(
326 datetime.utcnow(),
327 description,
328 attributes_module.Attributes(attrs)
329 ))
331 def add_message_event(self, message_event):
332 """Add a message event to this span.
334 :type message_event: :class:`opencensus.trace.time_event.MessageEvent`
335 :param message_event: The message event to attach to this span.
336 """
337 self.message_events.append(message_event)
339 def add_link(self, link):
340 """Add a Link.
342 :type link: :class: `~opencensus.trace.link.Link`
343 :param link: A Link object.
344 """
345 if isinstance(link, link_module.Link):
346 self.links.append(link)
347 else:
348 raise TypeError("Type Error: received {}, but requires Link.".
349 format(type(link).__name__))
351 def set_status(self, status):
352 """Sets span status.
354 :type code: :class: `~opencensus.trace.status.Status`
355 :param code: A Status object.
356 """
357 if isinstance(status, status_module.Status):
358 self.status = status
359 else:
360 raise TypeError("Type Error: received {}, but requires Status.".
361 format(type(status).__name__))
363 def start(self):
364 """Set the start time for a span."""
365 self.start_time = utils.to_iso_str()
367 def finish(self):
368 """Set the end time for a span."""
369 self.end_time = utils.to_iso_str()
371 def __iter__(self):
372 """Iterate through the span tree."""
373 for span in chain.from_iterable(map(iter, self.children)):
374 yield span
375 yield self
377 def __enter__(self):
378 """Start a span."""
379 self.start()
380 return self
382 def __exit__(self, exception_type, exception_value, traceback):
383 """Finish a span."""
384 if traceback is not None:
385 self.stack_trace =\
386 stack_trace_module.StackTrace.from_traceback(traceback)
387 if exception_value is not None:
388 self.status = status_module.Status.from_exception(exception_value)
389 if self.context_tracer is not None:
390 self.context_tracer.end_span()
391 return
393 self.finish()
396def format_span_json(span):
397 """Helper to format a Span in JSON format.
399 :type span: :class:`~opencensus.trace.span.Span`
400 :param span: A Span to be transferred to JSON format.
402 :rtype: dict
403 :returns: Formatted Span.
404 """
405 span_json = {
406 'displayName': utils.get_truncatable_str(span.name),
407 'spanId': span.span_id,
408 'startTime': span.start_time,
409 'endTime': span.end_time,
410 'childSpanCount': len(span._child_spans)
411 }
413 parent_span_id = None
415 if span.parent_span is not None:
416 parent_span_id = span.parent_span.span_id
418 if parent_span_id is not None:
419 span_json['parentSpanId'] = parent_span_id
421 if span.attributes:
422 span_json['attributes'] = attributes_module.Attributes(
423 span.attributes).format_attributes_json()
425 if span.stack_trace is not None:
426 span_json['stackTrace'] = span.stack_trace.format_stack_trace_json()
428 formatted_time_events = []
429 if span.annotations:
430 formatted_time_events.extend(
431 {'time': aa.timestamp,
432 'annotation': aa.format_annotation_json()}
433 for aa in span.annotations)
434 if span.message_events:
435 formatted_time_events.extend(
436 {'time': aa.timestamp,
437 'message_event': aa.format_message_event_json()}
438 for aa in span.message_events)
439 if formatted_time_events:
440 span_json['timeEvents'] = {
441 'timeEvent': formatted_time_events
442 }
444 if span.links:
445 span_json['links'] = {
446 'link': [
447 link.format_link_json() for link in span.links]
448 }
450 if span.status is not None:
451 span_json['status'] = span.status.format_status_json()
453 if span.same_process_as_parent_span is not None:
454 span_json['sameProcessAsParentSpan'] = \
455 span.same_process_as_parent_span
457 return span_json