Erster Stand der Hintergrundprozesse, die auf der Synology unter /volume1/homes/wagner/SolarManager laufen: der Manager selbst, die Sammler je Geraet, die MQTT-Bruecke, der Wecker und - neu hinzugezogen - der AutoAction-Runner, der als Hintergrundprozess hierher gehoert und nicht ins Web-Verzeichnis. Zugangsdaten stehen nicht mehr im Quelltext, sondern in config.ini, die nicht mit eingecheckt wird. Vorlage ist config.ini.example, gelesen wird sie von konfig.py. Betroffen waren solarManager.py (Datenbank und Wattpilot), zeit.py, gatherWaterData.py, wecker.py und skoda_testdaten.py, das sich das Passwort bisher aus dem Quelltext eines anderen Moduls herausgesucht hat. Die Kia-Anbindung ist mit dem Fahrzeug entfallen: kiaTest.py, gatherCarData.py und hyundai_kia_connect_api sind nicht mehr dabei, ebenso gatherInverterData.py, auf das nur noch eine auskommentierte Zeile zeigte. Die mitgelieferten Bibliotheken bleiben im Repository - die NAS hat kein pip, sie muessen neben den Skripten liegen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1035 lines
34 KiB
Python
1035 lines
34 KiB
Python
# Copyright The OpenTelemetry Authors
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# pylint: disable=too-many-lines
|
|
|
|
from abc import ABC, abstractmethod
|
|
from bisect import bisect_left
|
|
from enum import IntEnum
|
|
from logging import getLogger
|
|
from math import inf
|
|
from threading import Lock
|
|
from typing import Generic, List, Optional, Sequence, TypeVar
|
|
|
|
from mysql.opentelemetry.metrics import (
|
|
Asynchronous,
|
|
Counter,
|
|
Histogram,
|
|
Instrument,
|
|
ObservableCounter,
|
|
ObservableGauge,
|
|
ObservableUpDownCounter,
|
|
Synchronous,
|
|
UpDownCounter,
|
|
)
|
|
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.buckets import (
|
|
Buckets,
|
|
)
|
|
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.exponent_mapping import (
|
|
ExponentMapping,
|
|
)
|
|
from mysql.opentelemetry.sdk.metrics._internal.exponential_histogram.mapping.logarithm_mapping import (
|
|
LogarithmMapping,
|
|
)
|
|
from mysql.opentelemetry.sdk.metrics._internal.measurement import Measurement
|
|
from mysql.opentelemetry.sdk.metrics._internal.point import (
|
|
Buckets as BucketsPoint,
|
|
ExponentialHistogramDataPoint,
|
|
Gauge,
|
|
Histogram as HistogramPoint,
|
|
HistogramDataPoint,
|
|
NumberDataPoint,
|
|
Sum,
|
|
)
|
|
from mysql.opentelemetry.util.types import Attributes
|
|
|
|
_DataPointVarT = TypeVar("_DataPointVarT", NumberDataPoint, HistogramDataPoint)
|
|
|
|
_logger = getLogger(__name__)
|
|
|
|
|
|
class AggregationTemporality(IntEnum):
|
|
"""
|
|
The temporality to use when aggregating data.
|
|
|
|
Can be one of the following values:
|
|
"""
|
|
|
|
UNSPECIFIED = 0
|
|
DELTA = 1
|
|
CUMULATIVE = 2
|
|
|
|
|
|
class _Aggregation(ABC, Generic[_DataPointVarT]):
|
|
def __init__(self, attributes: Attributes):
|
|
self._lock = Lock()
|
|
self._attributes = attributes
|
|
self._previous_point = None
|
|
|
|
@abstractmethod
|
|
def aggregate(self, measurement: Measurement) -> None:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def collect(
|
|
self,
|
|
aggregation_temporality: AggregationTemporality,
|
|
collection_start_nano: int,
|
|
) -> Optional[_DataPointVarT]:
|
|
pass
|
|
|
|
|
|
class _DropAggregation(_Aggregation):
|
|
def aggregate(self, measurement: Measurement) -> None:
|
|
pass
|
|
|
|
def collect(
|
|
self,
|
|
aggregation_temporality: AggregationTemporality,
|
|
collection_start_nano: int,
|
|
) -> Optional[_DataPointVarT]:
|
|
pass
|
|
|
|
|
|
class _SumAggregation(_Aggregation[Sum]):
|
|
def __init__(
|
|
self,
|
|
attributes: Attributes,
|
|
instrument_is_monotonic: bool,
|
|
instrument_temporality: AggregationTemporality,
|
|
start_time_unix_nano: int,
|
|
):
|
|
super().__init__(attributes)
|
|
|
|
self._start_time_unix_nano = start_time_unix_nano
|
|
self._instrument_temporality = instrument_temporality
|
|
self._instrument_is_monotonic = instrument_is_monotonic
|
|
|
|
if self._instrument_temporality is AggregationTemporality.DELTA:
|
|
self._value = 0
|
|
else:
|
|
self._value = None
|
|
|
|
def aggregate(self, measurement: Measurement) -> None:
|
|
with self._lock:
|
|
if self._value is None:
|
|
self._value = 0
|
|
self._value = self._value + measurement.value
|
|
|
|
def collect(
|
|
self,
|
|
aggregation_temporality: AggregationTemporality,
|
|
collection_start_nano: int,
|
|
) -> Optional[NumberDataPoint]:
|
|
"""
|
|
Atomically return a point for the current value of the metric and
|
|
reset the aggregation value.
|
|
"""
|
|
if self._instrument_temporality is AggregationTemporality.DELTA:
|
|
with self._lock:
|
|
value = self._value
|
|
start_time_unix_nano = self._start_time_unix_nano
|
|
|
|
self._value = 0
|
|
self._start_time_unix_nano = collection_start_nano
|
|
|
|
else:
|
|
with self._lock:
|
|
if self._value is None:
|
|
return None
|
|
value = self._value
|
|
self._value = None
|
|
start_time_unix_nano = self._start_time_unix_nano
|
|
|
|
current_point = NumberDataPoint(
|
|
attributes=self._attributes,
|
|
start_time_unix_nano=start_time_unix_nano,
|
|
time_unix_nano=collection_start_nano,
|
|
value=value,
|
|
)
|
|
|
|
if self._previous_point is None or (
|
|
self._instrument_temporality is aggregation_temporality
|
|
):
|
|
# Output DELTA for a synchronous instrument
|
|
# Output CUMULATIVE for an asynchronous instrument
|
|
self._previous_point = current_point
|
|
return current_point
|
|
|
|
if aggregation_temporality is AggregationTemporality.DELTA:
|
|
# Output temporality DELTA for an asynchronous instrument
|
|
value = current_point.value - self._previous_point.value
|
|
output_start_time_unix_nano = self._previous_point.time_unix_nano
|
|
|
|
else:
|
|
# Output CUMULATIVE for a synchronous instrument
|
|
value = current_point.value + self._previous_point.value
|
|
output_start_time_unix_nano = self._previous_point.start_time_unix_nano
|
|
|
|
current_point = NumberDataPoint(
|
|
attributes=self._attributes,
|
|
start_time_unix_nano=output_start_time_unix_nano,
|
|
time_unix_nano=current_point.time_unix_nano,
|
|
value=value,
|
|
)
|
|
|
|
self._previous_point = current_point
|
|
return current_point
|
|
|
|
|
|
class _LastValueAggregation(_Aggregation[Gauge]):
|
|
def __init__(self, attributes: Attributes):
|
|
super().__init__(attributes)
|
|
self._value = None
|
|
|
|
def aggregate(self, measurement: Measurement):
|
|
with self._lock:
|
|
self._value = measurement.value
|
|
|
|
def collect(
|
|
self,
|
|
aggregation_temporality: AggregationTemporality,
|
|
collection_start_nano: int,
|
|
) -> Optional[_DataPointVarT]:
|
|
"""
|
|
Atomically return a point for the current value of the metric.
|
|
"""
|
|
with self._lock:
|
|
if self._value is None:
|
|
return None
|
|
value = self._value
|
|
self._value = None
|
|
|
|
return NumberDataPoint(
|
|
attributes=self._attributes,
|
|
start_time_unix_nano=0,
|
|
time_unix_nano=collection_start_nano,
|
|
value=value,
|
|
)
|
|
|
|
|
|
class _ExplicitBucketHistogramAggregation(_Aggregation[HistogramPoint]):
|
|
def __init__(
|
|
self,
|
|
attributes: Attributes,
|
|
start_time_unix_nano: int,
|
|
boundaries: Sequence[float] = (
|
|
0.0,
|
|
5.0,
|
|
10.0,
|
|
25.0,
|
|
50.0,
|
|
75.0,
|
|
100.0,
|
|
250.0,
|
|
500.0,
|
|
750.0,
|
|
1000.0,
|
|
2500.0,
|
|
5000.0,
|
|
7500.0,
|
|
10000.0,
|
|
),
|
|
record_min_max: bool = True,
|
|
):
|
|
super().__init__(attributes)
|
|
self._boundaries = tuple(boundaries)
|
|
self._bucket_counts = self._get_empty_bucket_counts()
|
|
self._min = inf
|
|
self._max = -inf
|
|
self._sum = 0
|
|
self._record_min_max = record_min_max
|
|
self._start_time_unix_nano = start_time_unix_nano
|
|
# It is assumed that the "natural" aggregation temporality for a
|
|
# Histogram instrument is DELTA, like the "natural" aggregation
|
|
# temporality for a Counter is DELTA and the "natural" aggregation
|
|
# temporality for an ObservableCounter is CUMULATIVE.
|
|
self._instrument_temporality = AggregationTemporality.DELTA
|
|
|
|
def _get_empty_bucket_counts(self) -> List[int]:
|
|
return [0] * (len(self._boundaries) + 1)
|
|
|
|
def aggregate(self, measurement: Measurement) -> None:
|
|
value = measurement.value
|
|
|
|
if self._record_min_max:
|
|
self._min = min(self._min, value)
|
|
self._max = max(self._max, value)
|
|
|
|
self._sum += value
|
|
|
|
self._bucket_counts[bisect_left(self._boundaries, value)] += 1
|
|
|
|
def collect(
|
|
self,
|
|
aggregation_temporality: AggregationTemporality,
|
|
collection_start_nano: int,
|
|
) -> Optional[_DataPointVarT]:
|
|
"""
|
|
Atomically return a point for the current value of the metric.
|
|
"""
|
|
with self._lock:
|
|
if not any(self._bucket_counts):
|
|
return None
|
|
|
|
bucket_counts = self._bucket_counts
|
|
start_time_unix_nano = self._start_time_unix_nano
|
|
sum_ = self._sum
|
|
max_ = self._max
|
|
min_ = self._min
|
|
|
|
self._bucket_counts = self._get_empty_bucket_counts()
|
|
self._start_time_unix_nano = collection_start_nano
|
|
self._sum = 0
|
|
self._min = inf
|
|
self._max = -inf
|
|
|
|
current_point = HistogramDataPoint(
|
|
attributes=self._attributes,
|
|
start_time_unix_nano=start_time_unix_nano,
|
|
time_unix_nano=collection_start_nano,
|
|
count=sum(bucket_counts),
|
|
sum=sum_,
|
|
bucket_counts=tuple(bucket_counts),
|
|
explicit_bounds=self._boundaries,
|
|
min=min_,
|
|
max=max_,
|
|
)
|
|
|
|
if self._previous_point is None or (
|
|
self._instrument_temporality is aggregation_temporality
|
|
):
|
|
self._previous_point = current_point
|
|
return current_point
|
|
|
|
max_ = current_point.max
|
|
min_ = current_point.min
|
|
|
|
if aggregation_temporality is AggregationTemporality.CUMULATIVE:
|
|
start_time_unix_nano = self._previous_point.start_time_unix_nano
|
|
sum_ = current_point.sum + self._previous_point.sum
|
|
# Only update min/max on delta -> cumulative
|
|
max_ = max(current_point.max, self._previous_point.max)
|
|
min_ = min(current_point.min, self._previous_point.min)
|
|
bucket_counts = [
|
|
curr_count + prev_count
|
|
for curr_count, prev_count in zip(
|
|
current_point.bucket_counts,
|
|
self._previous_point.bucket_counts,
|
|
)
|
|
]
|
|
else:
|
|
start_time_unix_nano = self._previous_point.time_unix_nano
|
|
sum_ = current_point.sum - self._previous_point.sum
|
|
bucket_counts = [
|
|
curr_count - prev_count
|
|
for curr_count, prev_count in zip(
|
|
current_point.bucket_counts,
|
|
self._previous_point.bucket_counts,
|
|
)
|
|
]
|
|
|
|
current_point = HistogramDataPoint(
|
|
attributes=self._attributes,
|
|
start_time_unix_nano=start_time_unix_nano,
|
|
time_unix_nano=current_point.time_unix_nano,
|
|
count=sum(bucket_counts),
|
|
sum=sum_,
|
|
bucket_counts=tuple(bucket_counts),
|
|
explicit_bounds=current_point.explicit_bounds,
|
|
min=min_,
|
|
max=max_,
|
|
)
|
|
self._previous_point = current_point
|
|
return current_point
|
|
|
|
|
|
# pylint: disable=protected-access
|
|
class _ExponentialBucketHistogramAggregation(_Aggregation[HistogramPoint]):
|
|
# _min_max_size and _max_max_size are the smallest and largest values
|
|
# the max_size parameter may have, respectively.
|
|
|
|
# _min_max_size is is the smallest reasonable value which is small enough
|
|
# to contain the entire normal floating point range at the minimum scale.
|
|
_min_max_size = 2
|
|
|
|
# _max_max_size is an arbitrary limit meant to limit accidental creation of
|
|
# giant exponential bucket histograms.
|
|
_max_max_size = 16384
|
|
|
|
def __init__(
|
|
self,
|
|
attributes: Attributes,
|
|
start_time_unix_nano: int,
|
|
# This is the default maximum number of buckets per positive or
|
|
# negative number range. The value 160 is specified by mysql.OpenTelemetry.
|
|
# See the derivation here:
|
|
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exponential-bucket-histogram-aggregation)
|
|
max_size: int = 160,
|
|
):
|
|
super().__init__(attributes)
|
|
# max_size is the maximum capacity of the positive and negative
|
|
# buckets.
|
|
if max_size < self._min_max_size:
|
|
raise ValueError(
|
|
f"Buckets max size {max_size} is smaller than "
|
|
"minimum max size {self._min_max_size}"
|
|
)
|
|
|
|
if max_size > self._max_max_size:
|
|
raise ValueError(
|
|
f"Buckets max size {max_size} is larger than "
|
|
"maximum max size {self._max_max_size}"
|
|
)
|
|
|
|
self._max_size = max_size
|
|
|
|
# _sum is the sum of all the values aggregated by this aggregator.
|
|
self._sum = 0
|
|
|
|
# _count is the count of all calls to aggregate.
|
|
self._count = 0
|
|
|
|
# _zero_count is the count of all the calls to aggregate when the value
|
|
# to be aggregated is exactly 0.
|
|
self._zero_count = 0
|
|
|
|
# _min is the smallest value aggregated by this aggregator.
|
|
self._min = inf
|
|
|
|
# _max is the smallest value aggregated by this aggregator.
|
|
self._max = -inf
|
|
|
|
# _positive holds the positive values.
|
|
self._positive = Buckets()
|
|
|
|
# _negative holds the negative values by their absolute value.
|
|
self._negative = Buckets()
|
|
|
|
# _mapping corresponds to the current scale, is shared by both the
|
|
# positive and negative buckets.
|
|
self._mapping = LogarithmMapping(LogarithmMapping._max_scale)
|
|
|
|
self._instrument_temporality = AggregationTemporality.DELTA
|
|
self._start_time_unix_nano = start_time_unix_nano
|
|
|
|
self._previous_scale = None
|
|
self._previous_start_time_unix_nano = None
|
|
self._previous_sum = None
|
|
self._previous_max = None
|
|
self._previous_min = None
|
|
self._previous_positive = None
|
|
self._previous_negative = None
|
|
|
|
def aggregate(self, measurement: Measurement) -> None:
|
|
# pylint: disable=too-many-branches,too-many-statements, too-many-locals
|
|
|
|
with self._lock:
|
|
value = measurement.value
|
|
|
|
# 0. Set the following attributes:
|
|
# _min
|
|
# _max
|
|
# _count
|
|
# _zero_count
|
|
# _sum
|
|
if value < self._min:
|
|
self._min = value
|
|
|
|
if value > self._max:
|
|
self._max = value
|
|
|
|
self._count += 1
|
|
|
|
if value == 0:
|
|
self._zero_count += 1
|
|
# No need to do anything else if value is zero, just increment the
|
|
# zero count.
|
|
return
|
|
|
|
self._sum += value
|
|
|
|
# 1. Use the positive buckets for positive values and the negative
|
|
# buckets for negative values.
|
|
if value > 0:
|
|
buckets = self._positive
|
|
|
|
else:
|
|
# Both exponential and logarithm mappings use only positive values
|
|
# so the absolute value is used here.
|
|
value = -value
|
|
buckets = self._negative
|
|
|
|
# 2. Compute the index for the value at the current scale.
|
|
index = self._mapping.map_to_index(value)
|
|
|
|
# IncrementIndexBy starts here
|
|
|
|
# 3. Determine if a change of scale is needed.
|
|
is_rescaling_needed = False
|
|
|
|
if len(buckets) == 0:
|
|
buckets.index_start = index
|
|
buckets.index_end = index
|
|
buckets.index_base = index
|
|
|
|
elif (
|
|
index < buckets.index_start
|
|
and (buckets.index_end - index) >= self._max_size
|
|
):
|
|
is_rescaling_needed = True
|
|
low = index
|
|
high = buckets.index_end
|
|
|
|
elif (
|
|
index > buckets.index_end
|
|
and (index - buckets.index_start) >= self._max_size
|
|
):
|
|
is_rescaling_needed = True
|
|
low = buckets.index_start
|
|
high = index
|
|
|
|
# 4. Rescale the mapping if needed.
|
|
if is_rescaling_needed:
|
|
self._downscale(
|
|
self._get_scale_change(low, high),
|
|
self._positive,
|
|
self._negative,
|
|
)
|
|
|
|
index = self._mapping.map_to_index(value)
|
|
|
|
# 5. If the index is outside
|
|
# [buckets.index_start, buckets.index_end] readjust the buckets
|
|
# boundaries or add more buckets.
|
|
if index < buckets.index_start:
|
|
span = buckets.index_end - index
|
|
|
|
if span >= len(buckets.counts):
|
|
buckets.grow(span + 1, self._max_size)
|
|
|
|
buckets.index_start = index
|
|
|
|
elif index > buckets.index_end:
|
|
span = index - buckets.index_start
|
|
|
|
if span >= len(buckets.counts):
|
|
buckets.grow(span + 1, self._max_size)
|
|
|
|
buckets.index_end = index
|
|
|
|
# 6. Compute the index of the bucket to be incremented.
|
|
bucket_index = index - buckets.index_base
|
|
|
|
if bucket_index < 0:
|
|
bucket_index += len(buckets.counts)
|
|
|
|
# 7. Increment the bucket.
|
|
buckets.increment_bucket(bucket_index)
|
|
|
|
def collect(
|
|
self,
|
|
aggregation_temporality: AggregationTemporality,
|
|
collection_start_nano: int,
|
|
) -> Optional[_DataPointVarT]:
|
|
"""
|
|
Atomically return a point for the current value of the metric.
|
|
"""
|
|
# pylint: disable=too-many-statements, too-many-locals
|
|
|
|
with self._lock:
|
|
if self._count == 0:
|
|
return None
|
|
|
|
current_negative = self._negative
|
|
current_positive = self._positive
|
|
current_zero_count = self._zero_count
|
|
current_count = self._count
|
|
current_start_time_unix_nano = self._start_time_unix_nano
|
|
current_sum = self._sum
|
|
current_max = self._max
|
|
if current_max == -inf:
|
|
current_max = None
|
|
current_min = self._min
|
|
if current_min == inf:
|
|
current_min = None
|
|
|
|
if self._count == self._zero_count:
|
|
current_scale = 0
|
|
|
|
else:
|
|
current_scale = self._mapping.scale
|
|
|
|
self._negative = Buckets()
|
|
self._positive = Buckets()
|
|
self._start_time_unix_nano = collection_start_nano
|
|
self._sum = 0
|
|
self._count = 0
|
|
self._zero_count = 0
|
|
self._min = inf
|
|
self._max = -inf
|
|
|
|
current_point = ExponentialHistogramDataPoint(
|
|
attributes=self._attributes,
|
|
start_time_unix_nano=current_start_time_unix_nano,
|
|
time_unix_nano=collection_start_nano,
|
|
count=current_count,
|
|
sum=current_sum,
|
|
scale=current_scale,
|
|
zero_count=current_zero_count,
|
|
positive=BucketsPoint(
|
|
offset=current_positive.offset,
|
|
bucket_counts=current_positive.counts,
|
|
),
|
|
negative=BucketsPoint(
|
|
offset=current_negative.offset,
|
|
bucket_counts=current_negative.counts,
|
|
),
|
|
# FIXME: Find the right value for flags
|
|
flags=0,
|
|
min=current_min,
|
|
max=current_max,
|
|
)
|
|
|
|
if self._previous_scale is None or (
|
|
self._instrument_temporality is aggregation_temporality
|
|
):
|
|
self._previous_scale = current_scale
|
|
self._previous_start_time_unix_nano = current_start_time_unix_nano
|
|
self._previous_max = current_max
|
|
self._previous_min = current_min
|
|
self._previous_sum = current_sum
|
|
self._previous_positive = current_positive
|
|
self._previous_negative = current_negative
|
|
|
|
return current_point
|
|
|
|
min_scale = min(self._previous_scale, current_scale)
|
|
|
|
low_positive, high_positive = self._get_low_high_previous_current(
|
|
self._previous_positive, current_positive, min_scale
|
|
)
|
|
low_negative, high_negative = self._get_low_high_previous_current(
|
|
self._previous_negative, current_negative, min_scale
|
|
)
|
|
|
|
min_scale = min(
|
|
min_scale - self._get_scale_change(low_positive, high_positive),
|
|
min_scale - self._get_scale_change(low_negative, high_negative),
|
|
)
|
|
|
|
# FIXME Go implementation checks if the histogram (not the mapping
|
|
# but the histogram) has a count larger than zero, if not, scale
|
|
# (the histogram scale) would be zero. See exponential.go 191
|
|
self._downscale(
|
|
self._mapping.scale - min_scale,
|
|
self._previous_positive,
|
|
self._previous_negative,
|
|
)
|
|
|
|
if aggregation_temporality is AggregationTemporality.CUMULATIVE:
|
|
start_time_unix_nano = self._previous_start_time_unix_nano
|
|
sum_ = current_sum + self._previous_sum
|
|
# Only update min/max on delta -> cumulative
|
|
max_ = max(current_max, self._previous_max)
|
|
min_ = min(current_min, self._previous_min)
|
|
|
|
self._merge(
|
|
self._previous_positive,
|
|
current_positive,
|
|
current_scale,
|
|
min_scale,
|
|
aggregation_temporality,
|
|
)
|
|
self._merge(
|
|
self._previous_negative,
|
|
current_negative,
|
|
current_scale,
|
|
min_scale,
|
|
aggregation_temporality,
|
|
)
|
|
|
|
else:
|
|
start_time_unix_nano = self._previous_start_time_unix_nano
|
|
sum_ = current_sum - self._previous_sum
|
|
max_ = current_max
|
|
min_ = current_min
|
|
|
|
self._merge(
|
|
self._previous_positive,
|
|
current_positive,
|
|
current_scale,
|
|
min_scale,
|
|
aggregation_temporality,
|
|
)
|
|
self._merge(
|
|
self._previous_negative,
|
|
current_negative,
|
|
current_scale,
|
|
min_scale,
|
|
aggregation_temporality,
|
|
)
|
|
|
|
current_point = ExponentialHistogramDataPoint(
|
|
attributes=self._attributes,
|
|
start_time_unix_nano=start_time_unix_nano,
|
|
time_unix_nano=collection_start_nano,
|
|
count=current_count,
|
|
sum=sum_,
|
|
scale=current_scale,
|
|
zero_count=current_zero_count,
|
|
positive=BucketsPoint(
|
|
offset=current_positive.offset,
|
|
bucket_counts=current_positive.counts,
|
|
),
|
|
negative=BucketsPoint(
|
|
offset=current_negative.offset,
|
|
bucket_counts=current_negative.counts,
|
|
),
|
|
# FIXME: Find the right value for flags
|
|
flags=0,
|
|
min=min_,
|
|
max=max_,
|
|
)
|
|
|
|
self._previous_scale = current_scale
|
|
self._previous_positive = current_positive
|
|
self._previous_negative = current_negative
|
|
self._previous_start_time_unix_nano = current_start_time_unix_nano
|
|
self._previous_sum = current_sum
|
|
|
|
return current_point
|
|
|
|
def _get_low_high_previous_current(
|
|
self, previous_point_buckets, current_point_buckets, min_scale
|
|
):
|
|
(previous_point_low, previous_point_high) = self._get_low_high(
|
|
previous_point_buckets, min_scale
|
|
)
|
|
(current_point_low, current_point_high) = self._get_low_high(
|
|
current_point_buckets, min_scale
|
|
)
|
|
|
|
if current_point_low > current_point_high:
|
|
low = previous_point_low
|
|
high = previous_point_high
|
|
|
|
elif previous_point_low > previous_point_high:
|
|
low = current_point_low
|
|
high = current_point_high
|
|
|
|
else:
|
|
low = min(previous_point_low, current_point_low)
|
|
high = max(previous_point_high, current_point_high)
|
|
|
|
return low, high
|
|
|
|
def _get_low_high(self, buckets, min_scale):
|
|
if buckets.counts == [0]:
|
|
return 0, -1
|
|
|
|
shift = self._mapping._scale - min_scale
|
|
|
|
return buckets.index_start >> shift, buckets.index_end >> shift
|
|
|
|
def _get_scale_change(self, low, high):
|
|
change = 0
|
|
|
|
while high - low >= self._max_size:
|
|
high = high >> 1
|
|
low = low >> 1
|
|
|
|
change += 1
|
|
|
|
return change
|
|
|
|
def _downscale(self, change: int, positive, negative):
|
|
if change == 0:
|
|
return
|
|
|
|
if change < 0:
|
|
raise Exception("Invalid change of scale")
|
|
|
|
new_scale = self._mapping.scale - change
|
|
|
|
positive.downscale(change)
|
|
negative.downscale(change)
|
|
|
|
if new_scale <= 0:
|
|
mapping = ExponentMapping(new_scale)
|
|
else:
|
|
mapping = LogarithmMapping(new_scale)
|
|
|
|
self._mapping = mapping
|
|
|
|
def _merge(
|
|
self,
|
|
previous_buckets,
|
|
current_buckets,
|
|
current_scale,
|
|
min_scale,
|
|
aggregation_temporality,
|
|
):
|
|
current_change = current_scale - min_scale
|
|
|
|
for current_bucket_index, current_bucket in enumerate(current_buckets.counts):
|
|
if current_bucket == 0:
|
|
continue
|
|
|
|
# Not considering the case where len(previous_buckets) == 0. This
|
|
# would not happen because self._previous_point is only assigned to
|
|
# an ExponentialHistogramDataPoint object if self._count != 0.
|
|
|
|
index = (current_buckets.offset + current_bucket_index) >> current_change
|
|
|
|
if index < previous_buckets.index_start:
|
|
span = previous_buckets.index_end - index
|
|
|
|
if span >= self._max_size:
|
|
raise Exception("Incorrect merge scale")
|
|
|
|
if span >= len(previous_buckets.counts):
|
|
previous_buckets.grow(span + 1, self._max_size)
|
|
|
|
previous_buckets.index_start = index
|
|
|
|
if index > previous_buckets.index_end:
|
|
span = index - previous_buckets.index_end
|
|
|
|
if span >= self._max_size:
|
|
raise Exception("Incorrect merge scale")
|
|
|
|
if span >= len(previous_buckets.counts):
|
|
previous_buckets.grow(span + 1, self._max_size)
|
|
|
|
previous_buckets.index_end = index
|
|
|
|
bucket_index = index - previous_buckets.index_base
|
|
|
|
if bucket_index < 0:
|
|
bucket_index += len(previous_buckets.counts)
|
|
|
|
if aggregation_temporality is AggregationTemporality.DELTA:
|
|
current_bucket = -current_bucket
|
|
|
|
previous_buckets.increment_bucket(bucket_index, increment=current_bucket)
|
|
|
|
|
|
class Aggregation(ABC):
|
|
"""
|
|
Base class for all aggregation types.
|
|
"""
|
|
|
|
@abstractmethod
|
|
def _create_aggregation(
|
|
self,
|
|
instrument: Instrument,
|
|
attributes: Attributes,
|
|
start_time_unix_nano: int,
|
|
) -> _Aggregation:
|
|
"""Creates an aggregation"""
|
|
|
|
|
|
class DefaultAggregation(Aggregation):
|
|
"""
|
|
The default aggregation to be used in a `View`.
|
|
|
|
This aggregation will create an actual aggregation depending on the
|
|
instrument type, as specified next:
|
|
|
|
==================================================== ====================================
|
|
Instrument Aggregation
|
|
==================================================== ====================================
|
|
`mysql.opentelemetry.sdk.metrics.Counter` `SumAggregation`
|
|
`mysql.opentelemetry.sdk.metrics.UpDownCounter` `SumAggregation`
|
|
`mysql.opentelemetry.sdk.metrics.ObservableCounter` `SumAggregation`
|
|
`mysql.opentelemetry.sdk.metrics.ObservableUpDownCounter` `SumAggregation`
|
|
`mysql.opentelemetry.sdk.metrics.Histogram` `ExplicitBucketHistogramAggregation`
|
|
`mysql.opentelemetry.sdk.metrics.ObservableGauge` `LastValueAggregation`
|
|
==================================================== ====================================
|
|
"""
|
|
|
|
def _create_aggregation(
|
|
self,
|
|
instrument: Instrument,
|
|
attributes: Attributes,
|
|
start_time_unix_nano: int,
|
|
) -> _Aggregation:
|
|
# pylint: disable=too-many-return-statements
|
|
if isinstance(instrument, Counter):
|
|
return _SumAggregation(
|
|
attributes,
|
|
instrument_is_monotonic=True,
|
|
instrument_temporality=AggregationTemporality.DELTA,
|
|
start_time_unix_nano=start_time_unix_nano,
|
|
)
|
|
if isinstance(instrument, UpDownCounter):
|
|
return _SumAggregation(
|
|
attributes,
|
|
instrument_is_monotonic=False,
|
|
instrument_temporality=AggregationTemporality.DELTA,
|
|
start_time_unix_nano=start_time_unix_nano,
|
|
)
|
|
|
|
if isinstance(instrument, ObservableCounter):
|
|
return _SumAggregation(
|
|
attributes,
|
|
instrument_is_monotonic=True,
|
|
instrument_temporality=AggregationTemporality.CUMULATIVE,
|
|
start_time_unix_nano=start_time_unix_nano,
|
|
)
|
|
|
|
if isinstance(instrument, ObservableUpDownCounter):
|
|
return _SumAggregation(
|
|
attributes,
|
|
instrument_is_monotonic=False,
|
|
instrument_temporality=AggregationTemporality.CUMULATIVE,
|
|
start_time_unix_nano=start_time_unix_nano,
|
|
)
|
|
|
|
if isinstance(instrument, Histogram):
|
|
return _ExplicitBucketHistogramAggregation(attributes, start_time_unix_nano)
|
|
|
|
if isinstance(instrument, ObservableGauge):
|
|
return _LastValueAggregation(attributes)
|
|
|
|
raise Exception(f"Invalid instrument type {type(instrument)} found")
|
|
|
|
|
|
class ExponentialBucketHistogramAggregation(Aggregation):
|
|
def __init__(
|
|
self,
|
|
max_size: int = 160,
|
|
):
|
|
self._max_size = max_size
|
|
|
|
def _create_aggregation(
|
|
self,
|
|
instrument: Instrument,
|
|
attributes: Attributes,
|
|
start_time_unix_nano: int,
|
|
) -> _Aggregation:
|
|
return _ExponentialBucketHistogramAggregation(
|
|
attributes,
|
|
start_time_unix_nano,
|
|
max_size=self._max_size,
|
|
)
|
|
|
|
|
|
class ExplicitBucketHistogramAggregation(Aggregation):
|
|
"""This aggregation informs the SDK to collect:
|
|
|
|
- Count of Measurement values falling within explicit bucket boundaries.
|
|
- Arithmetic sum of Measurement values in population. This SHOULD NOT be collected when used with instruments that record negative measurements, e.g. UpDownCounter or ObservableGauge.
|
|
- Min (optional) Measurement value in population.
|
|
- Max (optional) Measurement value in population.
|
|
|
|
|
|
Args:
|
|
boundaries: Array of increasing values representing explicit bucket boundary values.
|
|
record_min_max: Whether to record min and max.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
boundaries: Sequence[float] = (
|
|
0.0,
|
|
5.0,
|
|
10.0,
|
|
25.0,
|
|
50.0,
|
|
75.0,
|
|
100.0,
|
|
250.0,
|
|
500.0,
|
|
750.0,
|
|
1000.0,
|
|
2500.0,
|
|
5000.0,
|
|
7500.0,
|
|
10000.0,
|
|
),
|
|
record_min_max: bool = True,
|
|
) -> None:
|
|
self._boundaries = boundaries
|
|
self._record_min_max = record_min_max
|
|
|
|
def _create_aggregation(
|
|
self,
|
|
instrument: Instrument,
|
|
attributes: Attributes,
|
|
start_time_unix_nano: int,
|
|
) -> _Aggregation:
|
|
return _ExplicitBucketHistogramAggregation(
|
|
attributes,
|
|
start_time_unix_nano,
|
|
self._boundaries,
|
|
self._record_min_max,
|
|
)
|
|
|
|
|
|
class SumAggregation(Aggregation):
|
|
"""This aggregation informs the SDK to collect:
|
|
|
|
- The arithmetic sum of Measurement values.
|
|
"""
|
|
|
|
def _create_aggregation(
|
|
self,
|
|
instrument: Instrument,
|
|
attributes: Attributes,
|
|
start_time_unix_nano: int,
|
|
) -> _Aggregation:
|
|
temporality = AggregationTemporality.UNSPECIFIED
|
|
if isinstance(instrument, Synchronous):
|
|
temporality = AggregationTemporality.DELTA
|
|
elif isinstance(instrument, Asynchronous):
|
|
temporality = AggregationTemporality.CUMULATIVE
|
|
|
|
return _SumAggregation(
|
|
attributes,
|
|
isinstance(instrument, (Counter, ObservableCounter)),
|
|
temporality,
|
|
start_time_unix_nano,
|
|
)
|
|
|
|
|
|
class LastValueAggregation(Aggregation):
|
|
"""
|
|
This aggregation informs the SDK to collect:
|
|
|
|
- The last Measurement.
|
|
- The timestamp of the last Measurement.
|
|
"""
|
|
|
|
def _create_aggregation(
|
|
self,
|
|
instrument: Instrument,
|
|
attributes: Attributes,
|
|
start_time_unix_nano: int,
|
|
) -> _Aggregation:
|
|
return _LastValueAggregation(attributes)
|
|
|
|
|
|
class DropAggregation(Aggregation):
|
|
"""Using this aggregation will make all measurements be ignored."""
|
|
|
|
def _create_aggregation(
|
|
self,
|
|
instrument: Instrument,
|
|
attributes: Attributes,
|
|
start_time_unix_nano: int,
|
|
) -> _Aggregation:
|
|
return _DropAggregation(attributes)
|