def test_no_limit(self): blist = BoundedList(maxlen=None) for num in range(100): blist.append(num) for num in range(100): self.assertEqual(blist[num], num)
def test_append_no_drop(self): """Append max capacity elements to the list without dropping elements.""" # create empty list list_len = len(self.base) blist = BoundedList(list_len) self.assertEqual(len(blist), 0) # fill list for item in self.base: blist.append(item) self.assertEqual(len(blist), list_len) self.assertEqual(blist.dropped, 0) for idx in range(list_len): self.assertEqual(blist[idx], self.base[idx]) # test __iter__ in BoundedList for idx, val in enumerate(blist): self.assertEqual(val, self.base[idx])
def test_raises(self): """Test corner cases - negative list size - access out of range indexes """ with self.assertRaises(ValueError): BoundedList(-1) blist = BoundedList(4) blist.append(37) blist.append(13) with self.assertRaises(IndexError): _ = blist[2] with self.assertRaises(IndexError): _ = blist[4] with self.assertRaises(IndexError): _ = blist[-3]
class Span(trace_api.Span): """See `opentelemetry.trace.Span`. Users should create `Span` objects via the `Tracer` instead of this constructor. Args: name: The name of the operation this span represents context: The immutable span context parent: This span's parent, may be a `SpanContext` if the parent is remote, null if this is a root span sampler: The sampler used to create this span trace_config: TODO resource: Entity producing telemetry attributes: The span's attributes to be exported events: Timestamped events to be exported links: Links to other spans to be exported span_processor: `SpanProcessor` to invoke when starting and ending this `Span`. """ # Initialize these lazily assuming most spans won't have them. _empty_attributes = BoundedDict(MAX_NUM_ATTRIBUTES) _empty_events = BoundedList(MAX_NUM_EVENTS) _empty_links = BoundedList(MAX_NUM_LINKS) def __init__( self, name: str, context: trace_api.SpanContext, parent: trace_api.ParentSpan = None, sampler: Optional[sampling.Sampler] = None, trace_config: None = None, # TODO resource: None = None, attributes: types.Attributes = None, # TODO events: Sequence[Event] = None, # TODO links: Sequence[trace_api.Link] = (), kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL, span_processor: SpanProcessor = SpanProcessor(), instrumentation_info: InstrumentationInfo = None, set_status_on_exception: bool = True, ) -> None: self.name = name self.context = context self.parent = parent self.sampler = sampler self.trace_config = trace_config self.resource = resource self.kind = kind self._set_status_on_exception = set_status_on_exception self.span_processor = span_processor self.status = None self._lock = threading.Lock() if attributes is None: self.attributes = Span._empty_attributes else: self.attributes = BoundedDict.from_map( MAX_NUM_ATTRIBUTES, attributes ) if events is None: self.events = Span._empty_events else: self.events = BoundedList.from_seq(MAX_NUM_EVENTS, events) if links is None: self.links = Span._empty_links else: self.links = BoundedList.from_seq(MAX_NUM_LINKS, links) self._end_time = None # type: Optional[int] self._start_time = None # type: Optional[int] self.instrumentation_info = instrumentation_info @property def start_time(self): return self._start_time @property def end_time(self): return self._end_time def __repr__(self): return '{}(name="{}", context={})'.format( type(self).__name__, self.name, self.context ) @staticmethod def _format_context(context): x_ctx = OrderedDict() x_ctx["trace_id"] = trace_api.format_trace_id(context.trace_id) x_ctx["span_id"] = trace_api.format_span_id(context.span_id) x_ctx["trace_state"] = repr(context.trace_state) return x_ctx @staticmethod def _format_attributes(attributes): if isinstance(attributes, BoundedDict): return attributes._dict # pylint: disable=protected-access return attributes @staticmethod def _format_events(events): f_events = [] for event in events: f_event = OrderedDict() f_event["name"] = event.name f_event["timestamp"] = util.ns_to_iso_str(event.timestamp) f_event["attributes"] = Span._format_attributes(event.attributes) f_events.append(f_event) return f_events @staticmethod def _format_links(links): f_links = [] for link in links: f_link = OrderedDict() f_link["context"] = Span._format_context(link.context) f_link["attributes"] = Span._format_attributes(link.attributes) f_links.append(f_link) return f_links def to_json(self): parent_id = None if self.parent is not None: if isinstance(self.parent, Span): ctx = self.parent.context parent_id = trace_api.format_span_id(ctx.span_id) elif isinstance(self.parent, SpanContext): parent_id = trace_api.format_span_id(self.parent.span_id) start_time = None if self.start_time: start_time = util.ns_to_iso_str(self.start_time) end_time = None if self.end_time: end_time = util.ns_to_iso_str(self.end_time) if self.status is not None: status = OrderedDict() status["canonical_code"] = str(self.status.canonical_code.name) if self.status.description: status["description"] = self.status.description f_span = OrderedDict() f_span["name"] = self.name f_span["context"] = self._format_context(self.context) f_span["kind"] = str(self.kind) f_span["parent_id"] = parent_id f_span["start_time"] = start_time f_span["end_time"] = end_time if self.status is not None: f_span["status"] = status f_span["attributes"] = self._format_attributes(self.attributes) f_span["events"] = self._format_events(self.events) f_span["links"] = self._format_links(self.links) return json.dumps(f_span, indent=4) def get_context(self): return self.context def set_attribute(self, key: str, value: types.AttributeValue) -> None: with self._lock: if not self.is_recording_events(): return has_ended = self.end_time is not None if not has_ended: if self.attributes is Span._empty_attributes: self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES) if has_ended: logger.warning("Setting attribute on ended span.") return if not key: logger.warning("invalid key (empty or null)") return if isinstance(value, Sequence): error_message = self._check_attribute_value_sequence(value) if error_message is not None: logger.warning("%s in attribute value sequence", error_message) return # Freeze mutable sequences defensively if isinstance(value, MutableSequence): value = tuple(value) elif not isinstance(value, (bool, str, int, float)): logger.warning("invalid type for attribute value") return self.attributes[key] = value @staticmethod def _check_attribute_value_sequence(sequence: Sequence) -> Optional[str]: """ Checks if sequence items are valid and are of the same type """ if len(sequence) == 0: return None first_element_type = type(sequence[0]) if first_element_type not in (bool, str, int, float): return "invalid type" for element in sequence: if not isinstance(element, first_element_type): return "different type" return None def _add_event(self, event: EventBase) -> None: with self._lock: if not self.is_recording_events(): return has_ended = self.end_time is not None if not has_ended: if self.events is Span._empty_events: self.events = BoundedList(MAX_NUM_EVENTS) if has_ended: logger.warning("Calling add_event() on an ended span.") return self.events.append(event) def add_event( self, name: str, attributes: types.Attributes = None, timestamp: Optional[int] = None, ) -> None: if attributes is None: attributes = Span._empty_attributes self._add_event( Event( name=name, attributes=attributes, timestamp=time_ns() if timestamp is None else timestamp, ) ) def add_lazy_event( self, name: str, event_formatter: types.AttributesFormatter, timestamp: Optional[int] = None, ) -> None: self._add_event( LazyEvent( name=name, event_formatter=event_formatter, timestamp=time_ns() if timestamp is None else timestamp, ) ) def start(self, start_time: Optional[int] = None) -> None: with self._lock: if not self.is_recording_events(): return has_started = self.start_time is not None if not has_started: self._start_time = ( start_time if start_time is not None else time_ns() ) if has_started: logger.warning("Calling start() on a started span.") return self.span_processor.on_start(self) def end(self, end_time: Optional[int] = None) -> None: with self._lock: if not self.is_recording_events(): return if self.start_time is None: raise RuntimeError("Calling end() on a not started span.") has_ended = self.end_time is not None if not has_ended: if self.status is None: self.status = Status(canonical_code=StatusCanonicalCode.OK) self._end_time = ( end_time if end_time is not None else time_ns() ) if has_ended: logger.warning("Calling end() on an ended span.") return self.span_processor.on_end(self) def update_name(self, name: str) -> None: with self._lock: has_ended = self.end_time is not None if has_ended: logger.warning("Calling update_name() on an ended span.") return self.name = name def is_recording_events(self) -> bool: return True def set_status(self, status: trace_api.Status) -> None: with self._lock: has_ended = self.end_time is not None if has_ended: logger.warning("Calling set_status() on an ended span.") return self.status = status def __exit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: """Ends context manager and calls `end` on the `Span`.""" if ( self.status is None and self._set_status_on_exception and exc_val is not None ): self.set_status( Status( canonical_code=StatusCanonicalCode.UNKNOWN, description="{}: {}".format(exc_type.__name__, exc_val), ) ) super().__exit__(exc_type, exc_val, exc_tb)
class Span(trace_api.Span): """See `opentelemetry.trace.Span`. Users should create `Span`s via the `Tracer` instead of this constructor. Args: name: The name of the operation this span represents context: The immutable span context parent: This span's parent, may be a `SpanContext` if the parent is remote, null if this is a root span sampler: The sampler used to create this span trace_config: TODO resource: TODO attributes: The span's attributes to be exported events: Timestamped events to be exported links: Links to other spans to be exported span_processor: `SpanProcessor` to invoke when starting and ending this `Span`. """ # Initialize these lazily assuming most spans won't have them. empty_attributes = BoundedDict(MAX_NUM_ATTRIBUTES) empty_events = BoundedList(MAX_NUM_EVENTS) empty_links = BoundedList(MAX_NUM_LINKS) def __init__( self, name: str, context: trace_api.SpanContext, parent: trace_api.ParentSpan = None, sampler: Optional[sampling.Sampler] = None, trace_config: None = None, # TODO resource: None = None, # TODO attributes: types.Attributes = None, # TODO events: Sequence[trace_api.Event] = None, # TODO links: Sequence[trace_api.Link] = None, # TODO kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL, span_processor: SpanProcessor = SpanProcessor(), ) -> None: self.name = name self.context = context self.parent = parent self.sampler = sampler self.trace_config = trace_config self.resource = resource self.kind = kind self.span_processor = span_processor self.status = trace_api.Status() self._lock = threading.Lock() if attributes is None: self.attributes = Span.empty_attributes else: self.attributes = BoundedDict.from_map(MAX_NUM_ATTRIBUTES, attributes) if events is None: self.events = Span.empty_events else: self.events = BoundedList.from_seq(MAX_NUM_EVENTS, events) if links is None: self.links = Span.empty_links else: self.links = BoundedList.from_seq(MAX_NUM_LINKS, links) self.end_time = None # type: Optional[int] self.start_time = None # type: Optional[int] def __repr__(self): return '{}(name="{}", context={})'.format( type(self).__name__, self.name, self.context) def __str__(self): return '{}(name="{}", context={}, kind={}, parent={}, start_time={}, end_time={})'.format( type(self).__name__, self.name, self.context, self.kind, repr(self.parent), util.ns_to_iso_str(self.start_time) if self.start_time else "None", util.ns_to_iso_str(self.end_time) if self.end_time else "None", ) def get_context(self): return self.context def set_attribute(self, key: str, value: types.AttributeValue) -> None: with self._lock: if not self.is_recording_events(): return has_ended = self.end_time is not None if not has_ended: if self.attributes is Span.empty_attributes: self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES) if has_ended: logger.warning("Setting attribute on ended span.") return self.attributes[key] = value def add_event( self, name: str, timestamp: int = None, attributes: types.Attributes = None, ) -> None: self.add_lazy_event( trace_api.Event( name, time_ns() if timestamp is None else timestamp, Span.empty_attributes if attributes is None else attributes, )) def add_lazy_event(self, event: trace_api.Event) -> None: with self._lock: if not self.is_recording_events(): return has_ended = self.end_time is not None if not has_ended: if self.events is Span.empty_events: self.events = BoundedList(MAX_NUM_EVENTS) if has_ended: logger.warning("Calling add_event() on an ended span.") return self.events.append(event) def add_link( self, link_target_context: "trace_api.SpanContext", attributes: types.Attributes = None, ) -> None: if attributes is None: attributes = ( Span.empty_attributes ) # TODO: empty_attributes is not a Dict. Use Mapping? self.add_lazy_link(trace_api.Link(link_target_context, attributes)) def add_lazy_link(self, link: "trace_api.Link") -> None: with self._lock: if not self.is_recording_events(): return has_ended = self.end_time is not None if not has_ended: if self.links is Span.empty_links: self.links = BoundedList(MAX_NUM_LINKS) if has_ended: logger.warning("Calling add_link() on an ended span.") return self.links.append(link) def start(self, start_time: Optional[int] = None) -> None: with self._lock: if not self.is_recording_events(): return has_started = self.start_time is not None if not has_started: self.start_time = (start_time if start_time is not None else time_ns()) if has_started: logger.warning("Calling start() on a started span.") return self.span_processor.on_start(self) def end(self, end_time: int = None) -> None: with self._lock: if not self.is_recording_events(): return if self.start_time is None: raise RuntimeError("Calling end() on a not started span.") has_ended = self.end_time is not None if not has_ended: self.end_time = end_time if end_time is not None else time_ns() if has_ended: logger.warning("Calling end() on an ended span.") return self.span_processor.on_end(self) def update_name(self, name: str) -> None: with self._lock: has_ended = self.end_time is not None if has_ended: logger.warning("Calling update_name() on an ended span.") return self.name = name def is_recording_events(self) -> bool: return True def set_status(self, status: trace_api.Status) -> None: with self._lock: has_ended = self.end_time is not None if has_ended: logger.warning("Calling set_status() on an ended span.") return self.status = status
class Span(trace_api.Span): """See `opentelemetry.trace.Span`. Users should create `Span` objects via the `Tracer` instead of this constructor. Args: name: The name of the operation this span represents context: The immutable span context parent: This span's parent, may be a `SpanContext` if the parent is remote, null if this is a root span sampler: The sampler used to create this span trace_config: TODO resource: TODO attributes: The span's attributes to be exported events: Timestamped events to be exported links: Links to other spans to be exported span_processor: `SpanProcessor` to invoke when starting and ending this `Span`. """ # Initialize these lazily assuming most spans won't have them. empty_attributes = BoundedDict(MAX_NUM_ATTRIBUTES) empty_events = BoundedList(MAX_NUM_EVENTS) empty_links = BoundedList(MAX_NUM_LINKS) def __init__( self, name: str, context: trace_api.SpanContext, parent: trace_api.ParentSpan = None, sampler: Optional[sampling.Sampler] = None, trace_config: None = None, # TODO resource: None = None, # TODO attributes: types.Attributes = None, # TODO events: Sequence[trace_api.Event] = None, # TODO links: Sequence[trace_api.Link] = (), kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL, span_processor: SpanProcessor = SpanProcessor(), instrumentation_info: InstrumentationInfo = None, set_status_on_exception: bool = True, ) -> None: self.name = name self.context = context self.parent = parent self.sampler = sampler self.trace_config = trace_config self.resource = resource self.kind = kind self._set_status_on_exception = set_status_on_exception self.span_processor = span_processor self.status = None self._lock = threading.Lock() if attributes is None: self.attributes = Span.empty_attributes else: self.attributes = BoundedDict.from_map(MAX_NUM_ATTRIBUTES, attributes) if events is None: self.events = Span.empty_events else: self.events = BoundedList.from_seq(MAX_NUM_EVENTS, events) if links is None: self.links = Span.empty_links else: self.links = BoundedList.from_seq(MAX_NUM_LINKS, links) self._end_time = None # type: Optional[int] self._start_time = None # type: Optional[int] self.instrumentation_info = instrumentation_info @property def start_time(self): return self._start_time @property def end_time(self): return self._end_time def __repr__(self): return '{}(name="{}", context={})'.format( type(self).__name__, self.name, self.context) def __str__(self): return ('{}(name="{}", context={}, kind={}, ' "parent={}, start_time={}, end_time={})").format( type(self).__name__, self.name, self.context, self.kind, repr(self.parent), util.ns_to_iso_str(self.start_time) if self.start_time else "None", util.ns_to_iso_str(self.end_time) if self.end_time else "None", ) def get_context(self): return self.context def set_attribute(self, key: str, value: types.AttributeValue) -> None: with self._lock: if not self.is_recording_events(): return has_ended = self.end_time is not None if not has_ended: if self.attributes is Span.empty_attributes: self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES) if has_ended: logger.warning("Setting attribute on ended span.") return if isinstance(value, Sequence): error_message = self._check_attribute_value_sequence(value) if error_message is not None: logger.warning("%s in attribute value sequence", error_message) return elif not isinstance(value, (bool, str, Number, Sequence)): logger.warning("invalid type for attribute value") return self.attributes[key] = value @staticmethod def _check_attribute_value_sequence(sequence: Sequence) -> Optional[str]: """ Checks if sequence items are valid and are of the same type """ if len(sequence) == 0: return None first_element_type = type(sequence[0]) if issubclass(first_element_type, Number): first_element_type = Number if first_element_type not in (bool, str, Number): return "invalid type" for element in sequence: if not isinstance(element, first_element_type): return "different type" return None def add_event( self, name: str, attributes: types.Attributes = None, timestamp: Optional[int] = None, ) -> None: self.add_lazy_event( trace_api.Event( name, Span.empty_attributes if attributes is None else attributes, time_ns() if timestamp is None else timestamp, )) def add_lazy_event(self, event: trace_api.Event) -> None: with self._lock: if not self.is_recording_events(): return has_ended = self.end_time is not None if not has_ended: if self.events is Span.empty_events: self.events = BoundedList(MAX_NUM_EVENTS) if has_ended: logger.warning("Calling add_event() on an ended span.") return self.events.append(event) def start(self, start_time: Optional[int] = None) -> None: with self._lock: if not self.is_recording_events(): return has_started = self.start_time is not None if not has_started: self._start_time = (start_time if start_time is not None else time_ns()) if has_started: logger.warning("Calling start() on a started span.") return self.span_processor.on_start(self) def end(self, end_time: Optional[int] = None) -> None: with self._lock: if not self.is_recording_events(): return if self.start_time is None: raise RuntimeError("Calling end() on a not started span.") has_ended = self.end_time is not None if not has_ended: if self.status is None: self.status = Status(canonical_code=StatusCanonicalCode.OK) self._end_time = (end_time if end_time is not None else time_ns()) if has_ended: logger.warning("Calling end() on an ended span.") return self.span_processor.on_end(self) def update_name(self, name: str) -> None: with self._lock: has_ended = self.end_time is not None if has_ended: logger.warning("Calling update_name() on an ended span.") return self.name = name def is_recording_events(self) -> bool: return True def set_status(self, status: trace_api.Status) -> None: with self._lock: has_ended = self.end_time is not None if has_ended: logger.warning("Calling set_status() on an ended span.") return self.status = status def __exit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: """Ends context manager and calls `end` on the `Span`.""" if (self.status is None and self._set_status_on_exception and exc_val is not None): self.set_status( Status( canonical_code=StatusCanonicalCode.UNKNOWN, description="{}: {}".format(exc_type.__name__, exc_val), )) super().__exit__(exc_type, exc_val, exc_tb)