ESP-IDF: reading the sub-pools’ :stats

The default root is one arena, and the graph derives three sub-pools from it: values (what vertices store), tables (registrations and the graph’s own containers) and net (the router’s and the links’ defaults). Each is a :stats seam on any vertex (RFC-0010 Amendment 3): one READ of :stats.mem.values, .tables or .net answers that sub-pool’s census as one SETTINGS block of NAME / u64 pairs.

What to notice

  • One READ, one block. The counters arrive together, sampled in one call: capacity, in_use, peak, refused and largest_refused. A reader looks them up by name and ignores a name it does not know.

  • Read in place, with nothing allocated. The example walks the block with tr::wire::tlv_node_t::over(bytes, tr::mem::null_source()), which validates the frame and walks its children without building a tree.

  • peak is the number to size by. The app writes values of 16, 200 and 700 bytes to one vertex. Each write replaces the last, so in_use holds only the 700-byte value, while peak still counts the moment the old and the new value were both alive.

  • A node with no link still has a net sub-pool. Its census answers, with nothing refused.

  • An injected root answers none of the three. A graph over the app’s own root derives no sub-pools, and each name answers SCHEMA_NOT_FOUND (one task, no pool locks).

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 — where a running node's RAM goes: one READ of `:stats.mem.values`,
  9 *        `.tables` or `.net` answers that sub-pool's census.
 10 *
 11 * The default root is one arena, and the graph derives three sub-pools from it: values (what
 12 * vertices store), tables (registrations and the graph's own containers) and net (the router's
 13 * and the links' defaults). Each is a `:stats` seam on any vertex (RFC-0010 Amendment 3): a
 14 * READ answers one `SETTINGS` block of `NAME` / u64 pairs, read here in place with
 15 * `tlv_node_t`, which allocates nothing. The app stores a few values of different sizes and
 16 * prints all three blocks: `in_use` against `peak` is the number to size the arena by.
 17 */
 18
 19#include <cstdint>
 20#include <cstdio>
 21#include <cstdlib>
 22#include <optional>
 23#include <span>
 24#include <string_view>
 25
 26#include "libtracer/byteorder.hpp"
 27#include "libtracer/tracer.hpp"
 28#include "sdkconfig.h"
 29
 30namespace {
 31
 32using tr::graph::path_t;
 33using tr::graph::role_t;
 34using tr::wire::type_t;
 35
 36/** @brief Failed checks so far. */
 37int g_failures = 0;
 38
 39/** @brief Print @p what with its verdict and count a failure. */
 40void check(bool ok, const char* what) {
 41    std::printf("  [%s] %s\n", ok ? "ok" : "FAIL", what);
 42    if (!ok) ++g_failures;
 43}
 44
 45/** @brief Print the verdict; on the `linux` target also exit with it, so CI can run this. */
 46void finish() {
 47    std::printf("RESULT %s\n", g_failures == 0 ? "ok" : "FAIL");
 48#if CONFIG_IDF_TARGET_LINUX
 49    std::exit(g_failures == 0 ? 0 : 1);
 50#endif
 51}
 52
 53/** @brief The three counters this example checks, from one sub-pool's census. */
 54struct census_t {
 55    std::uint64_t in_use = 0;  /**< @brief Bytes handed out and not returned. */
 56    std::uint64_t peak = 0;    /**< @brief The high-water mark of `in_use`. */
 57    std::uint64_t refused = 0; /**< @brief Requests answered by a refusal. */
 58};
 59
 60/** @brief Read the census at @p seam and print every counter in it. */
 61std::optional<census_t> census(const tr::graph::graph_t& g, std::string_view seam) {
 62    const auto read = g.read(*path_t::parse(seam));
 63    if (!read) return std::nullopt;
 64    const tr::view::view_t flat = (*read)->flatten();
 65    const auto block = tr::wire::tlv_node_t::over(flat.bytes(), tr::mem::null_source());
 66    if (!block || block->type() != type_t::SETTINGS) return std::nullopt;
 67
 68    std::printf("%.*s:", static_cast<int>(seam.size()), seam.data());
 69    census_t c;
 70    std::string_view name;
 71    for (const tr::wire::tlv_node_t child : block->children()) {
 72        const auto body = child.payload();
 73        if (child.type() == type_t::NAME) {
 74            name = {reinterpret_cast<const char*>(body.data()), body.size()};
 75        } else if (child.type() == type_t::VALUE && body.size() == 8) {
 76            const auto v = tr::detail::load_le<std::uint64_t>(body);
 77            std::printf(" %.*s=%llu", static_cast<int>(name.size()), name.data(),
 78                        static_cast<unsigned long long>(v));
 79            if (name == "in_use") c.in_use = v;
 80            if (name == "peak") c.peak = v;
 81            if (name == "refused") c.refused = v;
 82        }
 83    }
 84    std::printf("\n");
 85    return c;
 86}
 87
 88}  // namespace
 89
 90extern "C" void app_main(void) {
 91    tr::graph::graph_t g;
 92    const auto log = g.register_vertex(path_t("/log"), role_t::STORED_VALUE);
 93    static constexpr std::byte kLine[700]{};
 94    for (std::size_t n : {16U, 200U, 700U}) {
 95        const auto value = tr::view::over_bytes(std::span(kLine, n), g.value_backend());
 96        check(value && g.write(log, *value).has_value(), "write a value to /log");
 97    }
 98
 99    const auto values = census(g, "/log:stats.mem.values");
100    const auto tables = census(g, "/log:stats.mem.tables");
101    const auto net = census(g, "/log:stats.mem.net");
102
103    check(values && values->in_use > 0, "the value sub-pool holds what /log stores");
104    check(values && values->peak > values->in_use,
105          "its peak still counts the two values the last write replaced");
106    check(tables && tables->in_use > 0, "the table sub-pool holds the registration");
107    check(net && net->refused == 0, "the net sub-pool answers too, and has refused nothing");
108    finish();
109}

Build and run

$ cd integrations/esp-idf/examples/concepts/stats_subpools
$ 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. See also: sizing the arena · Rust: reading a node’s :stats.