pamoja.telemetry

Idiomatic telemetry facade.

A node that ships every event it produces will spend more on reporting than on the job it was installed to do, and on a satellite link that is money. A reporter ships what is worth its bytes, counts everything either way, and moves its own bar as the link gets more expensive, so the aggregate picture survives even when the detail cannot be sent.

The generated binding decides on the level alone, since that is all the core reporter reads. The event a caller writes travels no further than this layer, which hands it straight back when it should be shipped.

  1"""Idiomatic telemetry facade.
  2
  3A node that ships every event it produces will spend more on reporting than on
  4the job it was installed to do, and on a satellite link that is money. A reporter
  5ships what is worth its bytes, counts everything either way, and moves its own
  6bar as the link gets more expensive, so the aggregate picture survives even when
  7the detail cannot be sent.
  8
  9The generated binding decides on the level alone, since that is all the core
 10reporter reads. The event a caller writes travels no further than this layer,
 11which hands it straight back when it should be shipped.
 12"""
 13
 14from __future__ import annotations
 15
 16import enum
 17from dataclasses import dataclass
 18
 19from pamoja._native import Reporter as _Reporter
 20from pamoja._native import Snapshot
 21from pamoja._native import link_cost_threshold as _link_cost_threshold
 22
 23__all__ = [
 24    "Event",
 25    "Level",
 26    "LinkCost",
 27    "Reporter",
 28    "Snapshot",
 29    "link_cost_threshold",
 30]
 31
 32
 33class Level(str, enum.Enum):
 34    """How urgent an event is."""
 35
 36    #: Fine-grained detail, useful only when chasing a specific problem.
 37    TRACE = "Trace"
 38    #: Diagnostic detail for development.
 39    DEBUG = "Debug"
 40    #: A normal, noteworthy event.
 41    INFO = "Info"
 42    #: Something unexpected that the node recovered from.
 43    WARN = "Warn"
 44    #: A failure that needs attention.
 45    ERROR = "Error"
 46
 47
 48class LinkCost(str, enum.Enum):
 49    """What the link back to the network currently costs."""
 50
 51    #: Bytes are effectively free, such as on wired power and ethernet.
 52    FREE = "Free"
 53    #: Bytes are paid for, such as on a cellular plan.
 54    METERED = "Metered"
 55    #: Bytes are scarce, such as on a satellite or long-range radio link.
 56    EXPENSIVE = "Expensive"
 57    #: Nothing can be shipped at all.
 58    OFFLINE = "Offline"
 59
 60
 61@dataclass(frozen=True)
 62class Event:
 63    """A structured telemetry event.
 64
 65    The code is a stable, short label such as ``battery.low`` rather than a
 66    free-form message, so events stay small and group cleanly into counts.
 67    """
 68
 69    #: How urgent the event is.
 70    level: Level
 71    #: A stable, short identifier for what happened.
 72    code: str
 73    #: An optional measurement, such as the charge that triggered it.
 74    value: float | None = None
 75
 76
 77def link_cost_threshold(cost: LinkCost) -> Level:
 78    """Return the level a link cost calls for.
 79
 80    :param cost: What the link currently costs.
 81    :returns: The lowest level still worth its bytes at that cost.
 82    """
 83    return Level(_link_cost_threshold(cost.value))
 84
 85
 86class Reporter:
 87    """Record events, ship the ones worth their bytes, and count them all."""
 88
 89    def __init__(self, threshold: Level = Level.INFO) -> None:
 90        """Create a reporter that ships events at or above ``threshold``.
 91
 92        :param threshold: The lowest level to ship.
 93        """
 94        self._inner = _Reporter(threshold.value)
 95
 96    @property
 97    def threshold(self) -> Level:
 98        """The level this reporter is currently shipping from."""
 99        return Level(self._inner.threshold)
