diff --git a/cores/esp8266/Esp-frag.cpp b/cores/esp8266/Esp-frag.cpp new file mode 100644 index 000000000..34185a472 --- /dev/null +++ b/cores/esp8266/Esp-frag.cpp @@ -0,0 +1,48 @@ +/* + Esp.cpp - ESP8266-specific APIs + Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "umm_malloc/umm_malloc.h" +#include "umm_malloc/umm_malloc_cfg.h" +#include "coredecls.h" +#include "Esp.h" + +void EspClass::getHeapStats(uint32_t* hfree, uint16_t* hmax, uint8_t* hfrag) +{ + // L2 / Euclidian norm of free block sizes. + // Having getFreeHeap()=sum(hole-size), fragmentation is given by + // 100 * (1 - sqrt(sum(hole-size²)) / sum(hole-size)) + + umm_info(NULL, 0); + uint8_t block_size = umm_block_size(); + uint32_t fh = ummHeapInfo.freeBlocks * block_size; + if (hfree) + *hfree = fh; + if (hmax) + *hmax = ummHeapInfo.maxFreeContiguousBlocks * block_size; + if (hfrag) + *hfrag = 100 - (sqrt32(ummHeapInfo.freeSize2) * 100) / fh; +} + +uint8_t EspClass::getHeapFragmentation() +{ + uint8_t hfrag; + getHeapStats(nullptr, nullptr, &hfrag); + return hfrag; +} diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index 055ea7463..bc90bb416 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -24,6 +24,7 @@ #include #include "interrupts.h" #include "MD5Builder.h" +#include "umm_malloc/umm_malloc.h" extern "C" { #include "user_interface.h" @@ -171,6 +172,11 @@ uint32_t EspClass::getFreeHeap(void) return system_get_free_heap_size(); } +uint16_t EspClass::getMaxFreeBlockSize(void) +{ + return umm_max_block_size(); +} + uint32_t EspClass::getChipId(void) { return system_get_chip_id(); diff --git a/cores/esp8266/Esp.h b/cores/esp8266/Esp.h index 60747251c..226119f07 100644 --- a/cores/esp8266/Esp.h +++ b/cores/esp8266/Esp.h @@ -103,10 +103,13 @@ class EspClass { void restart(); uint16_t getVcc(); - uint32_t getFreeHeap(); - uint32_t getChipId(); + uint32_t getFreeHeap(); + uint16_t getMaxFreeBlockSize(); + uint8_t getHeapFragmentation(); // in % + void getHeapStats(uint32_t* free = nullptr, uint16_t* max = nullptr, uint8_t* frag = nullptr); + const char * getSdkVersion(); String getCoreVersion(); String getFullVersion(); diff --git a/cores/esp8266/coredecls.h b/cores/esp8266/coredecls.h index 39cedf557..3ac87eee9 100644 --- a/cores/esp8266/coredecls.h +++ b/cores/esp8266/coredecls.h @@ -8,6 +8,7 @@ extern "C" { // TODO: put declarations here, get rid of -Wno-implicit-function-declaration +#include #include // g_pcont declaration extern bool timeshift64_is_set; @@ -18,6 +19,8 @@ void tune_timeshift64 (uint64_t now_us); void settimeofday_cb (void (*cb)(void)); void disable_extra4k_at_link_time (void) __attribute__((noinline)); +uint32_t sqrt32 (uint32_t n); + #ifdef __cplusplus } #endif diff --git a/cores/esp8266/sqrt32.c b/cores/esp8266/sqrt32.c new file mode 100644 index 000000000..af6ffa495 --- /dev/null +++ b/cores/esp8266/sqrt32.c @@ -0,0 +1,56 @@ + +#include +#include + +uint32_t sqrt32 (uint32_t n) +{ + // http://www.codecodex.com/wiki/Calculate_an_integer_square_root#C + // Another very fast algorithm donated by Tristan.Muntsinger@gmail.com + // (note: tested across the full 32 bits range, see comment below) + + // 15 iterations (c=1<<15) + + unsigned int c = 0x8000; + unsigned int g = 0x8000; + + for(;;) + { + if (g*g > n) + g ^= c; + c >>= 1; + if (!c) + return g; + g |= c; + } +} + +/* + * tested with: + * + +#include +#include +#include + +int main (void) +{ + for (uint32_t i = 0; ++i; ) + { + uint32_t sr = sqrt32(i); + uint32_t ifsr = sqrt(i); + + if (ifsr != sr) + printf("%d: i%d f%d\n", i, sr, ifsr); + + if (!(i & 0xffffff)) + { + printf("%i%% (0x%08x)\r", ((i >> 16) * 100) >> 16, i); + fflush(stdout); + } + } + + printf("\n"); +} + + * + */ diff --git a/cores/esp8266/umm_malloc/umm_malloc.c b/cores/esp8266/umm_malloc/umm_malloc.c index d65eac781..c86142b27 100644 --- a/cores/esp8266/umm_malloc/umm_malloc.c +++ b/cores/esp8266/umm_malloc/umm_malloc.c @@ -1024,6 +1024,10 @@ void ICACHE_FLASH_ATTR *umm_info( void *ptr, int force ) { if( UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK ) { ++ummHeapInfo.freeEntries; ummHeapInfo.freeBlocks += curBlocks; + ummHeapInfo.freeSize2 += (unsigned int)curBlocks + * (unsigned int)sizeof(umm_block) + * (unsigned int)curBlocks + * (unsigned int)sizeof(umm_block); if (ummHeapInfo.maxFreeContiguousBlocks < curBlocks) { ummHeapInfo.maxFreeContiguousBlocks = curBlocks; @@ -1761,4 +1765,13 @@ size_t ICACHE_FLASH_ATTR umm_free_heap_size( void ) { return (size_t)ummHeapInfo.freeBlocks * sizeof(umm_block); } +size_t ICACHE_FLASH_ATTR umm_max_block_size( void ) { + umm_info(NULL, 0); + return ummHeapInfo.maxFreeContiguousBlocks * sizeof(umm_block); +} + +size_t ICACHE_FLASH_ATTR umm_block_size( void ) { + return sizeof(umm_block); +} + /* ------------------------------------------------------------------------ */ diff --git a/cores/esp8266/umm_malloc/umm_malloc.h b/cores/esp8266/umm_malloc/umm_malloc.h index 0019b0ec5..fcb1ade82 100644 --- a/cores/esp8266/umm_malloc/umm_malloc.h +++ b/cores/esp8266/umm_malloc/umm_malloc.h @@ -26,6 +26,8 @@ typedef struct UMM_HEAP_INFO_t { unsigned short int freeBlocks; unsigned short int maxFreeContiguousBlocks; + + unsigned int freeSize2; } UMM_HEAP_INFO; @@ -41,6 +43,8 @@ void *umm_realloc( void *ptr, size_t size ); void umm_free( void *ptr ); size_t umm_free_heap_size( void ); +size_t umm_max_block_size( void ); +size_t umm_block_size( void ); #ifdef __cplusplus } diff --git a/doc/faq/a02-my-esp-crashes.rst b/doc/faq/a02-my-esp-crashes.rst index cb60e6b32..460b1a294 100644 --- a/doc/faq/a02-my-esp-crashes.rst +++ b/doc/faq/a02-my-esp-crashes.rst @@ -291,9 +291,12 @@ Memory, memory, memory rely on exceptions for error handling, which is not available for the ESP, and in any case there is no access to the underlying code. - Instrumenting the code with the OOM debug option and calls to ``ESP.getFreeHeap()`` will - help the process of finding leaks. Now is time to re-read about the - `exception decoder <#exception-decoder>`__. + Instrumenting the code with the OOM debug option and calls to + ``ESP.getFreeHeap()`` / ``ESP.getHeapFragmentation()`` / + ``ESP.getMaxFreeBlockSize()`` will help the process of finding memory issues. + + Now is time to re-read about the `exception decoder + <#exception-decoder>`__. *Some techniques for reducing memory usage* diff --git a/doc/libraries.rst b/doc/libraries.rst index ce22cb23a..0c2158016 100644 --- a/doc/libraries.rst +++ b/doc/libraries.rst @@ -83,6 +83,10 @@ Some ESP-specific APIs related to deep sleep, RTC and flash memories are availab ``ESP.getFreeHeap()`` returns the free heap size. +``ESP.getHeapFragmentation()`` returns the fragmentation metric (0% is clean, more than ~50% is not harmless) + +``ESP.getMaxFreeBlockSize()`` returns the maximum allocatable ram block regarding heap fragmentation + ``ESP.getChipId()`` returns the ESP8266 chip ID as a 32-bit integer. ``ESP.getCoreVersion()`` returns a String containing the core version. diff --git a/libraries/esp8266/examples/HeapMetric/HeapMetric.ino b/libraries/esp8266/examples/HeapMetric/HeapMetric.ino new file mode 100644 index 000000000..c9ad8abba --- /dev/null +++ b/libraries/esp8266/examples/HeapMetric/HeapMetric.ino @@ -0,0 +1,79 @@ + +// nothing else than showing heap metric usage +// released to public domain + +#include + +void stats(const char* what) { + // we could use getFreeHeap() getMaxFreeBlockSize() and getHeapFragmentation() + // or all at once: + uint32_t free; + uint16_t max; + uint8_t frag; + ESP.getHeapStats(&free, &max, &frag); + + Serial.printf("free: %5d - max: %5d - frag: %3d%% <- ", free, max, frag); + // %s requires a malloc that could fail, using println instead: + Serial.println(what); +} + +void tryit(int blocksize) { + void** p; + int blocks; + + // heap-used ~= blocks*sizeof(void*) + blocks*blocksize + blocks = ((ESP.getMaxFreeBlockSize() / (blocksize + sizeof(void*))) + 3) & ~3; // rounded up, multiple of 4 + + Serial.printf("\nFilling memory with blocks of %d bytes each\n", blocksize); + stats("before"); + + p = (void**)malloc(sizeof(void*) * blocks); + for (int i = 0; i < blocks; i++) { + p[i] = malloc(blocksize); + } + stats("array and blocks allocation"); + + for (int i = 0; i < blocks; i += 2) { + if (p[i]) { + free(p[i]); + } + p[i] = nullptr; + } + stats("freeing every other blocks"); + + for (int i = 0; i < blocks; i += 4) { + if (p[i + 1]) { + free(p[i + 1]); + } + p[i + 1] = nullptr; + } + stats("freeing every other remaining blocks"); + + for (int i = 0; i < blocks; i++) { + if (p[i]) { + free(p[i]); + } + } + stats("freeing array"); + + free(p); + stats("after"); +} + +void setup() { + Serial.begin(115200); + WiFi.mode(WIFI_OFF); + + tryit(8000); + tryit(4000); + tryit(2000); + tryit(1000); + tryit(500); + tryit(200); + tryit(100); + tryit(50); + tryit(15); +} + +void loop() { +} diff --git a/libraries/esp8266/keywords.txt b/libraries/esp8266/keywords.txt index 91d90b0ad..f6bb96b5d 100644 --- a/libraries/esp8266/keywords.txt +++ b/libraries/esp8266/keywords.txt @@ -29,9 +29,12 @@ reset KEYWORD2 restart KEYWORD2 getVcc KEYWORD2 getFreeHeap KEYWORD2 +getHeapFragmentation KEYWORD2 +getMaxFreeBlockSize KEYWORD2 getChipId KEYWORD2 getSdkVersion KEYWORD2 getCoreVersion KEYWORD2 +getFullVersion KEYWORD2 getBootVersion KEYWORD2 getBootMode KEYWORD2 getCpuFreqMHz KEYWORD2