Skip to content

ESP8266: fix heap metric functions and add OOM diagnostics#24777

Open
joluxer wants to merge 2 commits into
arendst:developmentfrom
joluxer:pr/fix-esp8266-heap-metrics
Open

ESP8266: fix heap metric functions and add OOM diagnostics#24777
joluxer wants to merge 2 commits into
arendst:developmentfrom
joluxer:pr/fix-esp8266-heap-metrics

Conversation

@joluxer
Copy link
Copy Markdown

@joluxer joluxer commented May 21, 2026

Description:

Checklist:

  • The pull request is done against the latest development branch
  • Only relevant files were touched
  • Only one feature/fix was added per PR and the code change compiles without warnings
  • The code change is tested and works with Tasmota core ESP8266 V.2.7.8
  • The code change is tested and works with Tasmota core ESP32 V.3.3.8 from Platform 2026.05.50
  • I accept the CLA.

Summary

Two commits addressing ESP8266 heap observability. Commit 1 is self-contained
and useful independently; Commit 2 builds on the corrected metric functions.


Commit 1 - Adapt ESP_getMaxAllocHeap() and ESP_getHeapFragmentation()

Problem:
ESP_getMaxAllocHeap() returned ESP.getFreeHeap() (total free heap, not the
largest contiguous allocatable block).
ESP_getHeapFragmentation() read ummHeapInfo.maxFreeContiguousBlocks, which
is only valid immediately after a umm_info() heap walk - stale or zero otherwise.

Fix:
Both functions now align with their ESP32 equivalents, returning accurate live
values derived from umm_max_block_size() and umm_free_heap_size_lw().

A new ESP_UpdateHeapMetrics() caches the result of umm_max_block_size()
(heap walk, brief IRQ disable). The cache is refreshed:

  • once per second in PerformEverySecond() when SetOption130 is active
  • on demand in CmndStatus() before Status 4 output

umm_max_block_size() and umm_free_heap_size_lw() are unconditionally
available (UMM_INFO and UMM_STATS are hardcoded in umm_malloc_cfg.h);
no additional build flags are required.

Observable changes:

  • SetOption130 heap timestamp on ESP8266 now includes the fragmentation
    percentage. Format changes from HH:MM:SS.mmm-FreeKB to
    HH:MM:SS.mmm-FreeKB/Frag%. Log parsers using the ESP8266-specific
    single-field format will need updating. The new format matches ESP32 behavior.
  • Status 4 (StatusMEM) gains two ESP8266-specific fields:
    MaxFreeBlock (KB) and Frag (%).

Commit 2 - OOM diagnostics, Status 4 extensions, Status 44 heap dump

Opt-in heap diagnostics gated on UMM_INLINE_METRICS or UMM_STATS_FULL
build flags. Without these flags: no runtime behavior change.

OOM event monitoring (requires UMM_INLINE_METRICS or UMM_STATS_FULL):

  • ESP_HeapOomCheck(): called once per second; logs OOM counter delta on
    change (OOM: count N (+M))
  • ESP_HeapOomTest(): logs current OOM count on demand

Additional Status 4 fields:

  • OomCount - cumulative OOM events (UMM_INLINE_METRICS or UMM_STATS_FULL)
  • HeapLwm (KB) - heap low-watermark since boot (UMM_STATS_FULL)
  • MaxAllocSz (bytes) - peak single allocation size (UMM_STATS_FULL)

Status 44 (ESP8266-only):

  • Triggers umm_info(nullptr, true): prints full heap block map to serial
  • Returns {"Status44":{"HeapDump":"serial"}}
  • Command number chosen above the standard Status range (0–MAX_STATUS = 0–13)
    and below the reserved value 99 (full status dump)

Enabling the diagnostic flags:
The flags must reach the ESP8266 Arduino framework's umm_malloc allocator,
which is compiled separately from the Tasmota sketch. user_config_override.h
affects only sketch compilation units and cannot reach the framework.
The required mechanism is build_flags in platformio_override.ini:

[env]
build_flags = ${common.build_flags}
              -DUMM_STATS_FULL
              -DUMM_INLINE_METRICS

Example lines are added to platformio_override_sample.ini.


Memory impact (ESP8266, tasmota release build)

Scope Flash RAM IRAM
Commit 1 (always active) +~1960 B +~32 B 0
Commit 2, no flags +~84 B 0 0
Commit 2, with UMM_* flags +~492 B +~40 B +~300 B
Total, no flags +~2044 B +~32 B 0
Total, with flags +~2452 B +~72 B +~300 B

The +300 B IRAM with flags is attributable to OOM monitoring functions
compiled IRAM-resident. This applies only to explicit diagnostic builds;
standard release builds are unaffected (0 IRAM impact).


Tested

Developed and initially tested on Tasmota v15.4.0, ESP-12F.
Build-tested and target-tested on the development branch.
SetOption130 active in both captures below.

Before (upstream development, no flags):