100
101    @threshold.setter
102    def threshold(self, threshold: Level) -> None:
103        self._inner.threshold = threshold.value
104
105    @property
106    def total(self) -> int:
107        """How many events have been seen across every level."""
108        return self._inner.total
109
110    @property
111    def emitted(self) -> int:
112        """How many events passed the threshold and were shipped."""
113        return self._inner.emitted
114
115    @property
116    def dropped(self) -> int:
117        """How many events the threshold dropped."""
118        return self._inner.dropped
119
120    def adapt_to(self, cost: LinkCost) -> None:
121        """Move the threshold to match what the link now costs.
122
123        :param cost: What the link currently costs.
124        """
125        self._inner.adapt_to(cost.value)
126
127    def record(self, event: Event) -> Event | None:
128        """Record an event, returning it when it should be shipped.
129
130        :param event: The event that occurred.
131        :returns: The same event when it passed the threshold, or ``None`` when
132            it was counted and dropped.
133        """
134        return event if self._inner.record(event.level.value) else None
135
136    def count(self, level: Level) -> int:
137        """Return how many events have been seen at a level, shipped or not.
138
139        :param level: The level to count.
140        :returns: The number of events recorded at that level.
141        """
142        return self._inner.count(level.value)
143
144    def snapshot(self) -> Snapshot:
145        """Take a snapshot of the counters to ship in place of the stream.
146
147        :returns: The per-level counts and the shipped and dropped totals.
148        """
149        return self._inner.snapshot()
@dataclass(frozen=True)
class Event:
62@dataclass(frozen=True)
63class Event:
64    """A structured telemetry event.
65
66    The code is a stable, short label such as ``battery.low`` rather than a
67    free-form message, so events stay small and group cleanly into counts.
68    """
69
70    #: How urgent the event is.
71    level: Level
72    #: A stable, short identifier for what happened.
73    code: str
74    #: An optional measurement, such as the charge that triggered it.
75    value: float | None = None

A structured telemetry event.

The code is a stable, short label such as battery.low rather than a free-form message, so events stay small and group cleanly into counts.

Event(level: Level, code: str, value: float | None = None)
level: Level
code: str
value: float | None = None
class Level(builtins.str, enum.Enum):
34class Level(str, enum.Enum):
35    """How urgent an event is."""
36
37    #: Fine-grained detail, useful only when chasing a specific problem.
38    TRACE = "Trace"
39    #: Diagnostic detail for development.
40    DEBUG = "Debug"
41    #: A normal, noteworthy event.
42    INFO = "Info"
43    #: Something unexpected that the node recovered from.
44    WARN = "Warn"
45    #: A failure that needs attention.
46    ERROR = "Error"

How urgent an event is.

TRACE = <Level.TRACE: 'Trace'>
DEBUG = <Level.DEBUG: 'Debug'>
INFO = <Level.INFO: 'Info'>
WARN = <Level.WARN: 'Warn'>
ERROR = <Level.ERROR: 'Error'>
class LinkCost(builtins.str, enum.Enum):
49class LinkCost(str, enum.Enum):
50    """What the link back to the network currently costs."""
51
52    #: Bytes are effectively free, such as on wired power and ethernet.
53    FREE = "Free"
54    #: Bytes are paid for, such as on a cellular plan.
55    METERED = "Metered"
56    #: Bytes are scarce, such as on a satellite or long-range radio link.
57    EXPENSIVE = "Expensive"
58    #: Nothing can be shipped at all.
59    OFFLINE = "Offline"

What the link back to the network currently costs.

FREE = <LinkCost.FREE: 'Free'>
METERED = <LinkCost.METERED: 'Metered'>
EXPENSIVE = <LinkCost.EXPENSIVE: 'Expensive'>
OFFLINE = <LinkCost.OFFLINE: 'Offline'>
class Reporter:
 87class Reporter:
 88    """Record events, ship the ones worth their bytes, and count them all."""
 89
 90    def __init__(self, threshold: Level = Level.INFO) -> None:
 91        """Create a reporter that ships events at or above ``threshold``.
 92
 93        :param threshold: The lowest level to ship.
 94        """
 95        self._inner = _Reporter(threshold.value)
 96
 97    @property
 98    def threshold(self) -> Level:
 99        """The level this reporter is currently shipping from."""
