From dfd3a14abc37b9965b070d39ce329d065af74eef Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Mon, 24 Jun 2019 00:15:03 +0300 Subject: [PATCH] Terminal: heap fragmentation stat (#1740) * debug: heap fragmentation stat * Rework method detection - return check integral type as a result of detection instead of method type - type-tagging instead of enable_if * fix alias * typo * only need one bool_type instance * Move heap debug into HeapStats header - don't show fragmentation stats on the first call - prettify fragmentation display - pack function params into helper struct - naming * pass external flag instead * add missing prototypes --- code/espurna/config/prototypes.h | 4 ++ code/espurna/espurna.ino | 7 +- code/espurna/libs/HeapStats.h | 114 +++++++++++++++++++++++++++++++ code/espurna/terminal.ino | 5 +- code/espurna/utils.ino | 41 ++++------- 5 files changed, 140 insertions(+), 31 deletions(-) create mode 100644 code/espurna/libs/HeapStats.h diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index 74bfcc8b..fa8760bc 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -66,6 +66,10 @@ extern "C" { #endif } +void infoMemory(const char* , unsigned int, unsigned int); +unsigned int getFreeHeap(); +unsigned int getInitialFreeHeap(); + // ----------------------------------------------------------------------------- // Domoticz // ----------------------------------------------------------------------------- diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index 852aa4d0..6992e216 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -22,6 +22,8 @@ along with this program. If not, see . #include "config/all.h" #include +#include "libs/HeapStats.h" + std::vector _loop_callbacks; std::vector _reload_callbacks; @@ -65,7 +67,7 @@ void setup() { // ------------------------------------------------------------------------- // Cache initial free heap value - getInitialFreeHeap(); + setInitialFreeHeap(); // Serial debug #if DEBUG_SUPPORT @@ -81,6 +83,9 @@ void setup() { // Init persistance settingsSetup(); + // Return bogus free heap value for broken devices + // XXX: device is likely to trigger other bugs! tread carefuly + wtfHeap(getSetting("wtfHeap", 0).toInt()); // Init Serial, SPIFFS and system check systemSetup(); diff --git a/code/espurna/libs/HeapStats.h b/code/espurna/libs/HeapStats.h new file mode 100644 index 00000000..41bca690 --- /dev/null +++ b/code/espurna/libs/HeapStats.h @@ -0,0 +1,114 @@ +/* + +Show extended heap stats when EspClass::getHeapStats() is available + +*/ + +#pragma once + +#include + +struct heap_stats_t { + uint32_t available; + uint16_t usable; + uint8_t frag_pct; +}; + +namespace EspClass_has_getHeapStats { + struct _detector { + template().getHeapStats(0,0,0))> + static std::true_type detect(int); + + template + static std::false_type detect(...); + }; + + template + struct detector : public _detector { + using result = decltype( + std::declval().detect(0)); + }; + + template + struct typed_check : public detector::result { + }; + + typed_check check{}; +}; + +template +void _getHeapStats(std::true_type&, T& instance, heap_stats_t& stats) { + instance.getHeapStats(&stats.available, &stats.usable, &stats.frag_pct); +} + +template +void _getHeapStats(std::false_type&, T& instance, heap_stats_t& stats) { + stats.available = instance.getFreeHeap(); + stats.usable = 0; + stats.frag_pct = 0; +} + +void getHeapStats(heap_stats_t& stats) { + _getHeapStats(EspClass_has_getHeapStats::check, ESP, stats); +} + +// WTF +// Calling ESP.getFreeHeap() is making the system crash on a specific +// AiLight bulb, but anywhere else it should work as expected +static bool _heap_value_wtf = false; + +heap_stats_t getHeapStats() { + heap_stats_t stats; + if (_heap_value_wtf) { + stats.available = 9999; + stats.usable = 9999; + stats.frag_pct = 0; + return stats; + } + getHeapStats(stats); + return stats; +} + +void wtfHeap(bool value) { + _heap_value_wtf = value; +} + +unsigned int getFreeHeap() { + return getHeapStats().available; +} + +static unsigned int _initial_heap_value = 0; +void setInitialFreeHeap() { + _initial_heap_value = getFreeHeap(); +} + +unsigned int getInitialFreeHeap() { + if (0 == _initial_heap_value) { + setInitialFreeHeap(); + } + return _initial_heap_value; +} + +void infoMemory(const char* name, const heap_stats_t& stats) { + infoMemory(name, getInitialFreeHeap(), stats.available); +} + +void infoHeapStats(const char* name, const heap_stats_t& stats) { + DEBUG_MSG_P( + PSTR("[MAIN] %-6s: %5u bytes available | %5u bytes lost (%2u%%) | %5u bytes free (%2u%%)\n"), + name, + stats.available, + (stats.available - stats.usable), + stats.frag_pct, + stats.usable, + (100 - stats.frag_pct) + ); +} + +void infoHeapStats(bool show_frag_stats = true) { + infoMemory("Heap", getHeapStats()); + if (show_frag_stats && EspClass_has_getHeapStats::check) { + infoHeapStats("Heap", getHeapStats()); + } +} diff --git a/code/espurna/terminal.ino b/code/espurna/terminal.ino index c67d6fd6..9081a4d8 100644 --- a/code/espurna/terminal.ino +++ b/code/espurna/terminal.ino @@ -12,6 +12,7 @@ Copyright (C) 2016-2019 by Xose Pérez #include "libs/EmbedisWrap.h" #include #include "libs/StreamInjector.h" +#include "libs/HeapStats.h" StreamInjector _serial = StreamInjector(TERMINAL_BUFFER_SIZE); EmbedisWrap embedis(_serial, TERMINAL_BUFFER_SIZE); @@ -124,12 +125,12 @@ void _terminalInitCommand() { }); terminalRegisterCommand(F("HEAP"), [](Embedis* e) { - infoMemory("Heap", getInitialFreeHeap(), getFreeHeap()); + infoHeapStats(); terminalOK(); }); terminalRegisterCommand(F("STACK"), [](Embedis* e) { - infoMemory("Stack", 4096, getFreeStack()); + infoMemory("Stack", CONT_STACKSIZE, getFreeStack()); terminalOK(); }); diff --git a/code/espurna/utils.ino b/code/espurna/utils.ino index ca280b8f..a0e9258f 100644 --- a/code/espurna/utils.ino +++ b/code/espurna/utils.ino @@ -7,6 +7,7 @@ Copyright (C) 2017-2019 by Xose Pérez */ #include +#include "libs/HeapStats.h" String getIdentifier() { char buffer[20]; @@ -63,26 +64,6 @@ unsigned char getHeartbeatInterval() { return getSetting("hbInterval", HEARTBEAT_INTERVAL).toInt(); } -// WTF -// Calling ESP.getFreeHeap() is making the system crash on a specific -// AiLight bulb, but anywhere else... -unsigned int getFreeHeap() { - if (getSetting("wtfHeap", 0).toInt() == 1) return 9999; - return ESP.getFreeHeap(); -} - -unsigned int getInitialFreeHeap() { - static unsigned int _heap = 0; - if (0 == _heap) { - _heap = getFreeHeap(); - } - return _heap; -} - -unsigned int getUsedHeap() { - return getInitialFreeHeap() - getFreeHeap(); -} - String getEspurnaModules() { return FPSTR(espurna_modules); } @@ -186,10 +167,10 @@ namespace Heartbeat { void heartbeat() { unsigned long uptime_seconds = getUptime(); - unsigned int free_heap = getFreeHeap(); - + heap_stats_t heap_stats = getHeapStats(); + UNUSED(uptime_seconds); - UNUSED(free_heap); + UNUSED(heap_stats); #if MQTT_SUPPORT unsigned char _heartbeat_mode = getHeartbeatMode(); @@ -204,7 +185,7 @@ void heartbeat() { if (serial) { DEBUG_MSG_P(PSTR("[MAIN] Uptime: %lu seconds\n"), uptime_seconds); - infoMemory("Heap", getInitialFreeHeap(), getFreeHeap()); + infoHeapStats(); #if ADC_MODE_VALUE == ADC_VCC DEBUG_MSG_P(PSTR("[MAIN] Power: %lu mV\n"), ESP.getVcc()); #endif @@ -264,7 +245,7 @@ void heartbeat() { #endif if (hb_cfg & Heartbeat::Freeheap) - mqttSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str()); + mqttSend(MQTT_TOPIC_FREEHEAP, String(heap_stats.available).c_str()); if (hb_cfg & Heartbeat::Relay) relayMQTT(); @@ -311,7 +292,7 @@ void heartbeat() { idbSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str()); if (hb_cfg & Heartbeat::Freeheap) - idbSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str()); + idbSend(MQTT_TOPIC_FREEHEAP, String(heap_stats.available).c_str()); if (hb_cfg & Heartbeat::Rssi) idbSend(MQTT_TOPIC_RSSI, String(WiFi.RSSI()).c_str()); @@ -455,11 +436,15 @@ void info() { // ------------------------------------------------------------------------- + static bool show_frag_stats = false; + infoMemory("EEPROM", SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE - settingsSize()); - infoMemory("Heap", getInitialFreeHeap(), getFreeHeap()); - infoMemory("Stack", 4096, getFreeStack()); + infoHeapStats(show_frag_stats); + infoMemory("Stack", CONT_STACKSIZE, getFreeStack()); DEBUG_MSG_P(PSTR("\n")); + show_frag_stats = true; + // ------------------------------------------------------------------------- DEBUG_MSG_P(PSTR("[MAIN] Boot version: %d\n"), ESP.getBootVersion());