Files
firmware/src/memGet.cpp
T
Jonathan BennettandClaude Opus 5 43155f3f90 feat: log heap watermark, largest free block and subsystem breakdown (#11660)
* feat: log heap watermark, largest free block and subsystem breakdown

The periodic heap line reported only free/total, which cannot distinguish a
leak from fragmentation, and the MemAudit per-subsystem breakdown was only
ever printed at boot.

Add ESP.getMinFreeHeap()/getMaxAllocHeap() wrappers to MemGet (0 on platforms
that cannot report them) and include both in the 5-minute line, then log the
MemAudit breakdown on the same tick. A falling watermark is a leak; a steady
watermark with a shrinking largest block is fragmentation, and the breakdown
names the tagged subsystem that moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E9ZacpGtsA6DWavqr5Ty2i

* docs: correct the watermark interpretation in logHeapUsage comment

A single step down in the minimum-free watermark is a transient allocation,
not proof of a leak; it takes repeated new lows across samples.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E9ZacpGtsA6DWavqr5Ty2i

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-03 08:52:55 +00:00

139 lines
3.7 KiB
C++

/**
* @file memGet.cpp
* @brief Implementation of MemGet class that provides functions to get memory information.
*
* This file contains the implementation of MemGet class that provides functions to get
* information about free heap, heap size, free psram and psram size. The functions are
* implemented for ESP32 and NRF52 architectures. If the platform does not have heap
* management function implemented, the functions return UINT32_MAX or 0.
*/
#include "memGet.h"
#include "configuration.h"
#if defined(MESHTASTIC_DYNAMIC_SBRK_HEAP)
#include <malloc.h>
#include <unistd.h> // sbrk
#if defined(ARCH_STM32)
// Returns the uncommitted sbrk headroom: addressable space between the current heap
// break and the stack pointer that has not yet been committed to the arena.
static uint32_t sbrkHeadroom()
{
// defined in STM32 linker script
extern char _estack;
extern char _Min_Stack_Size;
// Both are linker-script symbols rather than real objects: _estack is the top-of-RAM address and
// _Min_Stack_Size is a plain value. Convert each to an integer before subtracting - differencing the
// pointers themselves is pointer arithmetic across unrelated objects.
uint32_t max_sp = (uint32_t)&_estack - (uint32_t)&_Min_Stack_Size;
uint32_t heap_end = (uint32_t)sbrk(0);
return (max_sp > heap_end) ? (max_sp - heap_end) : 0;
}
#else
#error Unsupported architecture!
#endif
#endif
MemGet memGet;
/**
* Returns the amount of free heap memory in bytes.
* @return uint32_t The amount of free heap memory in bytes.
*/
uint32_t MemGet::getFreeHeap()
{
#ifdef ARCH_ESP32
return ESP.getFreeHeap();
#elif defined(ARCH_NRF52)
return dbgHeapFree();
#elif defined(ARCH_RP2040)
return rp2040.getFreeHeap();
#elif defined(MESHTASTIC_DYNAMIC_SBRK_HEAP) // Currently: ARCH_STM32WL
struct mallinfo m = mallinfo();
return m.fordblks + sbrkHeadroom(); // Free space within arena + uncommitted sbrk headroom
#else
// this platform does not have heap management function implemented
return UINT32_MAX;
#endif
}
/**
* Returns the size of the heap memory in bytes.
* @return uint32_t The size of the heap memory in bytes.
*/
uint32_t MemGet::getHeapSize()
{
#ifdef ARCH_ESP32
return ESP.getHeapSize();
#elif defined(ARCH_NRF52)
return dbgHeapTotal();
#elif defined(ARCH_RP2040)
return rp2040.getTotalHeap();
#elif defined(MESHTASTIC_DYNAMIC_SBRK_HEAP) // Currently: ARCH_STM32WL
struct mallinfo m = mallinfo();
return m.arena + sbrkHeadroom(); // Non-mmapped space allocated + uncommitted sbrk headroom
#else
// this platform does not have heap management function implemented
return UINT32_MAX;
#endif
}
/**
* Returns the lowest the free heap has ever been since boot.
* @return uint32_t Low watermark in bytes, or 0 if the platform can't report it.
*/
uint32_t MemGet::getMinFreeHeap()
{
#ifdef ARCH_ESP32
return ESP.getMinFreeHeap();
#else
return 0;
#endif
}
/**
* Returns the largest contiguous block malloc() could still return.
* @return uint32_t Block size in bytes, or 0 if the platform can't report it.
*/
uint32_t MemGet::getMaxAllocHeap()
{
#ifdef ARCH_ESP32
return ESP.getMaxAllocHeap();
#else
return 0;
#endif
}
/**
* Returns the amount of free psram memory in bytes.
*
* @return The amount of free psram memory in bytes.
*/
uint32_t MemGet::getFreePsram()
{
#ifdef ARCH_ESP32
return ESP.getFreePsram();
#elif defined(ARCH_PORTDUINO)
return 4194252;
#else
return 0;
#endif
}
/**
* @brief Returns the size of the PSRAM memory.
*
* @return uint32_t The size of the PSRAM memory.
*/
uint32_t MemGet::getPsramSize()
{
#ifdef ARCH_ESP32
return ESP.getPsramSize();
#elif defined(ARCH_PORTDUINO)
return 4194252;
#else
return 0;
#endif
}