Skip to content

Commit fe8945f

Browse files
committed
Initial changes to add support for MeterConfigurator
1 parent cc0b183 commit fe8945f

5 files changed

Lines changed: 161 additions & 21 deletions

File tree

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/__init__.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414

1515
import weakref
1616
from atexit import register, unregister
17+
from dataclasses import dataclass
1718
from logging import getLogger
1819
from os import environ
1920
from threading import Lock
2021
from time import time_ns
21-
from typing import Optional, Sequence
22+
from typing import Callable, Optional, Sequence
2223

2324
# This kind of import is needed to avoid Sphinx errors.
2425
import opentelemetry.sdk.metrics
@@ -71,13 +72,36 @@
7172
_logger = getLogger(__name__)
7273

7374

75+
@dataclass
76+
class _MeterConfig:
77+
is_enabled: bool = True
78+
79+
@classmethod
80+
def default(cls) -> "_MeterConfig":
81+
return _MeterConfig()
82+
83+
84+
class _ProxyMeterConfig:
85+
def __init__(self, config: _MeterConfig):
86+
self._config = config
87+
88+
@property
89+
def is_enabled(self) -> bool:
90+
return self._config.is_enabled
91+
92+
def update(self, config: _MeterConfig) -> None:
93+
self._config = config
94+
95+
7496
class Meter(APIMeter):
7597
"""See `opentelemetry.metrics.Meter`."""
7698

7799
def __init__(
78100
self,
79101
instrumentation_scope: InstrumentationScope,
80102
measurement_consumer: MeasurementConsumer,
103+
*,
104+
_meter_config: Optional[_MeterConfig] = None,
81105
):
82106
super().__init__(
83107
name=instrumentation_scope.name,
@@ -88,6 +112,15 @@ def __init__(
88112
self._measurement_consumer = measurement_consumer
89113
self._instrument_id_instrument = {}
90114
self._instrument_registration_lock = Lock()
115+
self._meter_config = _ProxyMeterConfig(
116+
_meter_config or _MeterConfig.default()
117+
)
118+
119+
def _is_enabled(self) -> bool:
120+
return self._meter_config.is_enabled
121+
122+
def _set_meter_config(self, meter_config: _MeterConfig) -> None:
123+
self._meter_config.update(meter_config)
91124

92125
def create_counter(self, name, unit="", description="") -> APICounter:
93126
with self._instrument_registration_lock:
@@ -102,6 +135,7 @@ def create_counter(self, name, unit="", description="") -> APICounter:
102135
self._measurement_consumer,
103136
unit,
104137
description,
138+
_meter_config=self._meter_config,
105139
)
106140
)
107141
instrument = self._instrument_id_instrument[status.instrument_id]
@@ -134,6 +168,7 @@ def create_up_down_counter(
134168
self._measurement_consumer,
135169
unit,
136170
description,
171+
_meter_config=self._meter_config,
137172
)
138173
)
139174
instrument = self._instrument_id_instrument[status.instrument_id]
@@ -171,6 +206,7 @@ def create_observable_counter(
171206
callbacks,
172207
unit,
173208
description,
209+
_meter_config=self._meter_config,
174210
)
175211
)
176212
instrument = self._instrument_id_instrument[status.instrument_id]
@@ -239,6 +275,7 @@ def create_histogram(
239275
unit,
240276
description,
241277
explicit_bucket_boundaries_advisory,
278+
_meter_config=self._meter_config,
242279
)
243280
)
244281
instrument = self._instrument_id_instrument[status.instrument_id]
@@ -266,6 +303,7 @@ def create_gauge(self, name, unit="", description="") -> APIGauge:
266303
self._measurement_consumer,
267304
unit,
268305
description,
306+
_meter_config=self._meter_config,
269307
)
270308
instrument = self._instrument_id_instrument[status.instrument_id]
271309

@@ -298,6 +336,7 @@ def create_observable_gauge(
298336
callbacks,
299337
unit,
300338
description,
339+
_meter_config=self._meter_config,
301340
)
302341
)
303342
instrument = self._instrument_id_instrument[status.instrument_id]
@@ -336,6 +375,7 @@ def create_observable_up_down_counter(
336375
callbacks,
337376
unit,
338377
description,
378+
_meter_config=self._meter_config,
339379
)
340380
)
341381
instrument = self._instrument_id_instrument[status.instrument_id]
@@ -370,6 +410,43 @@ def _get_exemplar_filter(exemplar_filter: str) -> ExemplarFilter:
370410
raise ValueError(msg)
371411

372412

