|
14 | 14 |
|
15 | 15 | from abc import ABC, abstractmethod
|
16 | 16 | from collections import OrderedDict
|
| 17 | +from enum import IntEnum |
17 | 18 | from logging import getLogger
|
18 | 19 | from math import inf
|
| 20 | +from threading import Lock |
| 21 | +from typing import Generic, Optional, Sequence, TypeVar |
19 | 22 |
|
20 |
| -from opentelemetry._metrics.instrument import _Monotonic |
| 23 | +from opentelemetry.sdk._metrics.measurement import Measurement |
| 24 | +from opentelemetry.sdk._metrics.point import Gauge, Histogram, PointT, Sum |
21 | 25 | from opentelemetry.util._time import _time_ns
|
22 | 26 |
|
| 27 | + |
| 28 | +class AggregationTemporality(IntEnum): |
| 29 | + UNSPECIFIED = 0 |
| 30 | + DELTA = 1 |
| 31 | + CUMULATIVE = 2 |
| 32 | + |
| 33 | + |
| 34 | +_PointVarT = TypeVar("_PointVarT", bound=PointT) |
| 35 | + |
23 | 36 | _logger = getLogger(__name__)
|
24 | 37 |
|
25 | 38 |
|
26 |
| -class Aggregation(ABC): |
27 |
| - @property |
28 |
| - def value(self): |
29 |
| - return self._value # pylint: disable=no-member |
| 39 | +class _InstrumentMonotonicityAwareAggregation: |
| 40 | + def __init__(self, instrument_is_monotonic: bool): |
| 41 | + self._instrument_is_monotonic = instrument_is_monotonic |
| 42 | + super().__init__() |
| 43 | + |
| 44 | + |
| 45 | +class Aggregation(ABC, Generic[_PointVarT]): |
| 46 | + def __init__(self): |
| 47 | + self._lock = Lock() |
30 | 48 |
|
31 | 49 | @abstractmethod
|
32 |
| - def aggregate(self, value): |
| 50 | + def aggregate(self, measurement: Measurement) -> None: |
33 | 51 | pass
|
34 | 52 |
|
35 | 53 | @abstractmethod
|
36 |
| - def make_point_and_reset(self): |
| 54 | + def collect(self) -> Optional[_PointVarT]: |
| 55 | + pass |
| 56 | + |
| 57 | + |
| 58 | +class SynchronousSumAggregation( |
| 59 | + _InstrumentMonotonicityAwareAggregation, Aggregation[Sum] |
| 60 | +): |
| 61 | + def __init__(self, instrument_is_monotonic: bool): |
| 62 | + super().__init__(instrument_is_monotonic) |
| 63 | + self._value = 0 |
| 64 | + self._start_time_unix_nano = _time_ns() |
| 65 | + |
| 66 | + def aggregate(self, measurement: Measurement) -> None: |
| 67 | + with self._lock: |
| 68 | + self._value = self._value + measurement.value |
| 69 | + |
| 70 | + def collect(self) -> Optional[Sum]: |
37 | 71 | """
|
38 |
| - Atomically return a point for the current value of the metric and reset the internal state. |
| 72 | + Atomically return a point for the current value of the metric and |
| 73 | + reset the aggregation value. |
39 | 74 | """
|
| 75 | + now = _time_ns() |
40 | 76 |
|
| 77 | + with self._lock: |
| 78 | + value = self._value |
| 79 | + start_time_unix_nano = self._start_time_unix_nano |
41 | 80 |
|
42 |
| -class SumAggregation(Aggregation): |
43 |
| - """ |
44 |
| - This aggregation collects data for the SDK sum metric point. |
45 |
| - """ |
| 81 | + self._value = 0 |
| 82 | + self._start_time_unix_nano = now + 1 |
46 | 83 |
|
47 |
| - def __init__(self, instrument): |
48 |
| - self._value = 0 |
| 84 | + return Sum( |
| 85 | + aggregation_temporality=AggregationTemporality.DELTA, |
| 86 | + is_monotonic=self._instrument_is_monotonic, |
| 87 | + start_time_unix_nano=start_time_unix_nano, |
| 88 | + time_unix_nano=now, |
| 89 | + value=value, |
| 90 | + ) |
49 | 91 |
|
50 |
| - def aggregate(self, value): |
51 |
| - self._value = self._value + value |
52 | 92 |
|
53 |
| - def make_point_and_reset(self): |
54 |
| - pass |
| 93 | +class AsynchronousSumAggregation( |
| 94 | + _InstrumentMonotonicityAwareAggregation, Aggregation[Sum] |
| 95 | +): |
| 96 | + def __init__(self, instrument_is_monotonic: bool): |
| 97 | + super().__init__(instrument_is_monotonic) |
| 98 | + self._value = None |
| 99 | + self._start_time_unix_nano = _time_ns() |
55 | 100 |
|
| 101 | + def aggregate(self, measurement: Measurement) -> None: |
| 102 | + with self._lock: |
| 103 | + self._value = measurement.value |
56 | 104 |
|
57 |
| -class LastValueAggregation(Aggregation): |
| 105 | + def collect(self) -> Optional[Sum]: |
| 106 | + """ |
| 107 | + Atomically return a point for the current value of the metric. |
| 108 | + """ |
| 109 | + if self._value is None: |
| 110 | + return None |
| 111 | + |
| 112 | + return Sum( |
| 113 | + start_time_unix_nano=self._start_time_unix_nano, |
| 114 | + time_unix_nano=_time_ns(), |
| 115 | + value=self._value, |
| 116 | + aggregation_temporality=AggregationTemporality.CUMULATIVE, |
| 117 | + is_monotonic=self._instrument_is_monotonic, |
| 118 | + ) |
58 | 119 |
|
59 |
| - """ |
60 |
| - This aggregation collects data for the SDK sum metric point. |
61 |
| - """ |
62 | 120 |
|
63 |
| - def __init__(self, instrument): |
| 121 | +class LastValueAggregation(Aggregation[Gauge]): |
| 122 | + def __init__(self): |
| 123 | + super().__init__() |
64 | 124 | self._value = None
|
65 |
| - self._timestamp = _time_ns() |
66 | 125 |
|
67 |
| - def aggregate(self, value): |
68 |
| - self._value = value |
69 |
| - self._timestamp = _time_ns() |
70 |
| - |
71 |
| - def make_point_and_reset(self): |
72 |
| - pass |
| 126 | + def aggregate(self, measurement: Measurement): |
| 127 | + with self._lock: |
| 128 | + self._value = measurement.value |
73 | 129 |
|
| 130 | + def collect(self) -> Optional[Gauge]: |
| 131 | + """ |
| 132 | + Atomically return a point for the current value of the metric. |
| 133 | + """ |
| 134 | + if self._value is None: |
| 135 | + return None |
74 | 136 |
|
75 |
| -class ExplicitBucketHistogramAggregation(Aggregation): |
| 137 | + return Gauge( |
| 138 | + time_unix_nano=_time_ns(), |
| 139 | + value=self._value, |
| 140 | + ) |
76 | 141 |
|
77 |
| - """ |
78 |
| - This aggregation collects data for the SDK sum metric point. |
79 |
| - """ |
80 | 142 |
|
| 143 | +class ExplicitBucketHistogramAggregation(Aggregation[Histogram]): |
81 | 144 | def __init__(
|
82 | 145 | self,
|
83 |
| - instrument, |
84 |
| - *args, |
85 |
| - boundaries=(0, 5, 10, 25, 50, 75, 100, 250, 500, 1000), |
86 |
| - record_min_max=True, |
| 146 | + boundaries: Sequence[int] = ( |
| 147 | + 0, |
| 148 | + 5, |
| 149 | + 10, |
| 150 | + 25, |
| 151 | + 50, |
| 152 | + 75, |
| 153 | + 100, |
| 154 | + 250, |
| 155 | + 500, |
| 156 | + 1000, |
| 157 | + ), |
| 158 | + record_min_max: bool = True, |
87 | 159 | ):
|
88 | 160 | super().__init__()
|
89 | 161 | self._value = OrderedDict([(key, 0) for key in (*boundaries, inf)])
|
90 | 162 | self._min = inf
|
91 | 163 | self._max = -inf
|
92 | 164 | self._sum = 0
|
93 |
| - self._instrument = instrument |
94 | 165 | self._record_min_max = record_min_max
|
| 166 | + self._start_time_unix_nano = _time_ns() |
| 167 | + self._boundaries = boundaries |
95 | 168 |
|
96 |
| - @property |
97 |
| - def min(self): |
98 |
| - if not self._record_min_max: |
99 |
| - _logger.warning("Min is not being recorded") |
100 |
| - |
101 |
| - return self._min |
| 169 | + def aggregate(self, measurement: Measurement) -> None: |
102 | 170 |
|
103 |
| - @property |
104 |
| - def max(self): |
105 |
| - if not self._record_min_max: |
106 |
| - _logger.warning("Max is not being recorded") |
| 171 | + value = measurement.value |
107 | 172 |
|
108 |
| - return self._max |
109 |
| - |
110 |
| - @property |
111 |
| - def sum(self): |
112 |
| - if isinstance(self._instrument, _Monotonic): |
113 |
| - return self._sum |
114 |
| - |
115 |
| - _logger.warning( |
116 |
| - "Sum is not filled out when the associated " |
117 |
| - "instrument is not monotonic" |
118 |
| - ) |
119 |
| - return None |
120 |
| - |
121 |
| - def aggregate(self, value): |
122 | 173 | if self._record_min_max:
|
123 | 174 | self._min = min(self._min, value)
|
124 | 175 | self._max = max(self._max, value)
|
125 | 176 |
|
126 |
| - if isinstance(self._instrument, _Monotonic): |
127 |
| - self._sum += value |
| 177 | + self._sum += value |
128 | 178 |
|
129 | 179 | for key in self._value.keys():
|
130 | 180 |
|
131 | 181 | if value < key:
|
132 |
| - self._value[key] = self._value[key] + value |
| 182 | + self._value[key] = self._value[key] + 1 |
133 | 183 |
|
134 | 184 | break
|
135 | 185 |
|
136 |
| - def make_point_and_reset(self): |
137 |
| - pass |
| 186 | + def collect(self) -> Optional[Histogram]: |
| 187 | + """ |
| 188 | + Atomically return a point for the current value of the metric. |
| 189 | + """ |
| 190 | + now = _time_ns() |
| 191 | + |
| 192 | + with self._lock: |
| 193 | + value = self._value |
| 194 | + start_time_unix_nano = self._start_time_unix_nano |
| 195 | + |
| 196 | + self._value = OrderedDict( |
| 197 | + [(key, 0) for key in (*self._boundaries, inf)] |
| 198 | + ) |
| 199 | + self._start_time_unix_nano = now + 1 |
| 200 | + |
| 201 | + return Histogram( |
| 202 | + start_time_unix_nano=start_time_unix_nano, |
| 203 | + time_unix_nano=now, |
| 204 | + bucket_counts=tuple(value.values()), |
| 205 | + explicit_bounds=self._boundaries, |
| 206 | + aggregation_temporality=AggregationTemporality.DELTA, |
| 207 | + ) |
0 commit comments