ESP-IDF: the component, and nothing else¶
The smallest ESP-IDF app that uses libtracer: a main component that says REQUIRES libtracer,
and a graph_t built with no argument. On an ESP-IDF build that graph draws every byte it holds
from one static arena in .bss, the MCU default root
(ADR-0083
Decision 4). The app registers one vertex, writes it, reads it back, and prints what the arena
carved for it.
What to notice¶
No source is passed, and none is needed. The component binds
kSlabPool = false, sotr::mem::default_root()is the static arena ofCONFIG_LIBTRACER_ARENA_BYTES(32 KiB on a chip). The graph’s tables and the value it stores both come from it; the root’s census (stats()) shows the bytes carved.The value is built on the graph’s own backend.
over_bytes(bytes, g.value_backend())copies the app’s bytes into a segment drawn from the arena’s value sub-pool, the same place the graph keeps what it stores.No transport is compiled.
sdkconfig.defaultsturnsCONFIG_LIBTRACER_TRANSPORT_UDP,_TCPand_WSoff: an in-process node sheds those translation units fromlibtracer.a.“No heap” is checked at link time, not here. CI’s no-heap link check reads
libtracer.awithnmand fails on any heap call not pinned intools/no_heap_baseline.json, a list that only shrinks.
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 component and nothing else: `REQUIRES libtracer`, a `graph_t` built
9 * with no argument, and every byte it holds carved from one static arena.
10 *
11 * On an ESP-IDF build the component binds `kSlabPool = false`, so the default root
12 * (`tr::mem::default_root()`) is a static arena of `CONFIG_LIBTRACER_ARENA_BYTES` in `.bss`
13 * (ADR-0083 Decision 4). A graph built without a source draws its tables, and the values it
14 * stores, from that arena. This app registers one vertex, writes it, reads it back, and prints
15 * what the arena carved for it. The transports are switched off in `sdkconfig.defaults`: an
16 * in-process node needs none of them.
17 */
18
19#include <cstdio>
20#include <cstdlib>
21#include <cstring>
22#include <span>
23
24#include "libtracer/tracer.hpp"
25#include "sdkconfig.h"
26
27namespace {
28
29using tr::graph::path_t;
30using tr::graph::role_t;
31
32/** @brief Failed checks so far. */
33int g_failures = 0;
34
35/** @brief Print @p what with its verdict and count a failure. */
36void check(bool ok, const char* what) {
37 std::printf(" [%s] %s\n", ok ? "ok" : "FAIL", what);
38 if (!ok) ++g_failures;
39}
40
41/** @brief Print the verdict; on the `linux` target also exit with it, so CI can run this. */
42void finish() {
43 std::printf("RESULT %s\n", g_failures == 0 ? "ok" : "FAIL");
44#if CONFIG_IDF_TARGET_LINUX
45 std::exit(g_failures == 0 ? 0 : 1);
46#endif
47}
48
49} // namespace
50
51extern "C" void app_main(void) {
52 tr::mem::block_source_t& root = tr::mem::default_root();
53 const std::size_t before = root.stats().in_use;
54
55 tr::graph::graph_t g; // no source: the default root, the static arena
56 const auto temp = g.register_vertex(path_t("/sensor/temp"), role_t::STORED_VALUE);
57
58 static constexpr char kText[] = "21.5C";
59 const auto bytes = std::as_bytes(std::span(kText, sizeof kText - 1));
60 const auto value = tr::view::over_bytes(bytes, g.value_backend());
61 check(value.has_value(), "the value's bytes came from the graph's value backend");
62 check(value && g.write(temp, *value).has_value(), "write /sensor/temp");
63
64 const auto read = g.read(temp);
65 const auto got = read ? (*read)->only().bytes() : std::span<const std::byte>{};
66 check(got.size() == bytes.size() && std::memcmp(got.data(), bytes.data(), got.size()) == 0,
67 "read /sensor/temp serves what was written");
68
69 const tr::mem::source_stats_t s = root.stats();
70 std::printf("default root \"%s\": %zu of %zu arena bytes carved, %zu of them by this node\n",
71 root.name(), s.in_use, s.capacity, s.in_use - before);
72 check(s.capacity == tr::mem::kArenaBytes, "the root is the arena config_t sized");
73 check(s.in_use > before, "the graph's tables and its value were carved from it");
74 finish();
75}
Build and run¶
$ cd integrations/esp-idf/examples/concepts/minimal
$ 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, where it
exits non-zero on a failed check. See also: the ESP-IDF one-concept set ·
backends.