12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
14
15
+ from logging import getLogger
15
16
from threading import RLock
16
17
from typing import Dict , Iterable , List
17
18
18
- from opentelemetry ._metrics import Instrument
19
+ from opentelemetry ._metrics import Asynchronous , Instrument
19
20
from opentelemetry .sdk ._metrics ._internal ._view_instrument_match import (
20
21
_ViewInstrumentMatch ,
21
22
)
22
- from opentelemetry .sdk ._metrics ._internal .aggregation import Aggregation
23
23
from opentelemetry .sdk ._metrics ._internal .sdk_configuration import (
24
24
SdkConfiguration ,
25
25
)
26
26
from opentelemetry .sdk ._metrics ._internal .view import View
27
+ from opentelemetry .sdk ._metrics .aggregation import (
28
+ Aggregation ,
29
+ ExplicitBucketHistogramAggregation ,
30
+ )
27
31
from opentelemetry .sdk ._metrics .measurement import Measurement
28
32
from opentelemetry .sdk ._metrics .point import AggregationTemporality , Metric
29
33
34
+ _logger = getLogger (__name__ )
35
+
30
36
_DEFAULT_VIEW = View (instrument_name = "" )
31
37
32
38
@@ -40,7 +46,7 @@ def __init__(
40
46
) -> None :
41
47
self ._lock = RLock ()
42
48
self ._sdk_config = sdk_config
43
- self ._view_instrument_match : Dict [
49
+ self ._instrument_view_instrument_matches : Dict [
44
50
Instrument , List [_ViewInstrumentMatch ]
45
51
] = {}
46
52
self ._instrument_class_aggregation = instrument_class_aggregation
@@ -51,29 +57,20 @@ def _get_or_init_view_instrument_match(
51
57
# Optimistically get the relevant views for the given instrument. Once set for a given
52
58
# instrument, the mapping will never change
53
59
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 ]
56
62
57
63
with self ._lock :
58
64
# 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 ]
61
67
62
68
# not present, hold the lock and add a new mapping
63
69
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
+ )
77
74
78
75
# if no view targeted the instrument, use the default
79
76
if not view_instrument_matches :
@@ -87,7 +84,10 @@ def _get_or_init_view_instrument_match(
87
84
),
88
85
)
89
86
)
90
- self ._view_instrument_match [instrument ] = view_instrument_matches
87
+ self ._instrument_view_instrument_matches [
88
+ instrument
89
+ ] = view_instrument_matches
90
+
91
91
return view_instrument_matches
92
92
93
93
def consume_measurement (self , measurement : Measurement ) -> None :
@@ -114,7 +114,7 @@ def collect(
114
114
with self ._lock :
115
115
for (
116
116
view_instrument_matches
117
- ) in self ._view_instrument_match .values ():
117
+ ) in self ._instrument_view_instrument_matches .values ():
118
118
for view_instrument_match in view_instrument_matches :
119
119
metrics .extend (
120
120
view_instrument_match .collect (
@@ -123,3 +123,72 @@ def collect(
123
123
)
124
124
125
125
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