Skip to content

Commit 13241fe

Browse files
authored
Merge pull request #2 from ESPToolKit/feature/leakcheck-ring-buffer
Bound leak-check history to prevent unbounded memory growth
2 parents 34e4782 + 42fa6ab commit 13241fe

3 files changed

Lines changed: 17 additions & 5 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ ESPMemoryMonitor is a tiny C++17 helper that wraps ESP-IDF heap/stack inspection
1313
- Per-region thresholds with hysteresis; `onThreshold` fires on enter/exit of warn/critical bands so alerts do not spam as memory bounces.
1414
- Optional extras: per-task stack high-water (via `uxTaskGetSystemState`), min-ever-free, and IDF failed-allocation callbacks (`heap_caps_register_failed_alloc_callback`).
1515
- Scope-based deltas and tag budgets: wrap a code path in `beginScope()` to measure DRAM/PSRAM consumed (or released), attribute it to a tag, and fire `onScope`/`onTagThreshold` callbacks when soft budgets are crossed.
16-
- Leak suspicion helpers: mark checkpoints for steady-state phases; the monitor compares averages and flags downward free-memory drift or rising fragmentation via `onLeakCheck`.
16+
- Leak suspicion helpers: mark checkpoints for steady-state phases; the monitor compares averages and flags downward free-memory drift or rising fragmentation via `onLeakCheck`, with bounded checkpoint/result retention.
1717
- Derived insights: windowed min/avg/max, slope-based bytes/second, and time-to-warn/critical estimates per region.
1818
- Task visibility: stack state transitions (`Safe/Warn/Critical`), optional new/vanished task detection, and per-task thresholds.
1919
- Export/panic helpers: convert snapshots to ArduinoJson for telemetry and install a shutdown/panic hook that captures a final snapshot before abort/restart.
@@ -172,6 +172,7 @@ serializeJson(doc, Serial);
172172
| `enableTaskTracking` | `false` | Emit stack-state transitions and task create/destroy events (requires `enablePerTaskStacks`). |
173173
| `defaultTaskStackBytes` / `stackWarnFraction` / `stackCriticalFraction` | `4096` / `0.25` / `0.10` | Default stack headroom thresholds when per-task overrides are absent. |
174174
| `leakNoiseBytes` | `1024` | Ignore free-byte changes smaller than this when flagging leak drift between checkpoints. |
175+
| `maxLeakChecksInHistory` | `16` | Ring-buffer depth for leak checkpoint timestamps/results (minimum effective value is 1). |
175176
| `usePSRAMBuffers` | `false` | Best-effort PSRAM preference for monitor-owned dynamic containers and internal storage models (history/scope/tag/task/leak internals plus transient threshold, scope/tag, window, and task-tracking scratch buffers); automatically falls back to normal heap when PSRAM is unavailable. |
176177

177178
`MemorySnapshot` holds `timestampUs` plus vectors of `RegionStats` (free bytes, low-water, largest block, fragmentation, slope/time estimates, window stats) and optional `TaskStackUsage` entries (task name, priority, state, free high-water bytes).

src/esp_memory_monitor/memory_monitor.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -940,8 +940,8 @@ void ESPMemoryMonitor::resetOwnedContainers() {
940940
std::hash<TaskHandle_t>{},
941941
std::equal_to<TaskHandle_t>{},
942942
MemoryMonitorAllocator<std::pair<const TaskHandle_t, InternalTaskStackUsage>>(_usePSRAMBuffers));
943-
_leakHistory = MemoryMonitorVector<InternalLeakCheckResult>(MemoryMonitorAllocator<InternalLeakCheckResult>(_usePSRAMBuffers));
944-
_leakCheckpoints = MemoryMonitorVector<uint64_t>(MemoryMonitorAllocator<uint64_t>(_usePSRAMBuffers));
943+
_leakHistory = MemoryMonitorDeque<InternalLeakCheckResult>(MemoryMonitorAllocator<InternalLeakCheckResult>(_usePSRAMBuffers));
944+
_leakCheckpoints = MemoryMonitorDeque<uint64_t>(MemoryMonitorAllocator<uint64_t>(_usePSRAMBuffers));
945945
}
946946

947947
ESPMemoryMonitor::InternalLeakCheckResult ESPMemoryMonitor::buildLeakCheckLocked(const std::string& label) {
@@ -950,9 +950,13 @@ ESPMemoryMonitor::InternalLeakCheckResult ESPMemoryMonitor::buildLeakCheckLocked
950950
return result;
951951
}
952952

953+
const size_t leakHistoryLimit = std::max<size_t>(1, _config.maxLeakChecksInHistory);
953954
const uint64_t latestTs = _history.back().timestampUs;
954955
const uint64_t startTs = _leakCheckpoints.empty() ? _history.front().timestampUs : _leakCheckpoints.back();
955956
_leakCheckpoints.push_back(latestTs);
957+
while (_leakCheckpoints.size() > leakHistoryLimit) {
958+
_leakCheckpoints.pop_front();
959+
}
956960

957961
auto computeAverages = [&](uint64_t from, uint64_t to) {
958962
struct RegionAvg {
@@ -1004,6 +1008,9 @@ ESPMemoryMonitor::InternalLeakCheckResult ESPMemoryMonitor::buildLeakCheckLocked
10041008
result.deltas.push_back(delta);
10051009
}
10061010
_leakHistory.push_back(result);
1011+
while (_leakHistory.size() > leakHistoryLimit) {
1012+
_leakHistory.pop_front();
1013+
}
10071014
return result;
10081015
}
10091016

@@ -1039,6 +1046,9 @@ ESPMemoryMonitor::InternalLeakCheckResult ESPMemoryMonitor::buildLeakCheckLocked
10391046
}
10401047

10411048
_leakHistory.push_back(result);
1049+
while (_leakHistory.size() > leakHistoryLimit) {
1050+
_leakHistory.pop_front();
1051+
}
10421052
return result;
10431053
}
10441054

src/esp_memory_monitor/memory_monitor.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ struct MemoryMonitorConfig {
7979
float stackWarnFraction = 0.25f;
8080
float stackCriticalFraction = 0.10f;
8181
size_t leakNoiseBytes = 1024;
82+
size_t maxLeakChecksInHistory = 16;
8283
bool usePSRAMBuffers = false;
8384
};
8485

@@ -415,8 +416,8 @@ class ESPMemoryMonitor {
415416
MemoryMonitorVector<TagBudget> _tagBudgets;
416417
MemoryMonitorUnorderedMap<MemoryMonitorString, TaskStackThreshold> _taskThresholds;
417418
MemoryMonitorUnorderedMap<TaskHandle_t, InternalTaskStackUsage> _knownTasks;
418-
MemoryMonitorVector<InternalLeakCheckResult> _leakHistory;
419-
MemoryMonitorVector<uint64_t> _leakCheckpoints;
419+
MemoryMonitorDeque<InternalLeakCheckResult> _leakHistory;
420+
MemoryMonitorDeque<uint64_t> _leakCheckpoints;
420421
bool _panicHookInstalled = false;
421422
};
422423

0 commit comments

Comments
 (0)