18:36:07.615-024 CMD: Status 4
18:36:07.627-021 MQT: feedback/heizstab/STATUS4 = {"StatusMEM":{"ProgramSize":659,
  "Free":1388,"Heap":24,"ProgramFlashSize":4096,"FlashSize":4096,
  "FlashChipId":"1625C2","FlashFrequency":40,"FlashMode":"DOUT",
  "Features":[...],"Drivers":"...","Sensors":"..."}}

After (this PR, UMM_STATS_FULL + UMM_INLINE_METRICS active):

18:40:08.501-023/02 CMD: Status 4
18:40:08.518-020/00 MQT: feedback/heizstab/STATUS4 = {"StatusMEM":{"ProgramSize":662,
  "Free":1384,"Heap":23,"ProgramFlashSize":4096,"FlashSize":4096,
  "FlashChipId":"1625C2","FlashFrequency":40,"FlashMode":"DOUT",
  "MaxFreeBlock":23,"Frag":1,"OomCount":0,"HeapLwm":18,"MaxAllocSz":4096,
  "Features":[...],"Drivers":"...","Sensors":"..."}}

Visible changes:

  • Timestamp format: -024-023/02 (FreeKB → FreeKB/Frag%)
  • New Status 4 fields: MaxFreeBlock, Frag, OomCount, HeapLwm, MaxAllocSz
  • ProgramSize delta: 659 → 662 KB (+3 KB, consistent with measured flash impact)

Status 44 (heap block map printed to serial, excerpt;
heap map format reference:
source - umm_info.c in ESP8266 Arduino core /
docs - rhempel/umm_malloc):

18:42:20.928-023/03 CMD: Status 44

+----------+-------+--------+--------+-------+--------+--------+
|0x3fff24c0|B     0|NB     1|PB     0|Z     1|NF    67|PF  1313|
|0x3fff24c8|B     1|NB     7|PB     0|Z     6|
|0x3fff25e8|B    37|NB    39|PB    28|Z     2|NF   177|PF   220|
|0x3fff3590|B   538|NB  1051|PB   535|Z   513|
|0x3fff63b8|B  2015|NB  4967|PB  1954|Z  2952|NF  1202|PF  1842|
|0x3fffbff8|B  4967|NB     0|PB  2015|Z     1|NF     0|PF     0|
... (128 entries total)
+----------+-------+--------+--------+-------+--------+--------+
Total Entries   128    Used Entries   116    Free Entries    12
Total Blocks   4966    Used Blocks   1936    Free Blocks   3030
+--------------------------------------------------------------+
Usage Metric:                  63
Fragmentation Metric:           3
+--------------------------------------------------------------+
umm heap statistics:
  Raw Free Space    24240
  OOM Count             0
  Low Watermark     17160
  Low Watermark ISR 21400
  MAX Alloc Request  4096
  Size of umm_block     8
+--------------------------------------------------------------+
18:42:20.557-023/03 OOM: count 0 (test-trigger)
18:42:21.002-020/00 MQT: feedback/heizstab/STATUS44 = {"Status44":{"HeapDump":"serial"}}

joluxer added 2 commits May 21, 2026 15:19
Both functions returned wrong values: ESP_getMaxAllocHeap() returned
ESP.getFreeHeap() (total free, not largest contiguous block);
ESP_getHeapFragmentation() read ummHeapInfo.maxFreeContiguousBlocks,
which is only valid immediately after a umm_info() heap walk.

Fix: cache the result of umm_max_block_size() in ESP_UpdateHeapMetrics().
The cache is refreshed once per second when SetOption130 is active, and
on demand in CmndStatus() before Status 4 is output. Both getter functions
read the cached value; the call site overhead is a single integer read.

umm_max_block_size() is unconditionally available (UMM_INFO is hardcoded
in the Arduino ESP8266 umm_malloc_cfg.h), so no build flags are required.

Status 4 (StatusMEM) gains two ESP8266-specific fields: MaxFreeBlock (KB)
and Frag (%).
OOM event monitoring (requires UMM_INLINE_METRICS or UMM_STATS_FULL):
- ESP_HeapOomCheck(): called once per second; logs the OOM counter delta
  when the counter changes ("OOM: count N (+M)")
- ESP_HeapOomTest(): logs current OOM count on demand

Status 4 (StatusMEM) gains additional ESP8266-specific fields when the
corresponding build flags are active:
- OomCount: cumulative out-of-memory events (UMM_INLINE_METRICS or
  UMM_STATS_FULL)
- HeapLwm (KB): heap low-watermark since boot (UMM_STATS_FULL)
- MaxAllocSz (bytes): peak single allocation size (UMM_STATS_FULL)

Status 44 (ESP8266-only diagnostic command):
- Triggers umm_info(nullptr, true) to print a full heap block map to
  the serial console
- Calls ESP_HeapOomTest() to log the current OOM count
- Returns {"Status44":{"HeapDump":"serial"}}
- Status 44 is accepted regardless of MAX_STATUS

Build flags UMM_STATS_FULL and UMM_INLINE_METRICS can be enabled via
platformio_override.ini build_flags; documented in
platformio_override_sample.ini.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant