Skip to content

Commit ce28a76

Browse files
d-a-vdevyte
authored andcommitted
metric for heap fragmentation (#5090)
* +Esp.getHeapUnfragness() * only in debug mode * default value * always enable, 64->32, light 32 integer square root, comments * fix when debugging is disabled * give credits * cosmetics * fragmentation metric updates (doc, better api, added getMaxFreeBlockSize()) * api reworked, +example * fixe types, fix names * coding style fix * use astyle for example
1 parent bbaea5a commit ce28a76

File tree

11 files changed

+227
-5
lines changed

11 files changed

+227
-5
lines changed

Diff for: cores/esp8266/Esp-frag.cpp

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
Esp.cpp - ESP8266-specific APIs
3+
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
4+
This file is part of the esp8266 core for Arduino environment.
5+
6+
This library is free software; you can redistribute it and/or
7+
modify it under the terms of the GNU Lesser General Public
8+
License as published by the Free Software Foundation; either
9+
version 2.1 of the License, or (at your option) any later version.
10+
11+
This library is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
Lesser General Public License for more details.
15+
16+
You should have received a copy of the GNU Lesser General Public
17+
License along with this library; if not, write to the Free Software
18+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19+
*/
20+
21+
#include "umm_malloc/umm_malloc.h"
22+
#include "umm_malloc/umm_malloc_cfg.h"
23+
#include "coredecls.h"
24+
#include "Esp.h"
25+
26+
void EspClass::getHeapStats(uint32_t* hfree, uint16_t* hmax, uint8_t* hfrag)
27+
{
28+
// L2 / Euclidian norm of free block sizes.
29+
// Having getFreeHeap()=sum(hole-size), fragmentation is given by
30+
// 100 * (1 - sqrt(sum(hole-size²)) / sum(hole-size))
31+
32+
umm_info(NULL, 0);
33+
uint8_t block_size = umm_block_size();
34+
uint32_t fh = ummHeapInfo.freeBlocks * block_size;
35+
if (hfree)
36+
*hfree = fh;
37+
if (hmax)
38+
*hmax = ummHeapInfo.maxFreeContiguousBlocks * block_size;
39+
if (hfrag)
40+
*hfrag = 100 - (sqrt32(ummHeapInfo.freeSize2) * 100) / fh;
41+
}
42+
43+
uint8_t EspClass::getHeapFragmentation()
44+
{
45+
uint8_t hfrag;
46+
getHeapStats(nullptr, nullptr, &hfrag);
47+
return hfrag;
48+
}

Diff for: cores/esp8266/Esp.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <memory>
2525
#include "interrupts.h"
2626
#include "MD5Builder.h"
27+
#include "umm_malloc/umm_malloc.h"
2728

2829
extern "C" {
2930
#include "user_interface.h"
@@ -171,6 +172,11 @@ uint32_t EspClass::getFreeHeap(void)
171172
return system_get_free_heap_size();
172173
}
173174

175+
uint16_t EspClass::getMaxFreeBlockSize(void)
176+
{
177+
return umm_max_block_size();
178+
}
179+
174180
uint32_t EspClass::getChipId(void)
175181
{
176182
return system_get_chip_id();

Diff for: cores/esp8266/Esp.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,13 @@ class EspClass {
103103
void restart();
104104

105105
uint16_t getVcc();
106-
uint32_t getFreeHeap();
107-
108106
uint32_t getChipId();
109107

108+
uint32_t getFreeHeap();
109+
uint16_t getMaxFreeBlockSize();
110+
uint8_t getHeapFragmentation(); // in %
111+
void getHeapStats(uint32_t* free = nullptr, uint16_t* max = nullptr, uint8_t* frag = nullptr);
112+
110113
const char * getSdkVersion();
111114
String getCoreVersion();
112115
String getFullVersion();

Diff for: cores/esp8266/coredecls.h

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ extern "C" {
88

99
// TODO: put declarations here, get rid of -Wno-implicit-function-declaration
1010

11+
#include <stdint.h>
1112
#include <cont.h> // g_pcont declaration
1213

1314
extern bool timeshift64_is_set;
@@ -18,6 +19,8 @@ void tune_timeshift64 (uint64_t now_us);
1819
void settimeofday_cb (void (*cb)(void));
1920
void disable_extra4k_at_link_time (void) __attribute__((noinline));
2021

22+
uint32_t sqrt32 (uint32_t n);
23+
2124
#ifdef __cplusplus
2225
}
2326
#endif

Diff for: cores/esp8266/sqrt32.c

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
#include <coredecls.h>
3+
#include <stdint.h>
4+
5+
uint32_t sqrt32 (uint32_t n)
6+
{
7+
// http://www.codecodex.com/wiki/Calculate_an_integer_square_root#C
8+
// Another very fast algorithm donated by [email protected]
9+
// (note: tested across the full 32 bits range, see comment below)
10+
11+
// 15 iterations (c=1<<15)
12+
13+
unsigned int c = 0x8000;
14+
unsigned int g = 0x8000;
15+
16+
for(;;)
17+
{
18+
if (g*g > n)
19+
g ^= c;
20+
c >>= 1;
21+
if (!c)
22+
return g;
23+
g |= c;
24+
}
25+
}
26+
27+
/*
28+
* tested with:
29+
*
30+
31+
#include <stdio.h>
32+
#include <stdint.h>
33+
#include <math.h>
34+
35+
int main (void)
36+
{
37+
for (uint32_t i = 0; ++i; )
38+
{
39+
uint32_t sr = sqrt32(i);
40+
uint32_t ifsr = sqrt(i);
41+
42+
if (ifsr != sr)
43+
printf("%d: i%d f%d\n", i, sr, ifsr);
44+
45+
if (!(i & 0xffffff))
46+
{
47+
printf("%i%% (0x%08x)\r", ((i >> 16) * 100) >> 16, i);
48+
fflush(stdout);
49+
}
50+
}
51+
52+
printf("\n");
53+
}
54+
55+
*
56+
*/

Diff for: cores/esp8266/umm_malloc/umm_malloc.c

+13
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,10 @@ void ICACHE_FLASH_ATTR *umm_info( void *ptr, int force ) {
10241024
if( UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK ) {
10251025
++ummHeapInfo.freeEntries;
10261026
ummHeapInfo.freeBlocks += curBlocks;
1027+
ummHeapInfo.freeSize2 += (unsigned int)curBlocks
1028+
* (unsigned int)sizeof(umm_block)
1029+
* (unsigned int)curBlocks
1030+
* (unsigned int)sizeof(umm_block);
10271031

10281032
if (ummHeapInfo.maxFreeContiguousBlocks < curBlocks) {
10291033
ummHeapInfo.maxFreeContiguousBlocks = curBlocks;
@@ -1761,4 +1765,13 @@ size_t ICACHE_FLASH_ATTR umm_free_heap_size( void ) {
17611765
return (size_t)ummHeapInfo.freeBlocks * sizeof(umm_block);
17621766
}
17631767

1768+
size_t ICACHE_FLASH_ATTR umm_max_block_size( void ) {
1769+
umm_info(NULL, 0);
1770+
return ummHeapInfo.maxFreeContiguousBlocks * sizeof(umm_block);
1771+
}
1772+
1773+
size_t ICACHE_FLASH_ATTR umm_block_size( void ) {
1774+
return sizeof(umm_block);
1775+
}
1776+
17641777
/* ------------------------------------------------------------------------ */

Diff for: cores/esp8266/umm_malloc/umm_malloc.h

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ typedef struct UMM_HEAP_INFO_t {
2626
unsigned short int freeBlocks;
2727

2828
unsigned short int maxFreeContiguousBlocks;
29+
30+
unsigned int freeSize2;
2931
}
3032
UMM_HEAP_INFO;
3133

@@ -41,6 +43,8 @@ void *umm_realloc( void *ptr, size_t size );
4143
void umm_free( void *ptr );
4244

4345
size_t umm_free_heap_size( void );
46+
size_t umm_max_block_size( void );
47+
size_t umm_block_size( void );
4448

4549
#ifdef __cplusplus
4650
}

Diff for: doc/faq/a02-my-esp-crashes.rst

+6-3
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,12 @@ Memory, memory, memory
291291
rely on exceptions for error handling, which is not available for the ESP, and in any
292292
case there is no access to the underlying code.
293293

294-
Instrumenting the code with the OOM debug option and calls to ``ESP.getFreeHeap()`` will
295-
help the process of finding leaks. Now is time to re-read about the
296-
`exception decoder <#exception-decoder>`__.
294+
Instrumenting the code with the OOM debug option and calls to
295+
``ESP.getFreeHeap()`` / ``ESP.getHeapFragmentation()`` /
296+
``ESP.getMaxFreeBlockSize()`` will help the process of finding memory issues.
297+
298+
Now is time to re-read about the `exception decoder
299+
<#exception-decoder>`__.
297300

298301

299302
*Some techniques for reducing memory usage*

Diff for: doc/libraries.rst

+4
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ Some ESP-specific APIs related to deep sleep, RTC and flash memories are availab
8383

8484
``ESP.getFreeHeap()`` returns the free heap size.
8585

86+
``ESP.getHeapFragmentation()`` returns the fragmentation metric (0% is clean, more than ~50% is not harmless)
87+
88+
``ESP.getMaxFreeBlockSize()`` returns the maximum allocatable ram block regarding heap fragmentation
89+
8690
``ESP.getChipId()`` returns the ESP8266 chip ID as a 32-bit integer.
8791

8892
``ESP.getCoreVersion()`` returns a String containing the core version.

Diff for: libraries/esp8266/examples/HeapMetric/HeapMetric.ino

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
2+
// nothing else than showing heap metric usage
3+
// released to public domain
4+
5+
#include <ESP8266WiFi.h>
6+
7+
void stats(const char* what) {
8+
// we could use getFreeHeap() getMaxFreeBlockSize() and getHeapFragmentation()
9+
// or all at once:
10+
uint32_t free;
11+
uint16_t max;
12+
uint8_t frag;
13+
ESP.getHeapStats(&free, &max, &frag);
14+
15+
Serial.printf("free: %5d - max: %5d - frag: %3d%% <- ", free, max, frag);
16+
// %s requires a malloc that could fail, using println instead:
17+
Serial.println(what);
18+
}
19+
20+
void tryit(int blocksize) {
21+
void** p;
22+
int blocks;
23+
24+
// heap-used ~= blocks*sizeof(void*) + blocks*blocksize
25+
blocks = ((ESP.getMaxFreeBlockSize() / (blocksize + sizeof(void*))) + 3) & ~3; // rounded up, multiple of 4
26+
27+
Serial.printf("\nFilling memory with blocks of %d bytes each\n", blocksize);
28+
stats("before");
29+
30+
p = (void**)malloc(sizeof(void*) * blocks);
31+
for (int i = 0; i < blocks; i++) {
32+
p[i] = malloc(blocksize);
33+
}
34+
stats("array and blocks allocation");
35+
36+
for (int i = 0; i < blocks; i += 2) {
37+
if (p[i]) {
38+
free(p[i]);
39+
}
40+
p[i] = nullptr;
41+
}
42+
stats("freeing every other blocks");
43+
44+
for (int i = 0; i < blocks; i += 4) {
45+
if (p[i + 1]) {
46+
free(p[i + 1]);
47+
}
48+
p[i + 1] = nullptr;
49+
}
50+
stats("freeing every other remaining blocks");
51+
52+
for (int i = 0; i < blocks; i++) {
53+
if (p[i]) {
54+
free(p[i]);
55+
}
56+
}
57+
stats("freeing array");
58+
59+
free(p);
60+
stats("after");
61+
}
62+
63+
void setup() {
64+
Serial.begin(115200);
65+
WiFi.mode(WIFI_OFF);
66+
67+
tryit(8000);
68+
tryit(4000);
69+
tryit(2000);
70+
tryit(1000);
71+
tryit(500);
72+
tryit(200);
73+
tryit(100);
74+
tryit(50);
75+
tryit(15);
76+
}
77+
78+
void loop() {
79+
}

Diff for: libraries/esp8266/keywords.txt

+3
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ reset KEYWORD2
2929
restart KEYWORD2
3030
getVcc KEYWORD2
3131
getFreeHeap KEYWORD2
32+
getHeapFragmentation KEYWORD2
33+
getMaxFreeBlockSize KEYWORD2
3234
getChipId KEYWORD2
3335
getSdkVersion KEYWORD2
3436
getCoreVersion KEYWORD2
37+
getFullVersion KEYWORD2
3538
getBootVersion KEYWORD2
3639
getBootMode KEYWORD2
3740
getCpuFreqMHz KEYWORD2

0 commit comments

Comments
 (0)