DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
clock.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file clock.h
6 * @brief Pluggable monotonic clock for all library timing.
7 *
8 * The library's internal timing runs at **1000 Hz** - one tick is one millisecond,
9 * the cadence the test suite asserts and every timeout / poll is expressed in.
10 * `detws_millis()` is that single time source; by default it is the platform
11 * `millis()`.
12 *
13 * To drive the library from your own clock (a hardware timer, an external RTC, a
14 * simulation clock), call:
15 *
16 * detws_set_clock(my_clock_fn, my_ticks_per_second);
17 *
18 * Your clock reports a free-running tick count at `ticks_per_second`. The library
19 * **divides it down to its internal 1000 Hz**, so timeouts and polling keep the
20 * exact 1 ms granularity the tests verify regardless of how fast your clock runs.
21 * Pass a rate >= 1000, ideally a multiple of 1000 for exact division (e.g. a
22 * 1 MHz timer -> ticks_per_second = 1000000, divided by 1000). Pass `nullptr` to
23 * revert to the platform default. One source covers everything - swap it once and
24 * every subsystem follows.
25 *
26 * The worker poll cadence is fixed at 1000 Hz (the tested default); a build can
27 * trade latency for idle power with DETWS_WORKER_POLL_TICKS - see ServerConfig.h.
28 *
29 * Header-only (the override state lives in inline-function-local statics, a single
30 * instance across the whole program), so there is nothing extra to compile or link.
31 *
32 * @author Douglas Quigg (dstroy0)
33 * @date 2026
34 */
35
36#ifndef DETERMINISTICESPASYNCWEBSERVER_DET_CLOCK_H
37#define DETERMINISTICESPASYNCWEBSERVER_DET_CLOCK_H
38
39#include <Arduino.h> // platform millis()
40#include <stdint.h>
41
42/** @brief User clock: returns a free-running monotonic tick count. */
43typedef uint32_t (*detws_clock_fn)(void);
44
45// Override state. Inline functions with local statics resolve to a single shared
46// instance across all translation units (ODR), so a set from anywhere is seen
47// everywhere - no .cpp, no globals to define.
49{
50 static detws_clock_fn fn = nullptr;
51 return fn;
52}
53inline uint32_t &_detws_clock_div_ref()
54{
55 static uint32_t div = 1;
56 return div;
57}
58
59/**
60 * @brief Install a custom clock running at @p ticks_per_second; the library divides
61 * it down to its internal 1000 Hz. Pass (nullptr, 0) to revert to the
62 * platform default.
63 */
64inline void detws_set_clock(detws_clock_fn fn, uint32_t ticks_per_second)
65{
67 _detws_clock_div_ref() = (ticks_per_second >= 1000) ? (ticks_per_second / 1000) : 1;
68}
69
70/** @brief The library's monotonic time at 1000 Hz (milliseconds). */
71inline uint32_t detws_millis(void)
72{
74 if (fn)
75 return fn() / _detws_clock_div_ref();
76 return millis();
77}
78
79// ---------------------------------------------------------------------------
80// Microsecond time base (v5 clock-awareness): ISR timestamps + sub-ms latency
81// ---------------------------------------------------------------------------
82//
83// A second, higher-resolution source for real-time work: timestamping a hardware
84// event in an ISR and budgeting how long a piece of work takes. Pluggable like the
85// millisecond clock; the default is the platform micros() on device, or
86// detws_millis() * 1000 on host (override for sub-ms precision in tests).
87
89{
90 static detws_clock_fn fn = nullptr;
91 return fn;
92}
93inline uint32_t &_detws_micros_div_ref()
94{
95 static uint32_t div = 1;
96 return div;
97}
98
99/**
100 * @brief Install a custom microsecond clock running at @p ticks_per_second; the
101 * library divides it down to 1 MHz. Pass (nullptr, 0) for the platform
102 * default.
103 */
104inline void detws_set_micros_clock(detws_clock_fn fn, uint32_t ticks_per_second)
105{
107 _detws_micros_div_ref() = (ticks_per_second >= 1000000u) ? (ticks_per_second / 1000000u) : 1u;
108}
109
110/**
111 * @brief Monotonic microseconds - the high-resolution time base for ISR
112 * timestamps and sub-millisecond latency. Safe to call from an ISR. Wraps
113 * roughly every 71 minutes, so use it only for short deltas (unsigned
114 * subtraction is wrap-safe).
115 */
116inline uint32_t detws_micros(void)
117{
119 if (fn)
120 return fn() / _detws_micros_div_ref();
121#ifdef ARDUINO
122 return micros();
123#else
124 return detws_millis() * 1000u; // host fallback (no platform micros); override for precision
125#endif
126}
127
128// ---------------------------------------------------------------------------
129// Latency budgeting: measure an operation against a microsecond budget
130// ---------------------------------------------------------------------------
131
132/**
133 * @brief Rolling latency statistics in microseconds: sample count, min / max /
134 * mean, and how many samples blew a budget. Fixed size, no heap; a
135 * subsystem (the preempting queue, a DMA path, a forwarding rule) keeps one
136 * and reports it for real-time visibility.
137 */
139{
140 uint32_t count;
141 uint32_t over_budget; ///< samples whose latency exceeded the budget
142 uint32_t min_us;
143 uint32_t max_us;
144 uint64_t sum_us;
145};
146
147/** @brief Zero a stat (min seeded high so the first sample sets it). */
149{
150 s->count = 0;
151 s->over_budget = 0;
152 s->min_us = 0xFFFFFFFFu;
153 s->max_us = 0;
154 s->sum_us = 0;
155}
156
157/** @brief Start of a measured span: capture the current microsecond time. */
158inline uint32_t detws_lat_begin(void)
159{
160 return detws_micros();
161}
162
163/**
164 * @brief End of a span started at @p start_us: record its latency, counting it as
165 * over-budget when @p budget_us is non-zero and exceeded. Wrap-safe.
166 */
167inline void detws_lat_end(DetwsLatencyStat *s, uint32_t start_us, uint32_t budget_us)
168{
169 uint32_t lat = detws_micros() - start_us; // wrap-safe unsigned delta
170 s->count++;
171 s->sum_us += lat;
172 if (lat < s->min_us)
173 s->min_us = lat;
174 if (lat > s->max_us)
175 s->max_us = lat;
176 if (budget_us && lat > budget_us)
177 s->over_budget++;
178}
179
180/** @brief Mean latency (us) over the recorded samples, 0 if none. */
181inline uint32_t detws_lat_avg_us(const DetwsLatencyStat *s)
182{
183 return s->count ? (uint32_t)(s->sum_us / s->count) : 0u;
184}
185
186#endif // DETERMINISTICESPASYNCWEBSERVER_DET_CLOCK_H
uint32_t & _detws_micros_div_ref()
Definition clock.h:93
uint32_t & _detws_clock_div_ref()
Definition clock.h:53
uint32_t detws_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
Definition clock.h:71
void detws_set_micros_clock(detws_clock_fn fn, uint32_t ticks_per_second)
Install a custom microsecond clock running at ticks_per_second; the library divides it down to 1 MHz....
Definition clock.h:104
void detws_lat_reset(DetwsLatencyStat *s)
Zero a stat (min seeded high so the first sample sets it).
Definition clock.h:148
detws_clock_fn & _detws_clock_fn_ref()
Definition clock.h:48
uint32_t detws_lat_avg_us(const DetwsLatencyStat *s)
Mean latency (us) over the recorded samples, 0 if none.
Definition clock.h:181
uint32_t detws_micros(void)
Monotonic microseconds - the high-resolution time base for ISR timestamps and sub-millisecond latency...
Definition clock.h:116
uint32_t detws_lat_begin(void)
Start of a measured span: capture the current microsecond time.
Definition clock.h:158
void detws_lat_end(DetwsLatencyStat *s, uint32_t start_us, uint32_t budget_us)
End of a span started at start_us: record its latency, counting it as over-budget when budget_us is n...
Definition clock.h:167
detws_clock_fn & _detws_micros_fn_ref()
Definition clock.h:88
uint32_t(* detws_clock_fn)(void)
User clock: returns a free-running monotonic tick count.
Definition clock.h:43
void detws_set_clock(detws_clock_fn fn, uint32_t ticks_per_second)
Install a custom clock running at ticks_per_second; the library divides it down to its internal 1000 ...
Definition clock.h:64
Rolling latency statistics in microseconds: sample count, min / max / mean, and how many samples blew...
Definition clock.h:139
uint32_t max_us
Definition clock.h:143
uint32_t over_budget
samples whose latency exceeded the budget
Definition clock.h:141
uint32_t count
Definition clock.h:140
uint32_t min_us
Definition clock.h:142
uint64_t sum_us
Definition clock.h:144