Update when and how memory is allocated.

This commit is contained in:
Clive Blackledge 2025-10-01 00:31:27 -07:00
parent b206a69570
commit ba72308d0c
5 changed files with 122 additions and 29 deletions

View File

@ -53,12 +53,42 @@ extern MemGet memGet;
#define LOG_TRACE(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#else
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
#define LOG_DEBUG(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__)
#define LOG_INFO(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__)
#define LOG_WARN(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__)
#define LOG_ERROR(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_ERROR, __VA_ARGS__)
#define LOG_CRIT(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_CRIT, __VA_ARGS__)
#define LOG_TRACE(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_TRACE, __VA_ARGS__)
#define LOG_DEBUG(...) \
do { \
if (console) { \
console->log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__); \
} \
} while (0)
#define LOG_INFO(...) \
do { \
if (console) { \
console->log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__); \
} \
} while (0)
#define LOG_WARN(...) \
do { \
if (console) { \
console->log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__); \
} \
} while (0)
#define LOG_ERROR(...) \
do { \
if (console) { \
console->log(MESHTASTIC_LOG_LEVEL_ERROR, __VA_ARGS__); \
} \
} while (0)
#define LOG_CRIT(...) \
do { \
if (console) { \
console->log(MESHTASTIC_LOG_LEVEL_CRIT, __VA_ARGS__); \
} \
} while (0)
#define LOG_TRACE(...) \
do { \
if (console) { \
console->log(MESHTASTIC_LOG_LEVEL_TRACE, __VA_ARGS__); \
} \
} while (0)
#else
#define LOG_DEBUG(...)
#define LOG_INFO(...)
@ -70,7 +100,12 @@ extern MemGet memGet;
#endif
#if defined(DEBUG_HEAP)
#define LOG_HEAP(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__)
#define LOG_HEAP(...) \
do { \
if (console) { \
console->log(MESHTASTIC_LOG_LEVEL_HEAP, __VA_ARGS__); \
} \
} while (0)
// Macro-based heap debugging
#define DEBUG_HEAP_BEFORE auto heapBefore = memGet.getFreeHeap();
@ -195,4 +230,4 @@ class Syslog
bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0)));
};
#endif // HAS_NETWORKING
#endif // HAS_NETWORKING

View File

