- harness/es.py: append-only event store + проекции (образец — кооп-портал) - harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу - harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы - harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе (глобальный asyncio-рантайм Strands не переживает 2+ create_harness) - cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом) - 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
540 lines
16 KiB
Python
540 lines
16 KiB
Python
# Copyright The OpenTelemetry Authors
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# pylint: disable=too-many-ancestors
|
|
|
|
|
|
from abc import ABC, abstractmethod
|
|
from collections.abc import Callable, Generator, Iterable, Sequence
|
|
from dataclasses import dataclass
|
|
from logging import getLogger
|
|
from re import compile as re_compile
|
|
from typing import (
|
|
Generic,
|
|
TypeVar,
|
|
)
|
|
|
|
# pylint: disable=unused-import; needed for typing and sphinx
|
|
from opentelemetry import metrics
|
|
from opentelemetry.context import Context
|
|
from opentelemetry.metrics._internal.observation import Observation
|
|
from opentelemetry.util.types import (
|
|
Attributes,
|
|
)
|
|
|
|
_logger = getLogger(__name__)
|
|
|
|
_name_regex = re_compile(r"[a-zA-Z][-_./a-zA-Z0-9]{0,254}")
|
|
_unit_regex = re_compile(r"[\x00-\x7F]{0,63}")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class _MetricsHistogramAdvisory:
|
|
explicit_bucket_boundaries: Sequence[float] | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CallbackOptions:
|
|
"""Options for the callback
|
|
|
|
Args:
|
|
timeout_millis: Timeout for the callback's execution. If the callback does asynchronous
|
|
work (e.g. HTTP requests), it should respect this timeout.
|
|
"""
|
|
|
|
timeout_millis: float = 10_000
|
|
|
|
|
|
InstrumentT = TypeVar("InstrumentT", bound="Instrument")
|
|
# pylint: disable=invalid-name
|
|
CallbackT = Callable[[CallbackOptions], Iterable[Observation]] | Generator[Iterable[Observation], CallbackOptions, None]
|
|
|
|
|
|
class Instrument(ABC):
|
|
"""Abstract class that serves as base for all instruments."""
|
|
|
|
@abstractmethod
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
unit: str = "",
|
|
description: str = "",
|
|
) -> None:
|
|
pass
|
|
|
|
@staticmethod
|
|
def _check_name_unit_description(name: str, unit: str, description: str) -> dict[str, str | None]:
|
|
"""
|
|
Checks the following instrument name, unit and description for
|
|
compliance with the spec.
|
|
|
|
Returns a dict with keys "name", "unit" and "description", the
|
|
corresponding values will be the checked strings or `None` if the value
|
|
is invalid. If valid, the checked strings should be used instead of the
|
|
original values.
|
|
"""
|
|
|
|
result: dict[str, str | None] = {}
|
|
|
|
if _name_regex.fullmatch(name) is not None:
|
|
result["name"] = name
|
|
else:
|
|
result["name"] = None
|
|
|
|
if unit is None:
|
|
unit = ""
|
|
if _unit_regex.fullmatch(unit) is not None:
|
|
result["unit"] = unit
|
|
else:
|
|
result["unit"] = None
|
|
|
|
if description is None:
|
|
result["description"] = ""
|
|
else:
|
|
result["description"] = description
|
|
|
|
return result
|
|
|
|
|
|
class _ProxyInstrument(ABC, Generic[InstrumentT]):
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
unit: str = "",
|
|
description: str = "",
|
|
) -> None:
|
|
self._name = name
|
|
self._unit = unit
|
|
self._description = description
|
|
self._real_instrument: InstrumentT | None = None
|
|
|
|
def on_meter_set(self, meter: "metrics.Meter") -> None:
|
|
"""Called when a real meter is set on the creating _ProxyMeter"""
|
|
|
|
# We don't need any locking on proxy instruments because it's OK if some
|
|
# measurements get dropped while a real backing instrument is being
|
|
# created.
|
|
self._real_instrument = self._create_real_instrument(meter)
|
|
|
|
@abstractmethod
|
|
def _create_real_instrument(self, meter: "metrics.Meter") -> InstrumentT:
|
|
"""Create an instance of the real instrument. Implement this."""
|
|
|
|
|
|
class _ProxyAsynchronousInstrument(_ProxyInstrument[InstrumentT]):
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
callbacks: Sequence[CallbackT] | None = None,
|
|
unit: str = "",
|
|
description: str = "",
|
|
) -> None:
|
|
super().__init__(name, unit, description)
|
|
self._callbacks = callbacks
|
|
|
|
|
|
class Synchronous(Instrument):
|
|
"""Base class for all synchronous instruments"""
|
|
|
|
|
|
class Asynchronous(Instrument):
|
|
"""Base class for all asynchronous instruments"""
|
|
|
|
@abstractmethod
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
callbacks: Sequence[CallbackT] | None = None,
|
|
unit: str = "",
|
|
description: str = "",
|
|
) -> None:
|
|
super().__init__(name, unit=unit, description=description)
|
|
|
|
|
|
class Counter(Synchronous):
|
|
"""A Counter is a synchronous `Instrument` which supports non-negative increments."""
|
|
|
|
@abstractmethod
|
|
def add(
|
|
self,
|
|
amount: float,
|
|
attributes: Attributes = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
"""Records an increment to the counter.
|
|
|
|
Args:
|
|
amount: The amount to increment the counter by. Must be non-negative.
|
|
attributes: Optional set of attributes to associate with the measurement.
|
|
context: Optional context to associate with the measurement. If not
|
|
provided, the current context is used.
|
|
"""
|
|
|
|
|
|
class NoOpCounter(Counter):
|
|
"""No-op implementation of `Counter`."""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
unit: str = "",
|
|
description: str = "",
|
|
) -> None:
|
|
super().__init__(name, unit=unit, description=description)
|
|
|
|
def add(
|
|
self,
|
|
amount: float,
|
|
attributes: Attributes = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
return super().add(amount, attributes=attributes, context=context)
|
|
|
|
|
|
class _ProxyCounter(_ProxyInstrument[Counter], Counter):
|
|
def add(
|
|
self,
|
|
amount: float,
|
|
attributes: Attributes = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
if self._real_instrument:
|
|
self._real_instrument.add(amount, attributes, context)
|
|
|
|
def _create_real_instrument(self, meter: "metrics.Meter") -> Counter:
|
|
return meter.create_counter(
|
|
self._name,
|
|
self._unit,
|
|
self._description,
|
|
)
|
|
|
|
|
|
class UpDownCounter(Synchronous):
|
|
"""An UpDownCounter is a synchronous `Instrument` which supports increments and decrements."""
|
|
|
|
@abstractmethod
|
|
def add(
|
|
self,
|
|
amount: float,
|
|
attributes: Attributes = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
"""Records an increment or decrement to the counter.
|
|
|
|
Unlike `Counter`, the ``amount`` may be negative, allowing the
|
|
instrument to track values that go up and down (e.g. number of
|
|
active requests, queue depth).
|
|
|
|
Args:
|
|
amount: The amount to add to the counter. May be positive or negative.
|
|
attributes: Optional set of attributes to associate with the measurement.
|
|
context: Optional context to associate with the measurement. If not
|
|
provided, the current context is used.
|
|
"""
|
|
|
|
|
|
class NoOpUpDownCounter(UpDownCounter):
|
|
"""No-op implementation of `UpDownCounter`."""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
unit: str = "",
|
|
description: str = "",
|
|
) -> None:
|
|
super().__init__(name, unit=unit, description=description)
|
|
|
|
def add(
|
|
self,
|
|
amount: float,
|
|
attributes: Attributes = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
return super().add(amount, attributes=attributes, context=context)
|
|
|
|
|
|
class _ProxyUpDownCounter(_ProxyInstrument[UpDownCounter], UpDownCounter):
|
|
def add(
|
|
self,
|
|
amount: float,
|
|
attributes: Attributes = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
if self._real_instrument:
|
|
self._real_instrument.add(amount, attributes, context)
|
|
|
|
def _create_real_instrument(self, meter: "metrics.Meter") -> UpDownCounter:
|
|
return meter.create_up_down_counter(
|
|
self._name,
|
|
self._unit,
|
|
self._description,
|
|
)
|
|
|
|
|
|
class ObservableCounter(Asynchronous):
|
|
"""An ObservableCounter is an asynchronous `Instrument` which reports monotonically
|
|
increasing value(s) when the instrument is being observed.
|
|
"""
|
|
|
|
|
|
class NoOpObservableCounter(ObservableCounter):
|
|
"""No-op implementation of `ObservableCounter`."""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
callbacks: Sequence[CallbackT] | None = None,
|
|
unit: str = "",
|
|
description: str = "",
|
|
) -> None:
|
|
super().__init__(
|
|
name,
|
|
callbacks,
|
|
unit=unit,
|
|
description=description,
|
|
)
|
|
|
|
|
|
class _ProxyObservableCounter(_ProxyAsynchronousInstrument[ObservableCounter], ObservableCounter):
|
|
def _create_real_instrument(self, meter: "metrics.Meter") -> ObservableCounter:
|
|
return meter.create_observable_counter(
|
|
self._name,
|
|
self._callbacks,
|
|
self._unit,
|
|
self._description,
|
|
)
|
|
|
|
|
|
class ObservableUpDownCounter(Asynchronous):
|
|
"""An ObservableUpDownCounter is an asynchronous `Instrument` which reports additive value(s) (e.g.
|
|
the process heap size - it makes sense to report the heap size from multiple processes and sum them
|
|
up, so we get the total heap usage) when the instrument is being observed.
|
|
"""
|
|
|
|
|
|
class NoOpObservableUpDownCounter(ObservableUpDownCounter):
|
|
"""No-op implementation of `ObservableUpDownCounter`."""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
callbacks: Sequence[CallbackT] | None = None,
|
|
unit: str = "",
|
|
description: str = "",
|
|
) -> None:
|
|
super().__init__(
|
|
name,
|
|
callbacks,
|
|
unit=unit,
|
|
description=description,
|
|
)
|
|
|
|
|
|
class _ProxyObservableUpDownCounter(
|
|
_ProxyAsynchronousInstrument[ObservableUpDownCounter],
|
|
ObservableUpDownCounter,
|
|
):
|
|
def _create_real_instrument(self, meter: "metrics.Meter") -> ObservableUpDownCounter:
|
|
return meter.create_observable_up_down_counter(
|
|
self._name,
|
|
self._callbacks,
|
|
self._unit,
|
|
self._description,
|
|
)
|
|
|
|
|
|
class Histogram(Synchronous):
|
|
"""Histogram is a synchronous `Instrument` which can be used to report arbitrary values
|
|
that are likely to be statistically meaningful. It is intended for statistics such as
|
|
histograms, summaries, and percentile.
|
|
"""
|
|
|
|
@abstractmethod
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
unit: str = "",
|
|
description: str = "",
|
|
explicit_bucket_boundaries_advisory: Sequence[float] | None = None,
|
|
) -> None:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def record(
|
|
self,
|
|
amount: float,
|
|
attributes: Attributes = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
"""Records a measurement.
|
|
|
|
Used to report measurements that are likely to be statistically
|
|
meaningful, such as request durations, payload sizes, or any value
|
|
for which a distribution (e.g. percentiles) is useful.
|
|
|
|
Args:
|
|
amount: The measurement to record. Should be non-negative in most
|
|
cases; negative values are only meaningful when the histogram
|
|
is used to track signed deltas.
|
|
attributes: Optional set of attributes to associate with the measurement.
|
|
context: Optional context to associate with the measurement. If not
|
|
provided, the current context is used.
|
|
"""
|
|
|
|
|
|
class NoOpHistogram(Histogram):
|
|
"""No-op implementation of `Histogram`."""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
unit: str = "",
|
|
description: str = "",
|
|
explicit_bucket_boundaries_advisory: Sequence[float] | None = None,
|
|
) -> None:
|
|
super().__init__(
|
|
name,
|
|
unit=unit,
|
|
description=description,
|
|
explicit_bucket_boundaries_advisory=explicit_bucket_boundaries_advisory,
|
|
)
|
|
|
|
def record(
|
|
self,
|
|
amount: float,
|
|
attributes: Attributes = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
return super().record(amount, attributes=attributes, context=context)
|
|
|
|
|
|
class _ProxyHistogram(_ProxyInstrument[Histogram], Histogram):
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
unit: str = "",
|
|
description: str = "",
|
|
explicit_bucket_boundaries_advisory: Sequence[float] | None = None,
|
|
) -> None:
|
|
super().__init__(name, unit=unit, description=description)
|
|
self._explicit_bucket_boundaries_advisory = explicit_bucket_boundaries_advisory
|
|
|
|
def record(
|
|
self,
|
|
amount: float,
|
|
attributes: Attributes = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
if self._real_instrument:
|
|
self._real_instrument.record(amount, attributes, context)
|
|
|
|
def _create_real_instrument(self, meter: "metrics.Meter") -> Histogram:
|
|
return meter.create_histogram(
|
|
self._name,
|
|
self._unit,
|
|
self._description,
|
|
explicit_bucket_boundaries_advisory=self._explicit_bucket_boundaries_advisory,
|
|
)
|
|
|
|
|
|
class ObservableGauge(Asynchronous):
|
|
"""Asynchronous Gauge is an asynchronous `Instrument` which reports non-additive value(s) (e.g.
|
|
the room temperature - it makes no sense to report the temperature value from multiple rooms
|
|
and sum them up) when the instrument is being observed.
|
|
"""
|
|
|
|
|
|
class NoOpObservableGauge(ObservableGauge):
|
|
"""No-op implementation of `ObservableGauge`."""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
callbacks: Sequence[CallbackT] | None = None,
|
|
unit: str = "",
|
|
description: str = "",
|
|
) -> None:
|
|
super().__init__(
|
|
name,
|
|
callbacks,
|
|
unit=unit,
|
|
description=description,
|
|
)
|
|
|
|
|
|
class _ProxyObservableGauge(
|
|
_ProxyAsynchronousInstrument[ObservableGauge],
|
|
ObservableGauge,
|
|
):
|
|
def _create_real_instrument(self, meter: "metrics.Meter") -> ObservableGauge:
|
|
return meter.create_observable_gauge(
|
|
self._name,
|
|
self._callbacks,
|
|
self._unit,
|
|
self._description,
|
|
)
|
|
|
|
|
|
class Gauge(Synchronous):
|
|
"""A Gauge is a synchronous `Instrument` which can be used to record non-additive values as they occur."""
|
|
|
|
@abstractmethod
|
|
def set(
|
|
self,
|
|
amount: float,
|
|
attributes: Attributes = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
"""Records the current value of the gauge.
|
|
|
|
The gauge reports the last recorded value when observed. It is
|
|
intended for non-additive measurements where only the current
|
|
value matters (e.g. CPU utilisation percentage, room temperature).
|
|
|
|
Args:
|
|
amount: The current value to record.
|
|
attributes: Optional set of attributes to associate with the measurement.
|
|
context: Optional context to associate with the measurement. If not
|
|
provided, the current context is used.
|
|
"""
|
|
|
|
|
|
class NoOpGauge(Gauge):
|
|
"""No-op implementation of ``Gauge``."""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
unit: str = "",
|
|
description: str = "",
|
|
) -> None:
|
|
super().__init__(name, unit=unit, description=description)
|
|
|
|
def set(
|
|
self,
|
|
amount: float,
|
|
attributes: Attributes = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
return super().set(amount, attributes=attributes, context=context)
|
|
|
|
|
|
class _ProxyGauge(
|
|
_ProxyInstrument[Gauge],
|
|
Gauge,
|
|
):
|
|
def set(
|
|
self,
|
|
amount: float,
|
|
attributes: Attributes = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
if self._real_instrument:
|
|
self._real_instrument.set(amount, attributes, context)
|
|
|
|
def _create_real_instrument(self, meter: "metrics.Meter") -> Gauge:
|
|
return meter.create_gauge(
|
|
self._name,
|
|
self._unit,
|
|
self._description,
|
|
)
|