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()
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
Nonewhen it was counted and dropped.
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.
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.
A count of everything a reporter has seen, cheap enough to ship anywhere.
78def link_cost_threshold(cost: LinkCost) -> Level: 79 """Return the level a link cost calls for. 80 81 :param cost: What the link currently costs. 82 :returns: The lowest level still worth its bytes at that cost. 83 """ 84 return Level(_link_cost_threshold(cost.value))
Return the level a link cost calls for.
Parameters
- cost: What the link currently costs. :returns: The lowest level still worth its bytes at that cost.