ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
telemetry.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file telemetry.h
6 * @brief Zero-heap sample aggregation (PROTOCORE_ENABLE_TELEMETRY): a moving window, a rate, a totalizer.
7 *
8 * No external specification governs this module. The three accumulators are ordinary descriptive
9 * statistics and numeric integration over caller-owned records, and every name here is this
10 * library's own.
11 *
12 * - ::TelemetryWindow the arithmetic mean, the population variance, the standard deviation,
13 * the minimum and the maximum of the last @c cap samples. The mean and
14 * the variance come off running sums, so a push and either read are each
15 * a fixed number of operations; the minimum and the maximum scan the
16 * samples held.
17 * - ::TelemetryRate the first difference between successive samples, in units per second.
18 * - ::TelemetryTotalizer the trapezoidal integral of a rate over time. Its value is the quantity
19 * SenML names Sum (RFC 8428 sec 4.2, label "s" in sec 4.3 Table 1): the
20 * integrated sum of the values over time, in the Unit multiplied by
21 * seconds.
22 *
23 * The caller owns every byte: the window's sample array is a float array it passes in, and the
24 * three accumulator records are its own. Nothing here allocates, and the module keeps no file-scope
25 * state.
26 *
27 * Elapsed time is the unsigned difference of a monotonic millisecond count, so a counter rollover
28 * subtracts correctly.
29 *
30 * The module exports one symbol, @ref Telemetry. Everything in telemetry.c has internal linkage.
31 *
32 * @author Douglas Quigg (dstroy0)
33 * @date 2026
34 */
35
36#ifndef PROTOCORE_TELEMETRY_H
37#define PROTOCORE_TELEMETRY_H
38
39#include "protocore_config.h" // the entry point: protocore_types.h for the widths
40
41#if PROTOCORE_ENABLE_TELEMETRY
42
44
45/**
46 * @brief A moving window over the last @c cap samples, in storage the caller provides.
47 *
48 * The running sums carry the mean and the variance; the sample array carries the minimum and the
49 * maximum. The write cursor wraps at @c cap, so the array holds the newest @c count samples.
50 */
51typedef struct
52{
53 float *buf; ///< the sample storage the caller bound, cap floats or more
54 uint16_t cap; ///< how many samples the window holds
55 uint16_t count; ///< how many it holds now, at most cap
56 uint16_t head; ///< the next write index, and the oldest sample once full
57 double sum; ///< the running sum of the samples held
58 double sum_sq; ///< the running sum of their squares
59} TelemetryWindow;
60
61/** @brief The first difference between successive samples, in units per second. */
62typedef struct
63{
64 float last_value; ///< the previous sample
65 uint32_t last_ms; ///< the monotonic millisecond count it arrived at
66 proto_bool primed; ///< a first sample has been seen
67} TelemetryRate;
68
69/** @brief The trapezoidal integral of a rate over time: SenML's Sum (RFC 8428 sec 4.2). */
70typedef struct
71{
72 double total; ///< the accumulated total, in rate units multiplied by seconds
73 float last_rate; ///< the previous rate sample
74 uint32_t last_ms; ///< the monotonic millisecond count it arrived at
75 proto_bool primed; ///< a first rate sample has been seen
76} TelemetryTotalizer;
77
78/** @brief The window a call acts on, the storage an init binds to it, and the sample a push adds. */
79typedef struct
80{
81 TelemetryWindow *w; ///< the accumulator every window call names
82 float *buf; ///< the sample storage an init binds
83 uint16_t cap; ///< how many samples that storage holds
84 float sample; ///< the sample a push adds
85} TelemetryWindowArgs;
86
87/** @brief The rate tracker a call acts on, and the timed sample an update differentiates. */
88typedef struct
89{
90 TelemetryRate *r; ///< the accumulator every rate call names
91 float value; ///< the sample an update differentiates
92 uint32_t now_ms; ///< the monotonic millisecond count it arrived at
93} TelemetryRateArgs;
94
95/** @brief The totalizer a call acts on, and the timed rate an add integrates. */
96typedef struct
97{
98 TelemetryTotalizer *t; ///< the accumulator every totalizer call names
99 float rate; ///< the rate an add integrates, in units per second
100 uint32_t now_ms; ///< the monotonic millisecond count it arrived at
101} TelemetryTotalizerArgs;
102
103/**
104 * @brief The sample aggregators: a moving window, a rate of change, and a totalizer.
105 *
106 * A caller sets the members a call takes, invokes it through ::Telemetry, and reads the outcome off
107 * the same handle.
108 *
109 * No slot member: each call names its own caller-owned accumulator through the sub-struct of its
110 * concern, so no call names a row.
111 *
112 * @var TelemetryNs::window the window a call acts on, and what an init or a push feeds it
113 * @var TelemetryNs::rate the rate tracker a call acts on, and what an update feeds it
114 * @var TelemetryNs::totalizer the totalizer a call acts on, and what an add feeds it
115 * @var TelemetryNs::ok true when the call reached a bound accumulator; a window
116 * statistic also needs one sample held
117 * @var TelemetryNs::u16 a call's unsigned count outcome
118 * @var TelemetryNs::f32 a call's single-precision outcome
119 * @var TelemetryNs::f64 a call's double-precision outcome
120 * @var TelemetryNs::window_init bind @c window.buf and @c window.cap to @c window.w and empty it
121 * @var TelemetryNs::window_push add @c window.sample, evicting the oldest sample once full
122 * @var TelemetryNs::window_count the samples held, into @c u16
123 * @var TelemetryNs::window_mean their arithmetic mean, into @c f32
124 * @var TelemetryNs::window_variance their population variance, into @c f32
125 * @var TelemetryNs::window_stddev the square root of that variance, into @c f32
126 * @var TelemetryNs::window_min the smallest sample held, into @c f32
127 * @var TelemetryNs::window_max the largest sample held, into @c f32
128 * @var TelemetryNs::rate_init drop the prior sample, so the next update primes @c rate.r
129 * @var TelemetryNs::rate_update feed @c rate.value at @c rate.now_ms and report the change per
130 * second since the previous sample, into @c f32
131 * @var TelemetryNs::totalizer_init zero @c totalizer.t and drop its prior rate sample
132 * @var TelemetryNs::totalizer_add integrate @c totalizer.rate from the previous sample to
133 * @c totalizer.now_ms by the trapezoidal rule, into @c f64
134 * @var TelemetryNs::totalizer_total the running total, into @c f64 (SenML Sum, RFC 8428 sec 4.2)
135 * @var TelemetryNs::totalizer_reset zero the running total and drop the prior rate sample
136 */
137typedef struct
138{
139 TelemetryWindowArgs window; ///< what a window call acts on and reads
140 TelemetryRateArgs rate; ///< what a rate call acts on and reads
141 TelemetryTotalizerArgs totalizer; ///< what a totalizer call acts on and reads
142 proto_bool ok;
143 uint16_t u16;
144 float f32;
145 double f64;
146} TelemetryVars;
147
148/** @brief The operands and the outcome. */
149extern TelemetryVars TelemetryV;
150
151/** @brief The entries. */
152typedef struct
153{
154 void (*const window_init)(uint8_t *work);
155 void (*const window_push)(uint8_t *work);
156 void (*const window_count)(uint8_t *work);
157 void (*const window_mean)(uint8_t *work);
158 void (*const window_variance)(uint8_t *work);
159 void (*const window_stddev)(uint8_t *work);
160 void (*const window_min)(uint8_t *work);
161 void (*const window_max)(uint8_t *work);
162 void (*const rate_init)(uint8_t *work);
163 void (*const rate_update)(uint8_t *work);
164 void (*const totalizer_init)(uint8_t *work);
165 void (*const totalizer_add)(uint8_t *work);
166 void (*const totalizer_total)(uint8_t *work);
167 void (*const totalizer_reset)(uint8_t *work);
168} TelemetryNs;
169
170// What the table binds, defined once in the .c and taking one parameter each: everything
171// else an entry needs is an operand in TelemetryV or a region of the borrow at a fixed offset.
172void protocore_telemetry_window_init(uint8_t *work);
173void protocore_telemetry_window_push(uint8_t *work);
174void protocore_telemetry_window_count(uint8_t *work);
175void protocore_telemetry_window_mean(uint8_t *work);
176void protocore_telemetry_window_variance(uint8_t *work);
177void protocore_telemetry_window_stddev(uint8_t *work);
178void protocore_telemetry_window_min(uint8_t *work);
179void protocore_telemetry_window_max(uint8_t *work);
180void protocore_telemetry_rate_init(uint8_t *work);
181void protocore_telemetry_rate_update(uint8_t *work);
182void protocore_telemetry_totalizer_init(uint8_t *work);
183void protocore_telemetry_totalizer_add(uint8_t *work);
184void protocore_telemetry_totalizer_total(uint8_t *work);
185
186// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
187// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
188// `Telemetry.window_init(work)` resolves to a named function and becomes a DIRECT call. An extern table
189// leaves the call indirect and the symbol live at every level, -O2 -flto included.
190static const TelemetryNs Telemetry __attribute__((unused)) = {
191 .window_init = protocore_telemetry_window_init,
192 .window_push = protocore_telemetry_window_push,
193 .window_count = protocore_telemetry_window_count,
194 .window_mean = protocore_telemetry_window_mean,
195 .window_variance = protocore_telemetry_window_variance,
196 .window_stddev = protocore_telemetry_window_stddev,
197 .window_min = protocore_telemetry_window_min,
198 .window_max = protocore_telemetry_window_max,
199 .rate_init = protocore_telemetry_rate_init,
200 .rate_update = protocore_telemetry_rate_update,
201 .totalizer_init = protocore_telemetry_totalizer_init,
202 .totalizer_add = protocore_telemetry_totalizer_add,
203 .totalizer_total = protocore_telemetry_totalizer_total,
204 .totalizer_reset = protocore_telemetry_totalizer_init,
205};
206
208
209#endif // PROTOCORE_ENABLE_TELEMETRY
210
211#endif // PROTOCORE_TELEMETRY_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97