100        return Level(self._inner.threshold)
101
102    @threshold.setter
103    def threshold(self, threshold: Level) -> None:
104        self._inner.threshold = threshold.value
105
106    @property
107    def total(self) -> int:
108        """How many events have been seen across every level."""
109        return self._inner.total
110
111    @property
112    def emitted(self) -> int:
113        """How many events passed the threshold and were shipped."""
114        return self._inner.emitted
115
116    @property
117    def dropped(self) -> int:
118        """How many events the threshold dropped."""
119        return self._inner.dropped
120
121    def adapt_to(self, cost: LinkCost) -> None:
122        """Move the threshold to match what the link now costs.
123
124        :param cost: What the link currently costs.
125        """
126        self._inner.adapt_to(cost.value)
127
128    def record(self, event: Event) -> Event | None:
129        """Record an event, returning it when it should be shipped.
130
131        :param event: The event that occurred.
132        :returns: The same event when it passed the threshold, or ``None`` when
133            it was counted and dropped.
134        """
135        return event if self._inner.record(event.level.value) else None
136
137    def count(self, level: Level) -> int:
138        """Return how many events have been seen at a level, shipped or not.
139
140        :param level: The level to count.
141        :returns: The number of events recorded at that level.
142        """
143        return self._inner.count(level.value)
144
145    def snapshot(self) -> Snapshot:
146        """Take a snapshot of the counters to ship in place of the stream.
147
148        :returns: The per-level counts and the shipped and dropped totals.
149        """
150        return self._inner.snapshot()

Record events, ship the ones worth their bytes, and count them all.

Reporter(threshold: Level = <Level.INFO: 'Info'>)
90    def __init__(self, threshold: Level = Level.INFO) -> None:
91        """Create a reporter that ships events at or above ``threshold``.
92
93        :param threshold: The lowest level to ship.
94        """
95        self._inner = _Reporter(threshold.value)

Create a reporter that ships events at or above threshold.

Parameters
  • threshold: The lowest level to ship.
threshold: Level
 97    @property
 98    def threshold(self) -> Level:
 99        """The level this reporter is currently shipping from."""
100        return Level(self._inner.threshold)

The level this reporter is currently shipping from.

total: int
106    @property
107    def total(self) -> int:
108        """How many events have been seen across every level."""
109        return self._inner.total

How many events have been seen across every level.

emitted: int
111    @property
112    def emitted(self) -> int:
113        """How many events passed the threshold and were shipped."""
114        return self._inner.emitted

How many events passed the threshold and were shipped.

dropped: int
116    @property
117    def dropped(self) -> int:
118        """How many events the threshold dropped."""
119        return self._inner.dropped

How many events the threshold dropped.

def adapt_to(self, cost: LinkCost) -> None:
121    def adapt_to(self, cost: LinkCost) -> None:
122        """Move the threshold to match what the link now costs.
123
124        :param cost: What the link currently costs.
125        """
126        self._inner.adapt_to(cost.value)

Move the threshold to match what the link now costs.

Parameters
  • cost: What the link currently costs.
def record(self, event: Event) -> Event | None:
128    def record(self, event: Event) -> Event | None:
129        """Record an event, returning it when it should be shipped.
130
131        :param event: The event that occurred.
132        :returns: The same event when it passed the threshold, or ``None`` when
133            it was counted and dropped.
134        """
135        return event if self._inner.record(event.level.value) else None

Record an event, returning it when it should be shipped.

Parameters
  • event: The event that occurred. :returns: The same event when it passed the threshold, or None when it was counted and dropped.
def count(self, level: Level) -> int:
137    def count(self, level: Level) -> int:
138        """Return how many events have been seen at a level, shipped or not.
139
140        :param level: The level to count.
141        :returns: The number of events recorded at that level.
142        """
143        return self._inner.count(level.value)

Return how many events have been seen at a level, shipped or not.

Parameters
  • level: The level to count. :returns: The number of events recorded at that level.
def snapshot(self) -> Snapshot:
145    def snapshot(self) -> Snapshot:
146        """Take a snapshot of the counters to ship in place of the stream.
147
148        :returns: The per-level counts and the shipped and dropped totals.
149        """
150        return self._inner.snapshot()

Take a snapshot of the counters to ship in place of the stream.

:returns: The per-level counts and the shipped and dropped totals.

class Snapshot:

A count of everything a reporter has seen, cheap enough to ship anywhere.

trace

How many events were seen at trace level.

debug

How many events were seen at debug level.

warn

How many events were seen at warn level.

error

How many events were seen at error level.

info

How many events were seen at info level.

emitted

How many events passed the filter and were shipped.

dropped

How many events the filter dropped.