ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
guardrails.cpp
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 guardrails.cpp
6 * @brief Heap/stack guardrail evaluator + JSON (pure) and the ESP32 sampler.
7 *
8 * The evaluator and serializer are host-tested; the sample reads the live esp_* /
9 * FreeRTOS counters on ESP32 and returns zeros on host.
10 */
11
13#include "shared_primitives/strbuf.h" // pc_sb frame builder
14
15#if PC_ENABLE_GUARDRAILS
16
17#include <stdio.h>
18
19#if defined(ARDUINO)
20#include "esp_heap_caps.h"
21#include "esp_system.h"
22#include "freertos/FreeRTOS.h"
23#include "freertos/task.h"
24#endif
25uint8_t pc_guardrail_eval(const pc_health *h, uint32_t heap_min, uint32_t frag_min_block, uint32_t stack_min)
26{
27 uint8_t b = pc_breach::PC_BREACH_NONE;
28 if (!h)
29 {
30 return b;
31 }
32 if (h->free_heap < heap_min)
33 {
34 b |= pc_breach::PC_BREACH_HEAP;
35 }
36 if (h->largest_free_block < frag_min_block)
37 {
38 b |= pc_breach::PC_BREACH_FRAG;
39 }
40 if (h->stack_free < stack_min)
41 {
42 b |= pc_breach::PC_BREACH_STACK;
43 }
44 return b;
45}
46
47int pc_health_json(const pc_health *h, char *out, size_t cap)
48{
49 if (!out || cap == 0)
50 {
51 return 0;
52 }
53 out[0] = '\0';
54 if (!h)
55 {
56 return 0;
57 }
58 pc_sb sb_out = {out, cap, 0, true};
59 pc_sb_put(&sb_out, "{\"free_heap\":");
60 pc_sb_u32(&sb_out, (uint32_t)((unsigned)h->free_heap));
61 pc_sb_put(&sb_out, ",\"min_free_heap\":");
62 pc_sb_u32(&sb_out, (uint32_t)((unsigned)h->min_free_heap));
63 pc_sb_put(&sb_out, ",\"largest_free_block\":");
64 pc_sb_u32(&sb_out, (uint32_t)((unsigned)h->largest_free_block));
65 pc_sb_put(&sb_out, ",\"stack_free\":");
66 pc_sb_u32(&sb_out, (uint32_t)((unsigned)h->stack_free));
67 pc_sb_put(&sb_out, "}");
68 int w = (int)pc_sb_finish(&sb_out);
69 // w < 0 is unreachable: this format is all %u (unsigned) with literal text, no
70 // multibyte/wide-character conversion, which is the only way snprintf goes negative.
71 if (!sb_out.ok) // GCOVR_EXCL_BR_LINE w<0 half unreachable (see above)
72 {
73 out[0] = '\0';
74 return 0;
75 }
76 return w;
77}
78
79#ifdef ARDUINO
80
81namespace
82{
83// All guardrails sampler state, owned by one instance (internal linkage): the breach
84// callback, so it is one named owner, unreachable from any other translation unit.
85struct GuardrailsCtx
86{
87 pc_breach_fn cb = nullptr;
88};
89GuardrailsCtx s_gr;
90} // namespace
91
92void pc_guardrails_sample(pc_health *h)
93{
94 if (!h)
95 {
96 return;
97 }
98 h->free_heap = esp_get_free_heap_size();
99 h->min_free_heap = esp_get_minimum_free_heap_size();
100 h->largest_free_block = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT);
101 h->stack_free = (uint32_t)uxTaskGetStackHighWaterMark(nullptr) * sizeof(StackType_t);
102}
103
104void pc_guardrails_begin(pc_breach_fn cb)
105{
106 s_gr.cb = cb;
107}
108
109uint8_t pc_guardrails_check(void)
110{
111 pc_health h;
112 pc_guardrails_sample(&h);
114 if (b != pc_breach::PC_BREACH_NONE && s_gr.cb)
115 {
116 s_gr.cb(b, &h);
117 }
118 return b;
119}
120
121#else // host build - no live counters
122
123void pc_guardrails_sample(pc_health *h)
124{
125 if (h)
126 {
127 *h = pc_health{};
128 }
129}
130void pc_guardrails_begin(pc_breach_fn)
131{
132}
133uint8_t pc_guardrails_check(void)
134{
135 return pc_breach::PC_BREACH_NONE;
136}
137
138#endif // ARDUINO
139
140#endif // PC_ENABLE_GUARDRAILS
Runtime heap/stack guardrails (PC_ENABLE_GUARDRAILS).
#define PC_GUARDRAIL_HEAP_MIN
Free-heap floor (bytes); below this trips the heap guardrail.
#define PC_GUARDRAIL_FRAG_MIN_BLOCK
Largest-free-block floor (bytes); below this trips the fragmentation guardrail.
#define PC_GUARDRAIL_STACK_MIN
Task remaining-stack floor (bytes); below this trips the stack guardrail.
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
Definition strbuf.h:648
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
Definition strbuf.h:60
void pc_sb_u32(pc_sb *b, uint32_t v)
Append v as decimal (no leading zeros; "0" for zero).
Definition strbuf.h:303
Bump-append target; ok latches false once an append would overflow cap.
Definition strbuf.h:30
bool ok
Definition strbuf.h:34