413+
_MeterConfiguratorT = Callable[[InstrumentationScope], _MeterConfig]
414+
_InstrumentationScopePredicateT = Callable[[InstrumentationScope], bool]
415+
_MeterConfiguratorRulesT = Sequence[
416+
tuple[_InstrumentationScopePredicateT, _MeterConfig]
417+
]
418+
419+
420+
def _default_meter_configurator(
421+
_meter_scope: InstrumentationScope,
422+
) -> _MeterConfig:
423+
return _MeterConfig.default()
424+
425+
426+
def _disable_meter_configurator(
427+
_meter_scope: InstrumentationScope,
428+
) -> _MeterConfig:
429+
return _MeterConfig(is_enabled=False)
430+
431+
432+
class _RuleBasedMeterConfigurator:
433+
def __init__(
434+
self,
435+
*,
436+
rules: _MeterConfiguratorRulesT,
437+
default_config: _MeterConfig,
438+
):
439+
self._rules = rules
440+
self._default_config = default_config
441+
442+
def __call__(self, meter_scope: InstrumentationScope) -> _MeterConfig:
443+
for predicate, meter_config in self._rules:
444+
if predicate(meter_scope):
445+
return meter_config
446+
# by default return default config
447+
return self._default_config
448+
449+
373450
class MeterProvider(APIMeterProvider):
374451
r"""See `opentelemetry.metrics.MeterProvider`.
375452
@@ -414,6 +491,8 @@ def __init__(
414491
exemplar_filter: Optional[ExemplarFilter] = None,
415492
shutdown_on_exit: bool = True,
416493
views: Sequence["opentelemetry.sdk.metrics.view.View"] = (),
494+
*,
495+
_meter_configurator: Optional[_MeterConfiguratorT] = None,
417496
):
418497
self._lock = Lock()
419498
self._meter_lock = Lock()
@@ -443,6 +522,9 @@ def __init__(
443522
self._meters = {}
444523
self._shutdown_once = Once()
445524
self._shutdown = False
525+
self._meter_configurator = (
526+
_meter_configurator or _default_meter_configurator
527+
)
446528

447529
for metric_reader in self._sdk_config.metric_readers:
448530
with self._all_metric_readers_lock:
@@ -459,6 +541,22 @@ def __init__(
459541
self._measurement_consumer.collect
460542
)
461543

544+
def _set_meter_configurator(
545+
self, *, meter_configurator: _MeterConfiguratorT
546+
):
547+
"""Set a new MeterConfigurator for this MeterProvider.
548+
549+
Setting a new MeterConfigurator will result in the configurator being called
550+
for each outstanding Meter and for any newly created meters thereafter.
551+
Therefore, it is important that the provided function returns quickly.
552+
"""
553+
self._meter_configurator = meter_configurator
554+
with self._meter_lock:
555+
for info, meter in self._meters.items():
556+
if not isinstance(meter, Meter):
557+
continue
558+
meter._set_meter_config(self._meter_configurator(info))
559+
462560
def force_flush(self, timeout_millis: float = 10_000) -> bool:
463561
deadline_ns = time_ns() + timeout_millis * 10**6
464562

@@ -574,5 +672,6 @@ def get_meter(
574672
self._meters[info] = Meter(
575673
info,
576674
self._measurement_consumer,
675+
_meter_config=self._meter_configurator(info),
577676
)
578677
return self._meters[info]

opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/instrument.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@
1717

1818
from logging import getLogger
1919
from time import time_ns
20-
from typing import Generator, Iterable, List, Sequence, Union
20+
from typing import TYPE_CHECKING, Generator, Iterable, List, Sequence, Union
2121

2222
# This kind of import is needed to avoid Sphinx errors.
23-
import opentelemetry.sdk.metrics
2423
from opentelemetry.context import Context, get_current
2524
from opentelemetry.metrics import CallbackT
2625
from opentelemetry.metrics import Counter as APICounter
@@ -37,7 +36,14 @@
3736
_MetricsHistogramAdvisory,
3837
)
3938
from opentelemetry.sdk.metrics._internal.measurement import Measurement
40-
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
39+
40+
if TYPE_CHECKING:
41+
from opentelemetry.sdk.metrics._internal import (
42+
MeasurementConsumer,
43+
_ProxyMeterConfig,
44+
)
45+
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
46+
4147

4248
_logger = getLogger(__name__)
4349

@@ -52,9 +58,11 @@ def __init__(
5258
self,
5359
name: str,
5460
instrumentation_scope: InstrumentationScope,
55-
measurement_consumer: "opentelemetry.sdk.metrics.MeasurementConsumer",
61+
measurement_consumer: MeasurementConsumer,
5662
unit: str = "",
5763
description: str = "",
64+
*,
65+
_meter_config: _ProxyMeterConfig | None = None,
5866
):
5967
# pylint: disable=no-member
6068
result = self._check_name_unit_description(name, unit, description)
@@ -76,18 +84,24 @@ def __init__(
7684
self.description = description
7785
self.instrumentation_scope = instrumentation_scope
7886
self._measurement_consumer = measurement_consumer
87+
self._meter_config = _meter_config
7988
super().__init__(name, unit=unit, description=description)
8089

90+
def _is_enabled(self) -> bool:
91+
return self._meter_config is None or self._meter_config.is_enabled
92+
8193

8294
class _Asynchronous:
8395
def __init__(
8496
self,
8597
name: str,
8698
instrumentation_scope: InstrumentationScope,
87-
measurement_consumer: "opentelemetry.sdk.metrics.MeasurementConsumer",
99+
measurement_consumer: MeasurementConsumer,
88100
callbacks: Iterable[CallbackT] | None = None,
89101
unit: str = "",
90102
description: str = "",
103+
*,
104+
_meter_config: _ProxyMeterConfig | None = None,
91105
):
92106
# pylint: disable=no-member
93107
result = self._check_name_unit_description(name, unit, description)
@@ -109,6 +123,7 @@ def __init__(
109123
self.description = description
110124
self.instrumentation_scope = instrumentation_scope
111125
self._measurement_consumer = measurement_consumer
126+
self._meter_config = _meter_config
112127
super().__init__(name, callbacks, unit=unit, description=description)
113128

114129
self._callbacks: List[CallbackT] = []
@@ -132,9 +147,14 @@ def inner(
132147
else:
133148
self._callbacks.append(callback)
134149

150+
def _is_enabled(self) -> bool:
151+
return self._meter_config is None or self._meter_config.is_enabled
152+
135153
def callback(
136154
self, callback_options: CallbackOptions
137155
) -> Iterable[Measurement]:
156+
if not self._is_enabled():
157+
return
138158
for callback in self._callbacks:
139159
try:
140160
for api_measurement in callback(callback_options):
@@ -163,6 +183,9 @@ def add(
163183
attributes: dict[str, str] | None = None,
164184
context: Context | None = None,
165185
):
186+
if not self._is_enabled():
187+
return super().add(amount, attributes=attributes, context=context)
188+
166189
if amount < 0:
167190
_logger.warning(
168191
"Add amount must be non-negative on Counter %s.", self.name
@@ -192,6 +215,9 @@ def add(
192215
attributes: dict[str, str] | None = None,
193216
context: Context | None = None,
194217
):
218+
if not self._is_enabled():
219+
return super().add(amount, attributes=attributes, context=context)
220+
195221
time_unix_nano = time_ns()
196222
self._measurement_consumer.consume_measurement(
197223
Measurement(
@@ -227,17 +253,20 @@ def __init__(
227253
self,
228254
name: str,
229255
instrumentation_scope: InstrumentationScope,
230-
measurement_consumer: "opentelemetry.sdk.metrics.MeasurementConsumer",
256+
measurement_consumer: MeasurementConsumer,
231257
unit: str = "",
232258
description: str = "",
233259
explicit_bucket_boundaries_advisory: Sequence[float] | None = None,
260+
*,
261+
_meter_config: _ProxyMeterConfig | None = None,
234262
):
235263
super().__init__(
236264
name,
237265
unit=unit,
238266
description=description,
239267
instrumentation_scope=instrumentation_scope,
240268
measurement_consumer=measurement_consumer,
269+
_meter_config=_meter_config,
241270
)
242271
self._advisory = _MetricsHistogramAdvisory(
243272
explicit_bucket_boundaries=explicit_bucket_boundaries_advisory
@@ -254,6 +283,11 @@ def record(
254283
attributes: dict[str, str] | None = None,
255284
context: Context | None = None,
256285
):
286+
if not self._is_enabled():
287+
return super().record(
288+
amount, attributes=attributes, context=context
289+
)
290+
257291
if amount < 0:
258292
_logger.warning(
259293
"Record amount must be non-negative on Histogram %s.",
@@ -284,6 +318,9 @@ def set(
284318
attributes: dict[str, str] | None = None,
285319
context: Context | None = None,
286320
):
321+
if not self._is_enabled():
322+
return super().set(amount, attributes=attributes, context=context)
323+
287324
time_unix_nano = time_ns()
288325
self._measurement_consumer.consume_measurement(
289326
Measurement(

opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import abc
1717
import atexit
1818
import concurrent.futures
19-
import fnmatch
2019
import json
2120
import logging
2221
import os
@@ -1264,16 +1263,6 @@ def start_span( # pylint: disable=too-many-locals
12641263
]
12651264

12661265

1267-
# TODO: share this with configurators for other signals
1268-
def _scope_name_matches_glob(
1269-
glob_pattern: str,
1270-
) -> _InstrumentationScopePredicateT:
1271-
def inner(scope: InstrumentationScope) -> bool:
1272-
return fnmatch.fnmatch(scope.name, glob_pattern)
1273-
1274-
return inner
1275-
1276-
12771266
class _RuleBasedTracerConfigurator:
12781267
def __init__(
12791268
self,

0 commit comments

Comments
 (0)