1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15+ from logging import getLogger
1516from threading import RLock
1617from typing import Dict , Iterable , List
1718
18- from opentelemetry ._metrics import Instrument
19+ from opentelemetry ._metrics import Asynchronous , Instrument
1920from opentelemetry .sdk ._metrics ._internal ._view_instrument_match import (
2021 _ViewInstrumentMatch ,
2122)
22- from opentelemetry .sdk ._metrics ._internal .aggregation import Aggregation
2323from opentelemetry .sdk ._metrics ._internal .sdk_configuration import (
2424 SdkConfiguration ,
2525)
2626from opentelemetry .sdk ._metrics ._internal .view import View
27+ from opentelemetry .sdk ._metrics .aggregation import (
28+ Aggregation ,
29+ ExplicitBucketHistogramAggregation ,
30+ )
2731from opentelemetry .sdk ._metrics .measurement import Measurement
2832from 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