Skip to content

Commit 42b0b0c

Browse files
committed
Update sdk
1 parent b676189 commit 42b0b0c

File tree

5 files changed

+132
-37
lines changed

5 files changed

+132
-37
lines changed

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

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
1415
import weakref
1516
from atexit import register, unregister
17+
from collections.abc import Sequence
1618
from logging import getLogger
1719
from os import environ
1820
from threading import Lock
1921
from time import time_ns
20-
from typing import Optional, Sequence
22+
from typing import Optional
2123

2224
# This kind of import is needed to avoid Sphinx errors.
2325
import opentelemetry.sdk.metrics
@@ -63,7 +65,7 @@
6365
from opentelemetry.sdk.resources import Resource
6466
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
6567
from opentelemetry.util._once import Once
66-
from opentelemetry.util.types import Attributes
68+
from opentelemetry.util.types import Attributes, MetricsInstrumentAdvisory
6769

6870
_logger = getLogger(__name__)
6971

@@ -196,7 +198,29 @@ def create_observable_counter(
196198
self._instrument_id_instrument[instrument_id] = instrument
197199
return instrument
198200

199-
def create_histogram(self, name, unit="", description="") -> APIHistogram:
201+
def create_histogram(
202+
self,
203+
name: str,
204+
unit: str = "",
205+
description: str = "",
206+
advisory: MetricsInstrumentAdvisory = None,
207+
) -> APIHistogram:
208+
if advisory is not None:
209+
raise_error = False
210+
try:
211+
boundaries = advisory["ExplicitBucketBoundaries"]
212+
raise_error = not (
213+
boundaries
214+
and all(isinstance(e, (int, float)) for e in boundaries)
215+
)
216+
except (KeyError, TypeError):
217+
raise_error = True
218+
219+
if raise_error:
220+
raise ValueError(
221+
"Advisory must be a dict with ExplicitBucketBoundaries key containing a sequence of numbers"
222+
)
223+
200224
(
201225
is_instrument_registered,
202226
instrument_id,
@@ -223,6 +247,7 @@ def create_histogram(self, name, unit="", description="") -> APIHistogram:
223247
self._measurement_consumer,
224248
unit,
225249
description,
250+
advisory,
226251
)
227252
with self._instrument_id_instrument_lock:
228253
self._instrument_id_instrument[instrument_id] = instrument

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

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -437,32 +437,40 @@ def collect(
437437
)
438438

439439

440+
_DEFAULT_EXPLICIT_BUCKET_HISTOGRAM_AGGREGATION_BOUNDARIES: Sequence[float] = (
441+
0.0,
442+
5.0,
443+
10.0,
444+
25.0,
445+
50.0,
446+
75.0,
447+
100.0,
448+
250.0,
449+
500.0,
450+
750.0,
451+
1000.0,
452+
2500.0,
453+
5000.0,
454+
7500.0,
455+
10000.0,
456+
)
457+
458+
440459
class _ExplicitBucketHistogramAggregation(_Aggregation[HistogramPoint]):
441460
def __init__(
442461
self,
443462
attributes: Attributes,
444463
instrument_aggregation_temporality: AggregationTemporality,
445464
start_time_unix_nano: int,
446465
reservoir_builder: ExemplarReservoirBuilder,
447-
boundaries: Sequence[float] = (
448-
0.0,
449-
5.0,
450-
10.0,
451-
25.0,
452-
50.0,
453-
75.0,
454-
100.0,
455-
250.0,
456-
500.0,
457-
750.0,
458-
1000.0,
459-
2500.0,
460-
5000.0,
461-
7500.0,
462-
10000.0,
463-
),
466+
boundaries: Optional[Sequence[float]] = None,
464467
record_min_max: bool = True,
465468
):
469+
if boundaries is None:
470+
boundaries = (
471+
_DEFAULT_EXPLICIT_BUCKET_HISTOGRAM_AGGREGATION_BOUNDARIES
472+
)
473+
466474
super().__init__(
467475
attributes,
468476
reservoir_builder=partial(
@@ -1268,6 +1276,11 @@ def _create_aggregation(
12681276
)
12691277

12701278
if isinstance(instrument, Histogram):
1279+
boundaries: Optional[Sequence[float]] = (
1280+
instrument.advisory.get("ExplicitBucketBoundaries")
1281+
if instrument.advisory is not None
1282+
else None
1283+
)
12711284
return _ExplicitBucketHistogramAggregation(
12721285
attributes,
12731286
reservoir_builder=reservoir_factory(
@@ -1276,6 +1289,7 @@ def _create_aggregation(
12761289
instrument_aggregation_temporality=(
12771290
AggregationTemporality.DELTA
12781291
),
1292+
boundaries=boundaries,
12791293
start_time_unix_nano=start_time_unix_nano,
12801294
)
12811295

@@ -1347,25 +1361,13 @@ class ExplicitBucketHistogramAggregation(Aggregation):
13471361

13481362
def __init__(
13491363
self,
1350-
boundaries: Sequence[float] = (
1351-
0.0,
1352-
5.0,
1353-
10.0,
1354-
25.0,
1355-
50.0,
1356-
75.0,
1357-
100.0,
1358-
250.0,
1359-
500.0,
1360-
750.0,
1361-
1000.0,
1362-
2500.0,
1363-
5000.0,
1364-
7500.0,
1365-
10000.0,
1366-
),
1364+
boundaries: Optional[Sequence[float]] = None,
13671365
record_min_max: bool = True,
13681366
) -> None:
1367+
if boundaries is None:
1368+
boundaries = (
1369+
_DEFAULT_EXPLICIT_BUCKET_HISTOGRAM_AGGREGATION_BOUNDARIES
1370+
)
13691371
self._boundaries = boundaries
13701372
self._record_min_max = record_min_max
13711373

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from opentelemetry.metrics._internal.instrument import CallbackOptions
3535
from opentelemetry.sdk.metrics._internal.measurement import Measurement
3636
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
37+
from opentelemetry.util.types import MetricsInstrumentAdvisory
3738

3839
_logger = getLogger(__name__)
3940

@@ -219,6 +220,24 @@ def __new__(cls, *args, **kwargs):
219220

220221

221222
class Histogram(_Synchronous, APIHistogram):
223+
def __init__(
224+
self,
225+
name: str,
226+
instrumentation_scope: InstrumentationScope,
227+
measurement_consumer: "opentelemetry.sdk.metrics.MeasurementConsumer",
228+
unit: str = "",
229+
description: str = "",
230+
advisory: MetricsInstrumentAdvisory = None,
231+
):
232+
super().__init__(
233+
name,
234+
unit=unit,
235+
description=description,
236+
instrumentation_scope=instrumentation_scope,
237+
measurement_consumer=measurement_consumer,
238+
)
239+
self.advisory = advisory
240+
222241
def __new__(cls, *args, **kwargs):
223242
if cls is Histogram:
224243
raise TypeError("Histogram must be instantiated via a meter.")

opentelemetry-sdk/tests/metrics/test_aggregation.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# pylint: disable=protected-access
1616

1717
from math import inf
18+
from random import randint
1819
from time import sleep, time_ns
1920
from typing import Union
2021
from unittest import TestCase
@@ -628,6 +629,22 @@ def test_histogram(self):
628629
)
629630
self.assertIsInstance(aggregation, _ExplicitBucketHistogramAggregation)
630631

632+
def test_histogram_with_advisory(self):
633+
boundaries = [randint(0, 1000)]
634+
aggregation = self.default_aggregation._create_aggregation(
635+
_Histogram(
636+
"name",
637+
Mock(),
638+
Mock(),
639+
advisory={"ExplicitBucketBoundaries": boundaries},
640+
),
641+
Mock(),
642+
_default_reservoir_factory,
643+
0,
644+
)
645+
self.assertIsInstance(aggregation, _ExplicitBucketHistogramAggregation)
646+
self.assertEqual(aggregation._boundaries, tuple(boundaries))
647+
631648
def test_gauge(self):
632649
aggregation = self.default_aggregation._create_aggregation(
633650
_Gauge(

opentelemetry-sdk/tests/metrics/test_metrics.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,38 @@ def test_create_histogram(self):
500500
self.assertIsInstance(histogram, Histogram)
501501
self.assertEqual(histogram.name, "name")
502502

503+
def test_create_histogram_with_advisory(self):
504+
histogram = self.meter.create_histogram(
505+
"name",
506+
unit="unit",
507+
description="description",
508+
advisory={"ExplicitBucketBoundaries": [0, 1, 2]},
509+
)
510+
511+
self.assertIsInstance(histogram, Histogram)
512+
self.assertEqual(histogram.name, "name")
513+
self.assertEqual(
514+
histogram.advisory, {"ExplicitBucketBoundaries": [0, 1, 2]}
515+
)
516+
517+
def test_create_histogram_advisory_validation(self):
518+
advisories = [
519+
{"ExplicitBucketBoundaries": None},
520+
{"ExplicitBucketBoundaries": []},
521+
{},
522+
[],
523+
{"ExplicitBucketBoundaries": [1, 2.0, "3"]},
524+
]
525+
for advisory in advisories:
526+
with self.subTest(advisory=advisory):
527+
with self.assertRaises(ValueError):
528+
self.meter.create_histogram(
529+
"name",
530+
unit="unit",
531+
description="description",
532+
advisory=advisory,
533+
)
534+
503535
def test_create_observable_gauge(self):
504536
observable_gauge = self.meter.create_observable_gauge(
505537
"name", callbacks=[Mock()], unit="unit", description="description"

0 commit comments

Comments
 (0)