ESP-IDF: sizing the arena¶
The MCU default root is one .bss array of config_t::kArenaBytes, which the component binds
from CONFIG_LIBTRACER_ARENA_BYTES. Its size is a compile-time number, so the linker map shows
exactly what libtracer holds, and it never grows: a node that outgrows it is told so, by value
at run time and by a sizing message at init
(ADR-0083,
ADR-0056).
What to notice¶
Size it against the node’s peak. The app sets the arena to 8 KiB in
sdkconfig.defaults, registers and writes the eight vertices it is built to hold, and reads the root’s census:capacityis the arena,in_usethe bytes carved so far. Their difference is the headroom. The arena never gives bytes back to itself (a returned block is reused by its class), so the carved figure is a high-water mark.At run time, a refusal is a value.
try_register_vertexis the failable form: past the arena it answers an error, the root’s census counts the refusal and the size of the largest refused request, and the node keeps running.At init, a refusal is a sizing message.
register_vertexis infallible (ADR-0056): a literal path at startup is not a condition the app can handle, so libtracer reports and aborts. The line names the call, the sub-pool and the bytes it needed:libtracer: register_vertex: the "tables" memory source refused an allocation at initialization (88 bytes needed, 6400 bytes in use): a sizing bug, give it more room (ADR-0056, ADR-0083)
That is the line from a
linux-target run of this example; the run before it printed8 sensors: 3504 of 8192 bytes carvedandtry_register_vertex refused after 39 more. The “in use” figure is the sub-pool’s, not the arena’s.The example runs that last step only with
CONFIG_EXAMPLE_SHOW_INIT_EXHAUSTION=y(idf.py menuconfig, arena_sizing example). It is off by default because on a board the abort is a reset. The line goes through the build’sfault_sink_t; on ESP-IDF that is the console.The numbers to size by are on the node.
:stats.mem.*reports each sub-pool’speak, so a running node can tell you the arena it needs.
Source¶
1/*
2 * SPDX-License-Identifier: Apache-2.0
3 * SPDX-FileCopyrightText: Copyright 2026 avatarsd LLC
4 */
5
6/**
7 * @file
8 * @brief ONE CONCEPT — the arena's size is a compile-time number from `config_t`, sized against
9 * the node's peak, and a node that outgrows it is told so: by value at run time, by a
10 * sizing message at init.
11 *
12 * `CONFIG_LIBTRACER_ARENA_BYTES` binds `tr::graph::config_t::kArenaBytes`, the size of the one
13 * `.bss` array behind the default root (ADR-0083 Decision 4). This app sets it to 8 KiB in
14 * `sdkconfig.defaults`, registers and writes the vertices it is built to hold, and reads the
15 * root's census to see the headroom left. Then it keeps registering with `try_register_vertex`,
16 * the failable form, until the arena refuses: a run-time registration answers an error and the
17 * node keeps running. `register_vertex`, the infallible init form (ADR-0056), cannot answer one;
18 * past the arena it prints the sizing message and aborts. `CONFIG_EXAMPLE_SHOW_INIT_EXHAUSTION`
19 * runs that last step too; it is off by default because on a board the abort is a reset.
20 */
21
22#include <cstdint>
23#include <cstdio>
24#include <cstdlib>
25#include <span>
26
27#include "libtracer/tracer.hpp"
28#include "sdkconfig.h"
29
30namespace {
31
32using tr::graph::path_t;
33using tr::graph::role_t;
34
35/** @brief The vertices this node is built to hold. */
36constexpr int kSensors = 8;
37
38/** @brief Failed checks so far. */
39int g_failures = 0;
40
41/** @brief Print @p what with its verdict and count a failure. */
42void check(bool ok, const char* what) {
43 std::printf(" [%s] %s\n", ok ? "ok" : "FAIL", what);
44 if (!ok) ++g_failures;
45}
46
47/** @brief Print the verdict; on the `linux` target also exit with it, so CI can run this. */
48void finish() {
49 std::printf("RESULT %s\n", g_failures == 0 ? "ok" : "FAIL");
50#if CONFIG_IDF_TARGET_LINUX
51 std::exit(g_failures == 0 ? 0 : 1);
52#endif
53}
54
55/** @brief The path `/s/<i>`, parsed. */
56path_t sensor(int i) {
57 char text[16];
58 std::snprintf(text, sizeof text, "/s/%d", i);
59 return *path_t::parse(text);
60}
61
62} // namespace
63
64extern "C" void app_main(void) {
65 tr::mem::block_source_t& root = tr::mem::default_root();
66 std::printf("config_t::kArenaBytes = %zu (CONFIG_LIBTRACER_ARENA_BYTES)\n",
67 tr::mem::kArenaBytes);
68
69 tr::graph::graph_t g;
70 for (int i = 0; i < kSensors; ++i) {
71 const auto v = g.register_vertex(sensor(i), role_t::STORED_VALUE);
72 const std::uint32_t reading = 200 + i;
73 const auto value =
74 tr::view::over_bytes(std::as_bytes(std::span(&reading, 1)), g.value_backend());
75 check(value && g.write(v, *value).has_value(), "register and write one sensor");
76 }
77 const tr::mem::source_stats_t s = root.stats();
78 std::printf("%d sensors: %zu of %zu bytes carved, %zu bytes of headroom\n", kSensors, s.in_use,
79 s.capacity, s.capacity - s.in_use);
80 check(s.capacity == tr::mem::kArenaBytes && s.in_use < s.capacity, "the node fits");
81
82 // Past the arena at run time: the failable form answers an error, and nothing aborts.
83 int extra = 0;
84 while (extra < 10000 && g.try_register_vertex(sensor(kSensors + extra), role_t::STORED_VALUE))
85 ++extra;
86 const tr::mem::source_stats_t after = root.stats();
87 std::printf("try_register_vertex refused after %d more; %zu refusals, largest %zu bytes\n",
88 extra, after.refused, after.largest_refused);
89 check(extra < 10000 && after.refused > 0, "the arena refused by value, and the node runs on");
90
91#if CONFIG_EXAMPLE_SHOW_INIT_EXHAUSTION
92 // Past the arena at init: register_vertex is infallible, so this prints the sizing
93 // message naming the sub-pool and the bytes it needed, and aborts.
94 (void)g.register_vertex(path_t("/one/too/many"), role_t::STORED_VALUE);
95#endif
96 finish();
97}
Build and run¶
$ cd integrations/esp-idf/examples/concepts/arena_sizing
$ idf.py set-target esp32c6 build
$ idf.py flash monitor # board-only
CI builds it for esp32c6, and also builds and runs it on the ESP-IDF linux target with the
init step off. See also: the component, and nothing else.