Skip to content

Statistics, health, Prometheus

enocean.stats

Runtime statistics and health of a communicator, always collected (no dependency, negligible cost).

Read them with communicator.stats.snapshot() and communicator.health(), or expose them to Prometheus with enocean.prometheus.

SenderStats dataclass

SenderStats(
    packets: int,
    last_seen_at: float,
    rorg: int,
    dbm: int | None,
)

What was last heard from one device (per-sender tracking must be enabled).

StatsSnapshot dataclass

StatsSnapshot(
    started_at: float,
    packets_received: dict[PacketKind, int],
    packets_sent: dict[PacketKind, int],
    bytes_received: int,
    bytes_sent: int,
    parse_errors: dict[str, int],
    teach_in_responses: int,
    base_id_requests: int,
    base_id_timeouts: int,
    base_id_fetch_seconds: float | None,
    transport_errors: int,
    processing_errors: int,
    last_packet_received_at: float | None,
    packets_ignored: int = 0,
    senders: dict[str, SenderStats] = dict(),
    senders_evicted: int = 0,
)

A consistent copy of the statistics at one point in time. Timestamps are time.time() values.

CommunicatorStats dataclass

CommunicatorStats(
    started_at: float = time.time(),
    _lock: Lock = threading.Lock(),
    _packets_received: Counter[PacketKind] = Counter(),
    _packets_sent: Counter[PacketKind] = Counter(),
    _bytes_received: int = 0,
    _bytes_sent: int = 0,
    _parse_errors: Counter[str] = Counter(),
    _teach_in_responses: int = 0,
    _base_id_requests: int = 0,
    _base_id_timeouts: int = 0,
    _base_id_fetch_seconds: float | None = None,
    _transport_errors: int = 0,
    _processing_errors: int = 0,
    _last_packet_received_at: float | None = None,
    _packets_ignored: int = 0,
    _max_senders: int = 0,
    _senders: OrderedDict[str, SenderStats] = OrderedDict(),
    _senders_evicted: int = 0,
)

Counters updated by the communicator thread; safe to read from any thread through snapshot().

enable_sender_tracking

enable_sender_tracking(max_senders: int = 500) -> None

Also keep per-device statistics (SenderStats), for up to max_senders devices.

Beyond that, the least recently heard devices are forgotten, so neighbours' devices picked up by the radio can't grow memory or metric cardinality forever.

record_bytes_received

record_bytes_received(count: int) -> None

Count bytes read from the transport.

record_received

record_received(packet: Packet) -> None

Count a received packet, and its sender when per-sender tracking is enabled.

record_ignored

record_ignored() -> None

Count a packet dropped because its device is ignored by the DeviceRegistry.

record_sent

record_sent(packet: Packet, byte_count: int) -> None

Count a packet written to the transport, and its bytes.

record_parse_error

record_parse_error(kind: str) -> None

Count a parse error reported by Packet.parse_msg(on_error=...).

record_teach_in_response

record_teach_in_response() -> None

Count a UTE teach-in response sent.

record_base_id_request

record_base_id_request() -> None

Count a base ID request sent to the module.

record_base_id_received

record_base_id_received(seconds: float) -> None

Record how long the module took to answer the base ID request.

record_base_id_timeout

record_base_id_timeout() -> None

Count a base ID request the module did not answer in time.

record_transport_error

record_transport_error() -> None

Count a transport failure (e.g. the serial port failing to read or write).

record_processing_error

record_processing_error() -> None

Count an exception raised while processing received packets (e.g. by a callback).

snapshot

snapshot() -> StatsSnapshot

A consistent copy of all the counters, safe to use from any thread.

Health dataclass

Health(
    running: bool,
    transport_ready: bool,
    base_id_known: bool,
    receive_queue_size: int,
    transmit_queue_size: int,
    seconds_since_last_packet: float,
    problems: tuple[str, ...],
)

State of a communicator, for liveness/readiness probes. problems explains why it is not healthy.

healthy property

healthy: bool

Whether no problem was found.

packet_kind

packet_kind(packet: Packet) -> PacketKind

The (packet type, RORG) statistics are counted by; RORG is None for non-radio packets.

enocean.prometheus

Prometheus metrics for communicators. Requires the optional dependency: pip install enocean[prometheus].

from prometheus_client import start_http_server
from enocean.prometheus import register

register(communicator)          # or register({'usb0': com0, 'usb1': com1}), registry=..., namespace=...
start_http_server(9100)

Metrics are computed from communicator.stats and communicator.health() when scraped: nothing is done between scrapes.

EnOceanCollector

EnOceanCollector(
    communicators: Communicator
    | Mapping[str, Communicator],
    namespace: str = 'enocean',
    const_labels: Mapping[str, str] | None = None,
    max_silence: float | None = None,
    per_sender: bool = False,
    max_senders: int = 500,
)

Bases: Collector

Collects metrics from one communicator, or several given as {name: communicator} (adds a communicator label).

namespace prefixes every metric name; const_labels are added to every sample (e.g. {'site': 'home'}); max_silence (seconds) is passed to health() for the healthy gauge. per_sender enables per-device tracking (bounded by max_senders) and exports it with a sender label: mind the cardinality.

collect

collect() -> Iterator[Metric]

Metrics of the communicators, read from their stats and health (called by prometheus_client on scrape).

register

register(
    communicators: Communicator
    | Mapping[str, Communicator],
    registry: CollectorRegistry = REGISTRY,
    namespace: str = 'enocean',
    const_labels: Mapping[str, str] | None = None,
    max_silence: float | None = None,
    per_sender: bool = False,
    max_senders: int = 500,
) -> EnOceanCollector

Creates an EnOceanCollector and registers it (in the default registry unless one is given).

enocean.consolelogger

Logging setup for applications and examples using the library.

JsonFormatter

Bases: Formatter

Formats records as one JSON object per line, including fields passed with extra=.

format

format(record: LogRecord) -> str

Format a record as a JSON object: time, level, logger, message, extra fields, exception.

init_logging

init_logging(
    level: int = logging.DEBUG,
    log_to_file: bool = False,
    logsize: int = 1024,
    logcount: int = 5,
    json_format: bool = False,
) -> None

Send the library's logs to stderr, and optionally to a rotating enocean.log file.

Parameters:

Name Type Description Default
level int

Minimum level of the records emitted.

DEBUG
log_to_file bool

Also write to enocean.log in the working directory.

False
logsize int

Maximum size of enocean.log, in kB, before it is rotated.

1024
logcount int

Number of rotated files kept.

5
json_format bool

Emit one JSON object per record, carrying structured fields such as packet_type, rorg, sender.

False