From 1eaa680e26d2de4002d3d51cc162d4df5b997834 Mon Sep 17 00:00:00 2001 From: agessaman Date: Sat, 11 Jul 2026 12:14:46 -0700 Subject: [PATCH] chore(mqtt): silence periodic stats log for production; add get mqtt.stats CLI The 30s "MQTT: Memory" line was useful during the outbox/sync-publish investigation but is spam for production. Gate the periodic logMemoryStatus() call in the MQTT task loop behind MQTT_MEMORY_DEBUG (a dedicated diagnostics flag, not enabled by plain MQTT_DEBUG or production builds) and stop the heltec_v3 variant from force-enabling MQTT_MEMORY_DEBUG on its observer_mqtt envs (now commented out to match heltec_v4). logMemoryStatus() itself is kept intact for opt-in debugging. Expose the same data on demand via a new `get mqtt.stats` CLI command backed by MQTTBridge::formatMqttStatsReply(): free/max heap, queue depth, outbox total, and per-slot publish ok/err counts (1-based, matching the msgs: line). Fits the 160-byte reply buffer at 6 slots; returns "(bridge not running)" when down. --- src/helpers/CommonCLI_Observer.cpp | 2 ++ src/helpers/bridges/MQTTBridge.cpp | 48 ++++++++++++++++++++++++++++-- src/helpers/bridges/MQTTBridge.h | 3 ++ variants/heltec_v3/platformio.ini | 6 ++-- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/helpers/CommonCLI_Observer.cpp b/src/helpers/CommonCLI_Observer.cpp index 3b4219f3..695aacef 100644 --- a/src/helpers/CommonCLI_Observer.cpp +++ b/src/helpers/CommonCLI_Observer.cpp @@ -695,6 +695,8 @@ bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* conf start = (int)_atoi(start_arg); } formatMQTTPresetListReply(reply, 160, start); + } else if (memcmp(config, "mqtt.stats", 10) == 0) { + MQTTBridge::formatMqttStatsReply(reply, 160); } else if (memcmp(config, "mqtt.status", 11) == 0) { MQTTBridge::formatMqttStatusReply(reply, 160, &_mqtt_prefs); } else if (memcmp(config, "mqtt.packets", 12) == 0) { diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index 1682c1de..15104a65 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -250,6 +250,45 @@ void MQTTBridge::formatMqttStatusReply(char* buf, size_t bufsize, const MQTTPref snprintf(buf + pos, bufsize - pos, ", q:%d", q); } +// On-demand publish-health + heap snapshot for the `get mqtt.stats` CLI command. +// Same data as the (MQTT_MEMORY_DEBUG-only) periodic logMemoryStatus() line, but +// returned as a reply instead of logged. Per-slot "sN=ok/err": ok = cumulative +// accepted publishes, err = cumulative failures (socket error / network timeout). +// Outbox should read ~0 (QoS0 publishes synchronously); a rising err isolates a +// broker whose uplink is dropping writes. +void MQTTBridge::formatMqttStatsReply(char* buf, size_t bufsize) { + if (buf == nullptr || bufsize == 0) return; + if (s_mqtt_bridge_instance == nullptr || !s_mqtt_bridge_instance->_initialized) { + snprintf(buf, bufsize, "> (bridge not running)"); + return; + } + MQTTBridge* b = s_mqtt_bridge_instance; + + int q = 0; +#ifdef ESP_PLATFORM + if (b->_packet_queue_handle != nullptr) { + q = (int)uxQueueMessagesWaiting(b->_packet_queue_handle); + } +#else + q = b->_queue_count; +#endif + + size_t outbox_total = 0; + for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) { + if (b->_slots[i].client) outbox_total += b->_slots[i].client->getOutboxSize(); + } + + int pos = snprintf(buf, bufsize, "> Free=%d Max=%d q:%d/%d Outbox=%u |", + (int)ESP.getFreeHeap(), (int)ESP.getMaxAllocHeap(), + q, MAX_QUEUE_SIZE, (unsigned)outbox_total); + for (int i = 0; i < RUNTIME_MQTT_SLOTS && pos < (int)bufsize - 1; i++) { + if (!b->_slots[i].enabled || !b->_slots[i].client) continue; + pos += snprintf(buf + pos, bufsize - pos, " s%d=%lu/%lu", i + 1, + b->_slots[i].client->getPublishOk(), + b->_slots[i].client->getPublishErr()); + } +} + uint8_t MQTTBridge::getLastWifiDisconnectReason() { return s_wifi_disconnect_reason; } unsigned long MQTTBridge::getLastWifiDisconnectTime() { return s_wifi_disconnect_time; } @@ -899,14 +938,17 @@ void MQTTBridge::mqttTaskLoop() { unsigned long now = millis(); - // Periodic heap + outbox snapshot (compiles to nothing unless MQTT_DEBUG is set). - // Outbox= is the value to watch: it should sit near 0 on a healthy uplink and - // plateau at the configured cap (not climb) during a stall. + // Periodic heap + publish-health snapshot. Gated behind MQTT_MEMORY_DEBUG (a + // dedicated diagnostics flag, NOT enabled on production or plain MQTT_DEBUG builds) + // so it stays off by default — the same data is available on demand via the + // `get mqtt.stats` CLI command (formatMqttStatsReply / logMemoryStatus()). + #ifdef MQTT_MEMORY_DEBUG static unsigned long last_mem_log = 0; if (now - last_mem_log >= 30000) { last_mem_log = now; logMemoryStatus(); } + #endif bool wifi_just_connected = handleWiFiConnection(now); if (wifi_just_connected) { diff --git a/src/helpers/bridges/MQTTBridge.h b/src/helpers/bridges/MQTTBridge.h index 377ea51c..69782bd4 100644 --- a/src/helpers/bridges/MQTTBridge.h +++ b/src/helpers/bridges/MQTTBridge.h @@ -468,6 +468,9 @@ public: * (for LoRa). Returns false if the bridge is not running. */ bool ntpDiag(char* reply, size_t reply_size, bool verbose); static void formatMqttStatusReply(char* buf, size_t bufsize, const MQTTPrefs* obs); + /** On-demand publish-health + heap snapshot for `get mqtt.stats` (per-slot ok/err, + * outbox size, free/max heap, queue depth). */ + static void formatMqttStatsReply(char* buf, size_t bufsize); /** True when WiFi is set and at least one MQTT slot can run (preset + custom host if needed). */ static bool isConfigValid(const MQTTPrefs* obs); static void formatSlotDiagReply(char* buf, size_t bufsize, int slot_index); diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 4bd3819a..2d13237b 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -127,7 +127,8 @@ build_flags = -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 - -D MQTT_MEMORY_DEBUG=1 +; Periodic 30s heap/pub-stats serial log — enable only for debugging (use `get mqtt.stats` on demand instead). +; -D MQTT_MEMORY_DEBUG=1 ; Keep default observer profile less verbose to reduce runtime contention. ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -486,7 +487,8 @@ build_flags = -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 - -D MQTT_MEMORY_DEBUG=1 +; Periodic 30s heap/pub-stats serial log — enable only for debugging (use `get mqtt.stats` on demand instead). +; -D MQTT_MEMORY_DEBUG=1 ; Keep default observer profile less verbose to reduce runtime contention. ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1