Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 07a5b64

Browse files
ocelotlaabmasslzchen
authored
Inform the user of conflicting view configuration (open-telemetry#2608)
* Inform the user if there is a view configuration conflict Fixes open-telemetry#2556 * Add test case * Fix lint * Add test case for another kind of collision * Fix test cases * Fix lint * Fix tests * Add equal comparison between views * Rename __eq__ to conflicts * Refactor deep code into a function * Refactor conflict handling methods * Update opentelemetry-sdk/src/opentelemetry/sdk/_metrics/_internal/_view_instrument_match.py Co-authored-by: Aaron Abbott <[email protected]> * Update opentelemetry-sdk/src/opentelemetry/sdk/_metrics/_internal/_view_instrument_match.py Co-authored-by: Leighton Chen <[email protected]> * Use continue Co-authored-by: Aaron Abbott <[email protected]> Co-authored-by: Leighton Chen <[email protected]>
1 parent a72c7de commit 07a5b64

File tree

3 files changed

+596
-28
lines changed

3 files changed

+596
-28
lines changed

opentelemetry-sdk/src/opentelemetry/sdk/_metrics/_internal/_view_instrument_match.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
_Aggregation,
2323
_convert_aggregation_temporality,
2424
_PointVarT,
25+
_SumAggregation,
2526
)
2627
from opentelemetry.sdk._metrics._internal.sdk_configuration import (
2728
SdkConfiguration,
@@ -52,6 +53,39 @@ def __init__(
5253
self._attributes_previous_point: Dict[frozenset, _PointVarT] = {}
5354
self._lock = Lock()
5455
self._instrument_class_aggregation = instrument_class_aggregation
56+
self._name = self._view._name or self._instrument.name
57+
self._description = (
58+
self._view._description or self._instrument.description
59+
)
60+
if not isinstance(self._view._aggregation, DefaultAggregation):
61+
self._aggregation = self._view._aggregation._create_aggregation(
62+
self._instrument
63+
)
64+
else:
65+
self._aggregation = self._instrument_class_aggregation[
66+
self._instrument.__class__
67+
]._create_aggregation(self._instrument)
68+
69+
def conflicts(self, other: "_ViewInstrumentMatch") -> bool:
70+
# pylint: disable=protected-access
71+
72+
result = (
73+
self._name == other._name
74+
and self._instrument.unit == other._instrument.unit
75+
# The aggregation class is being used here instead of data point
76+
# type since they are functionally equivalent.
77+
and self._aggregation.__class__ == other._aggregation.__class__
78+
)
79+
if isinstance(self._aggregation, _SumAggregation):
80+
result = (
81+
result
82+
and self._aggregation._instrument_is_monotonic
83+
== other._aggregation._instrument_is_monotonic
84+
and self._aggregation._instrument_temporality
85+
== other._aggregation._instrument_temporality
86+
)
87+
88+
return result
5589

5690
# pylint: disable=protected-access
5791
def consume_measurement(self, measurement: Measurement) -> None:
@@ -118,14 +152,11 @@ def collect(
118152

119153
yield Metric(
120154
attributes=dict(attributes),
121-
description=(
122-
self._view._description
123-
or self._instrument.description
124-
),
155+
description=self._description,
125156
instrumentation_scope=(
126157
self._instrument.instrumentation_scope
127158
),
128-
name=self._view._name or self._instrument.name,
159+
name=self._name,
129160
resource=self._sdk_config.resource,
130161
unit=self._instrument.unit,
131162
point=_convert_aggregation_temporality(

opentelemetry-sdk/src/opentelemetry/sdk/_metrics/_internal/metric_reader_storage.py

Lines changed: 91 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,27 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from logging import getLogger
1516
from threading import RLock
1617
from typing import Dict, Iterable, List
1718

18-
from opentelemetry._metrics import Instrument
19+
from opentelemetry._metrics import Asynchronous, Instrument
1920
from opentelemetry.sdk._metrics._internal._view_instrument_match import (
2021
_ViewInstrumentMatch,
2122
)
22-
from opentelemetry.sdk._metrics._internal.aggregation import Aggregation
2323
from opentelemetry.sdk._metrics._internal.sdk_configuration import (
2424
SdkConfiguration,
2525
)
2626
from opentelemetry.sdk._metrics._internal.view import View
27+
from opentelemetry.sdk._metrics.aggregation import (
28+
Aggregation,
29+
ExplicitBucketHistogramAggregation,
30+
)
2731
from opentelemetry.sdk._metrics.measurement import Measurement
2832
from opentelemetry.sdk._metrics.point import AggregationTemporality, Metric
2933

34+
_logger = getLogger(__name__)
35+
3036
_DEFAULT_VIEW = View(instrument_name="")
3137

3238

@@ -40,7 +46,7 @@ def __init__(
4046
) -> None:
4147
self._lock = RLock()
4248
self._sdk_config = sdk_config
43-
self._view_instrument_match: Dict[
49+
self._instrument_view_instrument_matches: Dict[
4450
Instrument, List[_ViewInstrumentMatch]
4551
] = {}
4652
self._instrument_class_aggregation = instrument_class_aggregation
@@ -51,29 +57,20 @@ def _get_or_init_view_instrument_match(
5157
# Optimistically get the relevant views for the given instrument. Once set for a given
5258
# instrument, the mapping will never change
5359

54-
if instrument in self._view_instrument_match:
55-
return self._view_instrument_match[instrument]
60+
if instrument in self._instrument_view_instrument_matches:
61+
return self._instrument_view_instrument_matches[instrument]
5662

5763
with self._lock:
5864
# double check if it was set before we held the lock
59-
if instrument in self._view_instrument_match:
60-
return self._view_instrument_match[instrument]
65+
if instrument in self._instrument_view_instrument_matches:
66+
return self._instrument_view_instrument_matches[instrument]
6167

6268
# not present, hold the lock and add a new mapping
6369
view_instrument_matches = []
64-
for view in self._sdk_config.views:
65-
# pylint: disable=protected-access
66-
if view._match(instrument):
67-
view_instrument_matches.append(
68-
_ViewInstrumentMatch(
69-
view=view,
70-
instrument=instrument,
71-
sdk_config=self._sdk_config,
72-
instrument_class_aggregation=(
73-
self._instrument_class_aggregation
74-
),
75-
)
76-
)
70+
71+
self._handle_view_instrument_match(
72+
instrument, view_instrument_matches
73+
)
7774

7875
# if no view targeted the instrument, use the default
7976
if not view_instrument_matches:
@@ -87,7 +84,10 @@ def _get_or_init_view_instrument_match(
8784
),
8885
)
8986
)
90-
self._view_instrument_match[instrument] = view_instrument_matches
87+
self._instrument_view_instrument_matches[
88+
instrument
89+
] = view_instrument_matches
90+
9191
return view_instrument_matches
9292

9393
def consume_measurement(self, measurement: Measurement) -> None:
@@ -114,7 +114,7 @@ def collect(
114114
with self._lock:
115115
for (
116116
view_instrument_matches
117-
) in self._view_instrument_match.values():
117+
) in self._instrument_view_instrument_matches.values():
118118
for view_instrument_match in view_instrument_matches:
119119
metrics.extend(
120120
view_instrument_match.collect(
@@ -123,3 +123,72 @@ def collect(
123123
)
124124

125125
return metrics
126+
127+
def _handle_view_instrument_match(
128+
self,
129+
instrument: Instrument,
130+
view_instrument_matches: List["_ViewInstrumentMatch"],
131+
) -> None:
132+
for view in self._sdk_config.views:
133+
# pylint: disable=protected-access
134+
if not view._match(instrument):
135+
continue
136+
137+
if not self._check_view_instrument_compatibility(view, instrument):
138+
continue
139+
140+
new_view_instrument_match = _ViewInstrumentMatch(
141+
view=view,
142+
instrument=instrument,
143+
sdk_config=self._sdk_config,
144+
instrument_class_aggregation=(
145+
self._instrument_class_aggregation
146+
),
147+
)
148+
149+
for (
150+
existing_view_instrument_matches
151+
) in self._instrument_view_instrument_matches.values():
152+
for (
153+
existing_view_instrument_match
154+
) in existing_view_instrument_matches:
155+
if existing_view_instrument_match.conflicts(
156+
new_view_instrument_match
157+
):
158+
159+
_logger.warning(
160+
"Views %s and %s will cause conflicting "
161+
"metrics identities",
162+
existing_view_instrument_match._view,
163+
new_view_instrument_match._view,
164+
)
165+
166+
view_instrument_matches.append(new_view_instrument_match)
167+
168+
@staticmethod
169+
def _check_view_instrument_compatibility(
170+
view: View, instrument: Instrument
171+
) -> bool:
172+
"""
173+
Checks if a view and an instrument are compatible.
174+
175+
Returns `true` if they are compatible and a `_ViewInstrumentMatch`
176+
object should be created, `false` otherwise.
177+
"""
178+
179+
result = True
180+
181+
# pylint: disable=protected-access
182+
if isinstance(instrument, Asynchronous) and isinstance(
183+
view._aggregation, ExplicitBucketHistogramAggregation
184+
):
185+
_logger.warning(
186+
"View %s and instrument %s will produce "
187+
"semantic errors when matched, the view "
188+
"has not been applied.",
189+
view,
190+
instrument,
191+
)
192+
result = False
193+
194+
return result

0 commit comments

Comments
 (0)