-
Notifications
You must be signed in to change notification settings - Fork 828
use BoundedAttributes for attributes in link, event, resource, spans #1915
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
e7550e7
f237bae
a8e923a
c00d04a
09ca0a3
9bd9596
736500d
c9662ed
943b078
f05094d
f29c775
48a378d
8a84a0a
1b98aa9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,8 +14,11 @@ | |
| # type: ignore | ||
|
|
||
| import logging | ||
| import threading | ||
| from collections import OrderedDict | ||
| from collections.abc import MutableMapping | ||
| from types import MappingProxyType | ||
| from typing import MutableSequence, Sequence | ||
| from typing import MutableSequence, Optional, Sequence | ||
|
|
||
| from opentelemetry.util import types | ||
|
|
||
|
|
@@ -108,3 +111,73 @@ def _create_immutable_attributes( | |
| attributes: types.Attributes, | ||
| ) -> types.Attributes: | ||
| return MappingProxyType(attributes.copy() if attributes else {}) | ||
|
|
||
|
|
||
| _DEFAULT_LIMIT = 128 | ||
|
|
||
|
|
||
| class BoundedDict(MutableMapping): | ||
| """An ordered dict with a fixed max capacity. | ||
|
|
||
| Oldest elements are dropped when the dict is full and a new element is | ||
| added. | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| maxlen: Optional[int] = _DEFAULT_LIMIT, | ||
| attributes: types.Attributes = None, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be good to make a separation from the SDK
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I considered for a minute making
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe just
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| immutable: bool = True, | ||
| ): | ||
| if maxlen is not None: | ||
| if not isinstance(maxlen, int): | ||
| raise ValueError | ||
| if maxlen < 0: | ||
| raise ValueError | ||
lzchen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| self.maxlen = maxlen | ||
| self.dropped = 0 | ||
| self._dict = OrderedDict() # type: OrderedDict | ||
| self._lock = threading.Lock() # type: threading.Lock | ||
| if attributes: | ||
| _filter_attributes(attributes) | ||
| for key, value in attributes.items(): | ||
| self[key] = value | ||
| self._immutable = immutable | ||
|
|
||
| def __repr__(self): | ||
| return "{}({}, maxlen={})".format( | ||
| type(self).__name__, dict(self._dict), self.maxlen | ||
| ) | ||
|
|
||
| def __getitem__(self, key): | ||
| return self._dict[key] | ||
|
|
||
| def __setitem__(self, key, value): | ||
| if getattr(self, "_immutable", False): | ||
| raise TypeError | ||
| with self._lock: | ||
| if self.maxlen is not None and self.maxlen == 0: | ||
| self.dropped += 1 | ||
| return | ||
|
|
||
| if key in self._dict: | ||
| del self._dict[key] | ||
| elif self.maxlen is not None and len(self._dict) == self.maxlen: | ||
| del self._dict[next(iter(self._dict.keys()))] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious what people's thoughts on this are. It's confusing to me that if the attributes dict is full, we delete an existing item from the dictionary in favour of the new item. Any thoughts @lzchen?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah my first impression when reading the specs is that the LATEST item that is attempting to be added would be dropped instead of popping the first item that was added. If we decide on changing the functionality, you could leave that for a separate PR if you wish.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do other SIGs do? And also spec says There SHOULD be a log emitted to indicate to the user that an attribute, event, or link was discarded due to such a limit. To prevent excessive logging, the log should not be emitted once per span, or per discarded attribute, event, or links. https://github.com/open-telemetry/opentelemetry-specification/blob/b46bcab5fb709381f1fd52096a19541370c7d1b3/specification/trace/sdk.md#span-limits
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like go does the same thing as we do currently. https://github.com/open-telemetry/opentelemetry-go/blob/c1f460e097d395fa7cd02acc4a6096d6c6f14b08/sdk/trace/attributesmap.go#L45-L62 I agree that for now it probably doesn't make sense to change the behaviour |
||
| self.dropped += 1 | ||
| self._dict[key] = value | ||
|
|
||
| def __delitem__(self, key): | ||
| if getattr(self, "_immutable", False): | ||
| raise TypeError | ||
| del self._dict[key] | ||
lzchen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def __iter__(self): | ||
| with self._lock: | ||
| return iter(self._dict.copy()) | ||
|
|
||
| def __len__(self): | ||
| return len(self._dict) | ||
|
|
||
| def copy(self): | ||
| return self._dict.copy() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,11 +39,7 @@ | |
|
|
||
| from opentelemetry import context as context_api | ||
| from opentelemetry import trace as trace_api | ||
| from opentelemetry.attributes import ( | ||
| _create_immutable_attributes, | ||
| _filter_attributes, | ||
| _is_valid_attribute_value, | ||
| ) | ||
| from opentelemetry.attributes import BoundedDict, _is_valid_attribute_value | ||
| from opentelemetry.sdk import util | ||
| from opentelemetry.sdk.environment_variables import ( | ||
| OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, | ||
|
|
@@ -53,7 +49,7 @@ | |
| from opentelemetry.sdk.resources import Resource | ||
| from opentelemetry.sdk.trace import sampling | ||
| from opentelemetry.sdk.trace.id_generator import IdGenerator, RandomIdGenerator | ||
| from opentelemetry.sdk.util import BoundedDict, BoundedList | ||
| from opentelemetry.sdk.util import BoundedList | ||
| from opentelemetry.sdk.util.instrumentation import InstrumentationInfo | ||
| from opentelemetry.trace import SpanContext | ||
| from opentelemetry.trace.status import Status, StatusCode | ||
|
|
@@ -65,6 +61,8 @@ | |
| _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT = 128 | ||
| _DEFAULT_OTEL_SPAN_EVENT_COUNT_LIMIT = 128 | ||
| _DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT = 128 | ||
| _DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT = 128 | ||
| _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT = 128 | ||
|
|
||
|
|
||
| _ENV_VALUE_UNSET = "unset" | ||
|
|
@@ -526,19 +524,27 @@ class SpanLimits: | |
| max_links: Maximum number of links that can be added to a Span. | ||
| Environment variable: OTEL_SPAN_LINK_COUNT_LIMIT | ||
| Default: {_DEFAULT_SPAN_LINK_COUNT_LIMIT} | ||
| max_event_attributes: Maximum number of attributes that can be added to an Event. | ||
| Default: {_DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT} | ||
| max_link_attributes: Maximum number of attributes that can be added to a Link. | ||
| Default: {_DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT} | ||
| """ | ||
|
|
||
| UNSET = -1 | ||
|
|
||
| max_attributes: int | ||
| max_events: int | ||
| max_links: int | ||
| max_event_attributes: int | ||
| max_link_attributes: int | ||
|
|
||
| def __init__( | ||
| self, | ||
| max_attributes: Optional[int] = None, | ||
| max_events: Optional[int] = None, | ||
| max_links: Optional[int] = None, | ||
| max_event_attributes: Optional[int] = None, | ||
| max_link_attributes: Optional[int] = None, | ||
| ): | ||
| self.max_attributes = self._from_env_if_absent( | ||
| max_attributes, | ||
|
|
@@ -555,6 +561,16 @@ def __init__( | |
| OTEL_SPAN_LINK_COUNT_LIMIT, | ||
| _DEFAULT_OTEL_SPAN_LINK_COUNT_LIMIT, | ||
| ) | ||
| self.max_event_attributes = self._from_env_if_absent( | ||
| max_event_attributes, | ||
| OTEL_SPAN_LINK_COUNT_LIMIT, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these placeholders until this is merged? Would be good to have a TODO.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Captured the work in an issue #1918 |
||
| _DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT, | ||
| ) | ||
| self.max_link_attributes = self._from_env_if_absent( | ||
| max_link_attributes, | ||
| OTEL_SPAN_LINK_COUNT_LIMIT, | ||
| _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT, | ||
| ) | ||
|
|
||
| def __repr__(self): | ||
| return "max_attributes={}, max_events={}, max_links={}".format( | ||
lzchen marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
@@ -591,6 +607,8 @@ def _from_env_if_absent( | |
| max_attributes=SpanLimits.UNSET, | ||
| max_events=SpanLimits.UNSET, | ||
| max_links=SpanLimits.UNSET, | ||
| max_event_attributes=SpanLimits.UNSET, | ||
| max_link_attributes=SpanLimits.UNSET, | ||
| ) | ||
|
|
||
| SPAN_ATTRIBUTE_COUNT_LIMIT = SpanLimits._from_env_if_absent( | ||
|
|
@@ -661,22 +679,14 @@ def __init__( | |
| self._span_processor = span_processor | ||
| self._limits = limits | ||
| self._lock = threading.Lock() | ||
|
|
||
| _filter_attributes(attributes) | ||
| if not attributes: | ||
| self._attributes = self._new_attributes() | ||
| else: | ||
| self._attributes = BoundedDict.from_map( | ||
| self._limits.max_attributes, attributes | ||
| ) | ||
|
|
||
| self._attributes = BoundedDict( | ||
| self._limits.max_attributes, attributes, immutable=False | ||
| ) | ||
| self._events = self._new_events() | ||
| if events: | ||
| for event in events: | ||
| _filter_attributes(event.attributes) | ||
| # pylint: disable=protected-access | ||
| event._attributes = _create_immutable_attributes( | ||
| event.attributes | ||
| event._attributes = BoundedDict( | ||
| self._limits.max_event_attributes, event.attributes | ||
| ) | ||
| self._events.append(event) | ||
|
|
||
|
|
@@ -690,9 +700,6 @@ def __repr__(self): | |
| type(self).__name__, self._name, self._context | ||
| ) | ||
|
|
||
| def _new_attributes(self): | ||
| return BoundedDict(self._limits.max_attributes) | ||
|
|
||
| def _new_events(self): | ||
| return BoundedList(self._limits.max_events) | ||
|
|
||
|
|
@@ -745,13 +752,12 @@ def add_event( | |
| attributes: types.Attributes = None, | ||
| timestamp: Optional[int] = None, | ||
| ) -> None: | ||
| _filter_attributes(attributes) | ||
| attributes = _create_immutable_attributes(attributes) | ||
| attributes = BoundedDict(self._limits.max_event_attributes, attributes) | ||
| self._add_event( | ||
| Event( | ||
| name=name, | ||
| attributes=attributes, | ||
| timestamp=_time_ns() if timestamp is None else timestamp, | ||
| timestamp=timestamp, | ||
srikanthccv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ) | ||
| ) | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.