@ -294,7 +294,7 @@ void printInfo()
{
LOG_INFO("S:B:%d,%s,%s,%s", HW_VENDOR, optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO));
}
#ifndef PIO_UNIT_TESTING
#if !defined(PIO_UNIT_TESTING) || !(PIO_UNIT_TESTING)
void setup()
{
@ -1580,7 +1580,7 @@ void scannerToSensorsMap(const std::unique_ptr<ScanI2CTwoWire> &i2cScanner, Scan
}
#endif
#ifndef PIO_UNIT_TESTING
#if !defined(PIO_UNIT_TESTING) || !(PIO_UNIT_TESTING)
void loop()
{
runASAP = false;

View File

@ -38,6 +38,12 @@
#include <Preferences.h>
#include <esp_efuse.h>
#include <esp_efuse_table.h>
#if __has_include(<esp_ptr.h>)
#include <esp_ptr.h>
#define NODEDB_HAS_ESP_PTR 1
#else
#define NODEDB_HAS_ESP_PTR 0
#endif
#include <nvs_flash.h>
#include <soc/efuse_reg.h>
#include <soc/soc.h>
@ -68,19 +74,60 @@ meshtastic_DeviceUIConfig uiconfig{.screen_brightness = 153, .screen_timeout = 3
meshtastic_LocalModuleConfig moduleConfig;
meshtastic_ChannelFile channelFile;
#if defined(CONFIG_IDF_TARGET_ESP32S3)
//------------------------------------------------------------------------------
// Runtime instrumentation helpers
//------------------------------------------------------------------------------
// Hot/cold storage helpers -------------------------------------------------
// ESP32-S3 targets (Station G2 and similar) keep the heavy NodeInfoLite payloads
// in PSRAM while mirroring routing/UI critical fields in a compact DRAM cache.
// The helpers below keep both views in sync.
namespace
{
// Log the pool headroom every 100 inserts (and when we hit MAX) so field logs
// capture how close we are to exhausting heap/PSRAM on real hardware.
void logNodeInsertStats(size_t count, const char *poolLabel)
{
if (count == 0)
return;
if ((count % 100) != 0 && count != MAX_NUM_NODES)
return;
LOG_INFO("NodeDB %s pool usage %u/%u nodes, heap free %u, psram free %u", poolLabel, static_cast<unsigned>(count),
static_cast<unsigned>(MAX_NUM_NODES), memGet.getFreeHeap(), memGet.getFreePsram());
}
#if defined(CONFIG_IDF_TARGET_ESP32S3)
bool logPsramAllocationOnce(void *ptr, size_t capacity)
{
static bool logged = false;
if (logged || !ptr)
return logged;
#if NODEDB_HAS_ESP_PTR
bool inPsram = esp_ptr_external_ram(ptr);
#else
bool inPsram = false;
#endif
LOG_INFO("NodeDB PSRAM backing at %p (%s) capacity %u entries (~%u bytes)", ptr, inPsram ? "PSRAM" : "DRAM",
static_cast<unsigned>(capacity), static_cast<unsigned>(capacity * sizeof(meshtastic_NodeInfoLite)));
logged = true;
return logged;
}
#endif
} // namespace
#if defined(CONFIG_IDF_TARGET_ESP32S3)
void NodeDB::initHotCache()
{
// Pre-reserve the full cold store in PSRAM during boot so the high watermark
// shows up immediately in PSRAM usage logs and we avoid fragmented
// allocations later in the mission.
psramMeshNodes.resize(MAX_NUM_NODES);
hotNodes.resize(MAX_NUM_NODES);
hotDirty.assign(MAX_NUM_NODES, true);
meshNodes = &psramMeshNodes;
logPsramAllocationOnce(psramMeshNodes.data(), psramMeshNodes.capacity());
}
void NodeDB::refreshHotCache()
@ -2263,6 +2310,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
syncHotFromCold(index);
lite = &psramMeshNodes[index];
LOG_INFO("Adding node to database with %i nodes and %u bytes free!", numMeshNodes, memGet.getFreeHeap());
logNodeInsertStats(numMeshNodes, "PSRAM");
}
markHotDirty(lite);
@ -2314,6 +2362,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
memset(lite, 0, sizeof(*lite));
lite->num = n;
LOG_INFO("Adding node to database with %i nodes and %u bytes free!", numMeshNodes, memGet.getFreeHeap());
logNodeInsertStats(numMeshNodes, "Heap");
}
return lite;

View File

@ -43,7 +43,7 @@ Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
#if defined(CONFIG_IDF_TARGET_ESP32S3)
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)
// Try to put the heavy MeshPacket pool into PSRAM. If that fails we fall back to
// heap allocation so the radio stays functional (at the cost of fewer packets).
static PsramMemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> psramPool;
@ -51,6 +51,9 @@ static MemoryDynamic<meshtastic_MeshPacket> fallbackPool;
Allocator<meshtastic_MeshPacket> &packetPool = psramPool.isValid()
? static_cast<Allocator<meshtastic_MeshPacket> &>(psramPool)
: static_cast<Allocator<meshtastic_MeshPacket> &>(fallbackPool);
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
static MemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> staticPool;
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
#else
static MemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> staticPool;
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
@ -66,14 +69,6 @@ static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
*/
Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRADIO)
{
#if defined(CONFIG_IDF_TARGET_ESP32S3)
if (!psramPool.isValid()) {
LOG_WARN("PSRAM packet pool unavailable, falling back to heap allocations");
}
if (!has_psram() && MAX_RX_TOPHONE > get_rx_tophone_limit()) {
LOG_WARN("Detected <2MB PSRAM, limiting phone queue to %d packets", get_rx_tophone_limit());
}
#endif
// This is called pre main(), don't touch anything here, the following code is not safe
/* LOG_DEBUG("Size of NodeInfo %d", sizeof(NodeInfo));

View File

@ -32,7 +32,13 @@ inline bool has_psram(size_t minimumBytes = PSRAM_LARGE_THRESHOLD_BYTES)
inline int get_rx_tophone_limit()
{
#if defined(CONFIG_IDF_TARGET_ESP32S3)
return has_psram() ? 400 : 32;
#if defined(BOARD_MAX_RX_TOPHONE)
return BOARD_MAX_RX_TOPHONE;
#elif defined(BOARD_HAS_PSRAM)
return 800;
#else
return 32;
#endif
#elif defined(ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
return 8;
#else
@ -45,8 +51,14 @@ inline int get_rx_tophone_limit()
// RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0]))
#ifndef MAX_RX_TOPHONE
#if defined(CONFIG_IDF_TARGET_ESP32S3)
#if defined(BOARD_MAX_RX_TOPHONE)
#define MAX_RX_TOPHONE BOARD_MAX_RX_TOPHONE
#elif defined(BOARD_HAS_PSRAM)
static constexpr int MAX_RX_TOPHONE_WITH_PSRAM = 800;
#define MAX_RX_TOPHONE MAX_RX_TOPHONE_WITH_PSRAM
#else
#define MAX_RX_TOPHONE 32
#endif
#elif defined(ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#define MAX_RX_TOPHONE 8
#else
@ -80,13 +92,14 @@ static_assert(sizeof(meshtastic_NodeInfoLite) <= 200, "NodeInfoLite size increas
#elif defined(ARCH_NRF52)
#define MAX_NUM_NODES 80
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
#if defined(BOARD_MAX_NUM_NODES)
#define MAX_NUM_NODES BOARD_MAX_NUM_NODES
#elif defined(BOARD_HAS_PSRAM)
#define MAX_NUM_NODES 3000
#else
#include "Esp.h"
static inline int get_max_num_nodes()
{
if (has_psram()) {
return 5000;
}
uint32_t flash_size = ESP.getFlashChipSize() / (1024 * 1024); // Fallback based on flash size
if (flash_size >= 15) {
return 250;
@ -96,6 +109,7 @@ static inline int get_max_num_nodes()
return 100;
}
#define MAX_NUM_NODES get_max_num_nodes()
#endif
#else
#define MAX_NUM_NODES 100
#endif