Skip to content

Add MemAudit: per-subsystem heap accounting in the boot log - #10900

Merged
thebentern merged 2 commits into
developfrom
feat/memaudit-heap-accounting
Jul 6, 2026
Merged

Add MemAudit: per-subsystem heap accounting in the boot log#10900
thebentern merged 2 commits into
developfrom
feat/memaudit-heap-accounting

Conversation

@thebentern

@thebentern thebentern commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Motivation

The 2.8.0 nRF52840 field reports of ~99% heap use had to be diagnosed by hand: reconstructing which subsystem owned how much heap from source, build flags, and guesswork, one report at a time. This PR turns that community heap breakdown into a first-class feature — every future report self-diagnoses straight from the serial log.

What it does

A tiny fixed-size registry (src/memory/MemAudit.h/.cpp, namespace memaudit) that subsystems report their large long-lived allocations into, printed as a single log line at the end of setup() and again alongside the existing periodic Heap free: line:

INFO  | MemAudit[boot]: tmm=2500 warm=4000 pkthist=5824 nodedb=13440 msgstore=2200 pktpool(live)=3270 total=31234
  • add(tag, delta) / set(tag, bytes) / snapshot(out, max) / logBreakdown(when)
  • Tags are string literals, registered on first use (pointer compare fast path, strcmp fallback), up to 16 tags, no heap allocation in the registry itself
  • Counters are relaxed 32-bit atomics: single instructions on ARM Cortex-M, no locks, safe from the one hot path (per-packet pool alloc/release)

Instrumented subsystems

Tag What it measures Where
nodedb hot NodeInfoLite vector (MAX_NUM_NODES * sizeof) NodeDB::nodeDBSelfCare()
satmaps position/telemetry/environment/status satellite maps (rb-tree overhead estimated at 44 B/entry) NodeDB::enforceSatelliteCaps()
warm warm node store entry array WarmNodeStore ctor/dtor
pkthist packet history records + hash index PacketHistory ctor
tmm / tmm_ni TrafficManagement unified cache / NodeInfo payload cache TrafficManagementModule ctor/dtor
msgstore message text pool MessageStore pool init
display TFT line + repaint chunk buffers TFTDisplay
pktpool(live) live in-flight meshtastic_MeshPackets (the only hot-path counter; atomic add) MemoryPool.h via an optional allocator tag set in Router.cpp

Cost

  • RAM: 128 B static table (16 tags x 8 B, measured in the rak4631 map) + one tag pointer per packet-pool instance; ~600 B transient stack while formatting the log line
  • Flash: ~340 B of MemAudit code in the rak4631 ELF (logBreakdown 192 B, findOrRegister 132 B, set 14 B, add inlined by LTO) plus the one-line call sites and tag literals — well under 1 KB total
  • Escape hatch: MESHTASTIC_MEM_AUDIT=0 compiles the whole thing out to inline no-op stubs (call sites need no #ifdefs). Default is on everywhere except STM32WL, which is already flash-tight and excluded by default.

Verification

  • New native suite test_mem_audit (add/set/snapshot math, tag reuse incl. cross-TU duplicate literals, null/unknown tags, table-full behavior); test/native-suite-count bumped 27 -> 28
  • Full native suite in Docker: GREEN — 542 test cases: 542 succeeded (28/28 suites, test_mem_audit PASSED)
  • pio run -e rak4631: SUCCESS

Summary by CodeRabbit

  • New Features
    • Added a memory audit registry and tag-based memory accounting across multiple subsystems.
    • Added boot-time and periodic heap breakdown logging to surface per-area memory usage.
  • Bug Fixes
    • Improved reliability of live memory accounting for pools, buffers, and message/text/packet caches.
  • Tests
    • Added unit tests validating audit registration, accumulation, snapshots, and full-capacity behavior.
  • Chores
    • Updated the WebAssembly build to include the memory audit sources.

The 2.8.0 nRF52840 heap-exhaustion field reports had to be diagnosed by
hand, reconstructing each subsystem's heap footprint from source and
build flags one report at a time. This makes every future report
self-diagnosing from the serial log: a tiny fixed-size registry
(src/memory/MemAudit.*) that big long-lived allocations report into,
printed as one line at the end of setup() and alongside the periodic
"Heap free:" log:

  MemAudit[boot]: tmm=2500 warm=4000 pkthist=5824 nodedb=13440
  msgstore=2200 pktpool(live)=3270 total=31234

Instrumented: NodeDB hot vector (nodedb) + satellite maps (satmaps,
rb-tree overhead estimated), WarmNodeStore (warm), PacketHistory records
and hash index (pkthist), TrafficManagement caches (tmm/tmm_ni),
MessageStore text pool (msgstore), TFT line/repaint buffers (display),
and live in-flight packets (pktpool(live)) via an optional audit tag on
the packet pool allocator - the one hot path, counted with a relaxed
32-bit atomic add (single instructions on Cortex-M, no locks).

Cost: 128 B RAM for the 16-tag table, well under 1 KB flash on rak4631.
MESHTASTIC_MEM_AUDIT=0 compiles it out to inline no-op stubs (call
sites need no ifdefs); STM32WL, the tightest flash target, defaults off.

New native suite test_mem_audit covers add/set/snapshot arithmetic, tag
reuse (pointer and cross-TU strcmp fallback), null/unknown tags, and
table-full behavior; test/native-suite-count bumped to 28.
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Flash this PR in the Web Flasher

firmware commit boards expires

Warning

This is an automated, unreviewed CI test build. Back up your device configuration
before flashing, and only flash devices you are able to recover.

Supported boards built by this PR (26)
Device Board Platform
Crowpanel Adv 3.5 TFT elecrow-adv-35-tft esp32-s3
Heltec HT62 heltec-ht62-esp32c3-sx1262 esp32-c3
Heltec Mesh Node 096 heltec-mesh-node-t096 nrf52840
Heltec Mesh Node T1 heltec-mesh-node-t1 nrf52840
Heltec Mesh Node T114 heltec-mesh-node-t114 nrf52840
Heltec V3 heltec-v3 esp32-s3
Heltec V4 heltec-v4 esp32-s3
Raspberry Pi Pico pico rp2040
Raspberry Pi Pico W picow rp2040
RAK WisMesh Tag rak_wismeshtag nrf52840
RAK WisBlock 11200 rak11200 esp32
RAK WisBlock 11310 rak11310 rp2040
RAK3312 rak3312 esp32-s3
RAK WisBlock 4631 rak4631 nrf52840
Seeed SenseCAP Mesh-Tracker-X1 seeed_mesh_tracker_X1 nrf52840
Seeed Wio Tracker L1 seeed_wio_tracker_L1 nrf52840
Seeed Xiao NRF52840 Kit seeed_xiao_nrf52840_kit nrf52840
Seeed Xiao ESP32-S3 seeed-xiao-s3 esp32-s3
Station G2 station-g2 esp32-s3
Station G3 station-g3 esp32-s3
LILYGO T-Deck t-deck-tft esp32-s3
LILYGO T-Echo t-echo nrf52840
LILYGO T-Echo Plus t-echo-plus nrf52840
LILYGO T-Impulse Plus t-impulse-plus nrf52840
LilyGo T3-C6 tlora-c6 esp32-c6
Seeed SenseCAP T1000-E tracker-t1000-e nrf52840

Build artifacts expire on 2026-08-05. Updated for 039a668.

@github-actions github-actions Bot added the enhancement New feature or request label Jul 6, 2026
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 43e98d03-2dd6-430c-aa0e-a8a077b78b78

📥 Commits

Reviewing files that changed from the base of the PR and between 87cc932 and 039a668.

📒 Files selected for processing (1)
  • variants/native/portduino/platformio.ini

📝 Walkthrough

Walkthrough

This PR adds a compile-time-gated memory audit registry (memaudit) that tracks per-subsystem byte usage via tagged counters, with add/set/snapshot/logBreakdown APIs. It is wired into allocator pools, subsystem storage, boot logging, periodic heap logging, tests, and native wasm build inputs.

Changes

Memory audit registry and instrumentation

Layer / File(s) Summary
MemAudit API and implementation
src/memory/MemAudit.h, src/memory/MemAudit.cpp
New header declares Tag struct and add/set/snapshot/logBreakdown API, gated by MESHTASTIC_MEM_AUDIT with no-op stubs when disabled; implementation uses a fixed-size atomic tag registry with acquire/release registration and relaxed-atomic updates.
Unit tests
test/test_mem_audit/test_main.cpp, test/native-suite-count, variants/native/portduino/platformio.ini
Adds Unity-based tests covering set/add semantics, slot reuse/merging, unknown/null tags, snapshot limits, and table-full behavior; updates native suite count and wasm source filtering to include memory/.
Allocator/MemoryPool audit tag wiring
src/mesh/MemoryPool.h, src/mesh/Router.cpp
Allocator, MemoryDynamic, and MemoryPool constructors accept an optional auditTag, reporting size deltas on alloc/release; Router.cpp names the live packet pool "pktpool(live)".
Subsystem allocation reporting
src/MessageStore.cpp, src/graphics/TFTDisplay.cpp, src/mesh/NodeDB.cpp, src/mesh/PacketHistory.cpp, src/mesh/WarmNodeStore.cpp, src/modules/TrafficManagementModule.cpp
Adds memaudit::set calls to report and clear buffer sizes for message pool, display buffers, satellite/node DB maps, packet history, warm node store, and traffic management caches.
Boot/heap breakdown logging
src/main.cpp, src/memGet.cpp
Adds memaudit::logBreakdown("boot") and memaudit::logBreakdown("heap") calls to emit aggregated per-tag usage logs.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Subsystem as Subsystem code
  participant MemAudit as memaudit registry
  participant Logger as main.cpp / memGet.cpp

  Subsystem->>MemAudit: add(tag, delta) / set(tag, bytes)
  MemAudit->>MemAudit: register or reuse tag slot
  MemAudit->>MemAudit: update byte counter
  Logger->>MemAudit: logBreakdown(when)
  MemAudit->>MemAudit: snapshot registered tags
  MemAudit-->>Logger: log aggregated tag=bytes breakdown + total
Loading

Poem

A rabbit counts each byte in sight,
from dawn’s boot log to heap at night. 🐰
With tags like warm and pktpool live,
the little counters bloom and thrive.
Then logBreakdown sings the score—
one tidy heap tale, plus a little more.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding MemAudit for subsystem heap accounting and boot-log reporting.
Description check ✅ Passed The description is detailed and covers motivation, behavior, instrumentation, cost, and verification, but it omits the template's attestation checkboxes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/memory/MemAudit.h (1)

7-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Trim the header comment block; exceeds guideline's comment-length limit.

This block spans ~19 lines of multi-paragraph explanation (design rationale, concurrency model, log format example). As per coding guidelines: "Keep code comments minimal: one or two lines at most, only explain the why when it is not obvious, and avoid multi-paragraph explanatory comments."

Consider trimming to a short summary and moving the detailed design rationale (concurrency semantics, tag-registration rules) to a design doc/PR description instead of inline comments.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/memory/MemAudit.h` around lines 7 - 27, Trim the top-of-file comment in
MemAudit.h to a minimal one- or two-line summary and remove the multi-paragraph
design rationale, log example, concurrency details, and compilation notes. Keep
only the essential purpose of MemAudit and any non-obvious constraint that
callers must know, and move the deeper explanation about relaxed atomics, tag
immutability, registration behavior, and no-op stubs into external documentation
or the PR description.

Source: Coding guidelines

src/memory/MemAudit.cpp (1)

93-112: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tag-length assumption isn't enforced at the write site.

The buffer sizing relies on tags never exceeding 16 characters (per the comment), but add/set/findOrRegister never validate tag length. A longer tag won't overflow the buffer (the pos + written >= sizeof(line) check catches truncation safely), but it will silently truncate the log line rather than fail loudly. Worth a static_assert/length check at registration time if MemAudit.h doesn't already enforce this constraint.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/memory/MemAudit.cpp` around lines 93 - 112, The MemAudit breakdown
logging assumes tags are at most 16 characters, but that constraint is not
enforced where tags are registered. Add an explicit validation in the tag
registration path used by add, set, and findOrRegister, or a static_assert if
the limit is compile-time guaranteed, so oversized tags are rejected or flagged
instead of silently truncating the output. Keep the check close to the tag
creation logic in MemAudit and ensure the existing logBreakdown buffer sizing
comment matches the enforced limit.
src/modules/TrafficManagementModule.cpp (1)

207-215: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Guard the "tmm_ni" clear the same way it's set.

memaudit::set("tmm_ni", 0) at Line 214 isn't wrapped in #if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM), unlike the corresponding set("tmm_ni", ...) at Line 182. On platforms without PSRAM, "tmm_ni" is never actually populated but the destructor still registers it, permanently occupying a slot in the fixed-size tag table and showing a meaningless tmm_ni=0 in every breakdown log on those targets.

♻️ Proposed fix
     if (nodeInfoPayload) {
         if (nodeInfoPayloadFromPsram)
             free(nodeInfoPayload);
         else
             delete[] nodeInfoPayload;
         nodeInfoPayload = nullptr;
+#if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
+        memaudit::set("tmm_ni", 0);
+#endif
     }
-    memaudit::set("tmm_ni", 0);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/TrafficManagementModule.cpp` around lines 207 - 215, The cleanup
in TrafficManagementModule’s destructor clears the memaudit tag unconditionally,
unlike the guarded set logic used earlier for tmm_ni. Update the teardown path
around the nodeInfoPayload cleanup to wrap memaudit::set("tmm_ni", 0) with the
same ARCH_ESP32 and BOARD_HAS_PSRAM preprocessor guard used when setting tmm_ni,
so non-PSRAM builds do not register or retain that tag.
src/mesh/NodeDB.cpp (1)

1801-1803: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Trim comment to comply with comment-length guideline.

This explanatory block is 3 lines; guidelines cap comments at one or two lines.

As per coding guidelines, "Keep code comments minimal: one or two lines at most, only explain the why when it is not obvious, and avoid multi-paragraph explanatory comments."

✏️ Suggested trim
-    // Approximate satellite heap usage: each std::map entry is one rb-tree node,
-    // value_type plus ~44 B of node overhead (parent/left/right pointers, color,
-    // allocator rounding on 32-bit targets - an estimate, not exact bookkeeping).
+    // Approximate satellite heap usage (rb-tree node overhead ~44 B/entry, not exact).
     size_t satBytes = 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mesh/NodeDB.cpp` around lines 1801 - 1803, Shorten the explanatory
comment in NodeDB’s heap-usage note to fit the one- or two-line guideline; keep
only the essential why/explanation and remove the extra detail about rb-tree
internals, node overhead, and allocator rounding from the comment block near the
heap usage estimate.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/mesh/MemoryPool.h`:
- Line 153: Guard MemoryPool::release() against already-free slots before
changing audit state. In the release path, after the existing range/null checks
and before auditAdd on the freed slot, make sure the slot is still marked used
in the used[] tracking for MemoryPool<T>; if it is already free, return without
touching the audit counter. Update the logic around the assert(used[index])
check so release() does not decrement audit state in release builds for
double-frees.

---

Nitpick comments:
In `@src/memory/MemAudit.cpp`:
- Around line 93-112: The MemAudit breakdown logging assumes tags are at most 16
characters, but that constraint is not enforced where tags are registered. Add
an explicit validation in the tag registration path used by add, set, and
findOrRegister, or a static_assert if the limit is compile-time guaranteed, so
oversized tags are rejected or flagged instead of silently truncating the
output. Keep the check close to the tag creation logic in MemAudit and ensure
the existing logBreakdown buffer sizing comment matches the enforced limit.

In `@src/memory/MemAudit.h`:
- Around line 7-27: Trim the top-of-file comment in MemAudit.h to a minimal one-
or two-line summary and remove the multi-paragraph design rationale, log
example, concurrency details, and compilation notes. Keep only the essential
purpose of MemAudit and any non-obvious constraint that callers must know, and
move the deeper explanation about relaxed atomics, tag immutability,
registration behavior, and no-op stubs into external documentation or the PR
description.

In `@src/mesh/NodeDB.cpp`:
- Around line 1801-1803: Shorten the explanatory comment in NodeDB’s heap-usage
note to fit the one- or two-line guideline; keep only the essential
why/explanation and remove the extra detail about rb-tree internals, node
overhead, and allocator rounding from the comment block near the heap usage
estimate.

In `@src/modules/TrafficManagementModule.cpp`:
- Around line 207-215: The cleanup in TrafficManagementModule’s destructor
clears the memaudit tag unconditionally, unlike the guarded set logic used
earlier for tmm_ni. Update the teardown path around the nodeInfoPayload cleanup
to wrap memaudit::set("tmm_ni", 0) with the same ARCH_ESP32 and BOARD_HAS_PSRAM
preprocessor guard used when setting tmm_ni, so non-PSRAM builds do not register
or retain that tag.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ec9a7b6f-12e5-4969-ba8b-7a73e2d76ed3

📥 Commits

Reviewing files that changed from the base of the PR and between 93250d8 and 87cc932.

📒 Files selected for processing (14)
  • src/MessageStore.cpp
  • src/graphics/TFTDisplay.cpp
  • src/main.cpp
  • src/memGet.cpp
  • src/memory/MemAudit.cpp
  • src/memory/MemAudit.h
  • src/mesh/MemoryPool.h
  • src/mesh/NodeDB.cpp
  • src/mesh/PacketHistory.cpp
  • src/mesh/Router.cpp
  • src/mesh/WarmNodeStore.cpp
  • src/modules/TrafficManagementModule.cpp
  • test/native-suite-count
  • test/test_mem_audit/test_main.cpp

Comment thread src/mesh/MemoryPool.h
if (index >= 0 && index < MaxSize) {
assert(used[index]); // Should be marked as used
used[index] = false;
this->auditAdd(-(int32_t)sizeof(T));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '130,170p' src/mesh/MemoryPool.h

Repository: meshtastic/firmware

Length of output: 1578


🏁 Script executed:

#!/bin/bash
sed -n '170,220p' src/mesh/MemoryPool.h

Repository: meshtastic/firmware

Length of output: 389


Guard release() against already-free slots before adjusting audit state. alloc() already skips auditAdd() when the pool is full, but release() only checks range/null and then decrements after an assert(used[index]). In release builds, an already-free slot would still change the counter and can drift over time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mesh/MemoryPool.h` at line 153, Guard MemoryPool::release() against
already-free slots before changing audit state. In the release path, after the
existing range/null checks and before auditAdd on the freed slot, make sure the
slot is still marked used in the used[] tracking for MemoryPool<T>; if it is
already free, return without touching the audit counter. Update the logic around
the assert(used[index]) check so release() does not decrement audit state in
release builds for double-frees.

The wasm env denies all sources and adds an explicit file list; MemAudit
callers (main, MeshService, NodeDB, PacketHistory) are in that list but
src/memory/MemAudit.cpp was not, so wasm-ld failed on undefined
memaudit:: symbols.
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Firmware Size Report

22 targets | vs develop: 21 increased, 1 decreased, net +408,000 (+398.4 KB)

Target Size vs develop
picow 1,273,032 📈 +28,152 (+27.5 KB)
pico2w 1,247,572 📈 +27,056 (+26.4 KB)
pico 809,408 📈 +26,720 (+26.1 KB)
rak11310 832,144 📈 +26,720 (+26.1 KB)
seeed_xiao_rp2040 807,608 📈 +26,704 (+26.1 KB)
Show 17 more target(s)
Target Size vs develop
pico2 795,576 📈 +25,800 (+25.2 KB)
seeed_xiao_rp2350 793,736 📈 +25,800 (+25.2 KB)
t-deck-tft 3,833,024 📈 +22,352 (+21.8 KB)
heltec-vision-master-e213-inkhud 2,248,592 📈 +21,232 (+20.7 KB)
elecrow-adv-35-tft 3,436,368 📈 +19,744 (+19.3 KB)
seeed-xiao-s3 2,293,520 📈 +17,376 (+17.0 KB)
heltec-ht62-esp32c3-sx1262 2,151,584 📈 +16,592 (+16.2 KB)
heltec-v3 2,280,528 📈 +16,448 (+16.1 KB)
rak3312 2,288,688 📈 +16,448 (+16.1 KB)
rak11200 1,876,912 📈 +16,304 (+15.9 KB)
station-g3 2,282,240 📈 +15,888 (+15.5 KB)
station-g2 2,282,224 📈 +15,872 (+15.5 KB)
t-eth-elite 2,507,088 📈 +15,600 (+15.2 KB)
heltec-v4 2,292,576 📈 +15,280 (+14.9 KB)
tlora-c6 2,383,168 📈 +14,864 (+14.5 KB)
rak3172 182,512 📉 -3,784 (-3.7 KB)
wio-e5 239,460 📈 +832

Updated for 5d3df00

@thebentern

Copy link
Copy Markdown
Contributor Author

On hardware (rak4631, 120-node NodeDB): the boot line renders exactly as designed and the arithmetic checks out against the ELF and live heap telemetry:

INFO | MemAudit[boot]: msgstore=2200 warm=4000 nodedb=11040 satmaps=17864 pkthist=4800 tmm=2500 total=42404

It already earned its keep twice during validation: it exposed that the satellite maps (satmaps=17864) are roughly double the prior estimate and now the largest single boot-time consumer, and it made the per-PR savings verifiable from a single serial line — which is precisely the field-report workflow this PR is for.

@thebentern
thebentern merged commit 6c7ee8a into develop Jul 6, 2026
94 checks passed
@thebentern
thebentern deleted the feat/memaudit-heap-accounting branch July 6, 2026 18:18
Itzdavid01 pushed a commit to Itzdavid01/firmware that referenced this pull request Sep 5, 2026
…ic#10900)

* Add MemAudit: per-subsystem heap accounting in the boot log

The 2.8.0 nRF52840 heap-exhaustion field reports had to be diagnosed by
hand, reconstructing each subsystem's heap footprint from source and
build flags one report at a time. This makes every future report
self-diagnosing from the serial log: a tiny fixed-size registry
(src/memory/MemAudit.*) that big long-lived allocations report into,
printed as one line at the end of setup() and alongside the periodic
"Heap free:" log:

  MemAudit[boot]: tmm=2500 warm=4000 pkthist=5824 nodedb=13440
  msgstore=2200 pktpool(live)=3270 total=31234

Instrumented: NodeDB hot vector (nodedb) + satellite maps (satmaps,
rb-tree overhead estimated), WarmNodeStore (warm), PacketHistory records
and hash index (pkthist), TrafficManagement caches (tmm/tmm_ni),
MessageStore text pool (msgstore), TFT line/repaint buffers (display),
and live in-flight packets (pktpool(live)) via an optional audit tag on
the packet pool allocator - the one hot path, counted with a relaxed
32-bit atomic add (single instructions on Cortex-M, no locks).

Cost: 128 B RAM for the 16-tag table, well under 1 KB flash on rak4631.
MESHTASTIC_MEM_AUDIT=0 compiles it out to inline no-op stubs (call
sites need no ifdefs); STM32WL, the tightest flash target, defaults off.

New native suite test_mem_audit covers add/set/snapshot arithmetic, tag
reuse (pointer and cross-TU strcmp fallback), null/unknown tags, and
table-full behavior; test/native-suite-count bumped to 28.

* native-wasm: add src/memory/ to the curated source filter

The wasm env denies all sources and adds an explicit file list; MemAudit
callers (main, MeshService, NodeDB, PacketHistory) are in that list but
src/memory/MemAudit.cpp was not, so wasm-ld failed on undefined
memaudit:: symbols.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant