Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ ESPMemoryMonitor is a tiny C++17 helper that wraps ESP-IDF heap/stack inspection
- Per-region thresholds with hysteresis; `onThreshold` fires on enter/exit of warn/critical bands so alerts do not spam as memory bounces.
- Optional extras: per-task stack high-water (via `uxTaskGetSystemState`), min-ever-free, and IDF failed-allocation callbacks (`heap_caps_register_failed_alloc_callback`).
- 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.
- Leak suspicion helpers: mark checkpoints for steady-state phases; the monitor compares averages and flags downward free-memory drift or rising fragmentation via `onLeakCheck`.
- 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.
- Derived insights: windowed min/avg/max, slope-based bytes/second, and time-to-warn/critical estimates per region.
- Task visibility: stack state transitions (`Safe/Warn/Critical`), optional new/vanished task detection, and per-task thresholds.
- Export/panic helpers: convert snapshots to ArduinoJson for telemetry and install a shutdown/panic hook that captures a final snapshot before abort/restart.
Expand Down Expand Up @@ -172,6 +172,7 @@ serializeJson(doc, Serial);
| `enableTaskTracking` | `false` | Emit stack-state transitions and task create/destroy events (requires `enablePerTaskStacks`). |
| `defaultTaskStackBytes` / `stackWarnFraction` / `stackCriticalFraction` | `4096` / `0.25` / `0.10` | Default stack headroom thresholds when per-task overrides are absent. |
| `leakNoiseBytes` | `1024` | Ignore free-byte changes smaller than this when flagging leak drift between checkpoints. |
| `maxLeakChecksInHistory` | `16` | Ring-buffer depth for leak checkpoint timestamps/results (minimum effective value is 1). |
| `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. |

`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).
Expand Down
14 changes: 12 additions & 2 deletions src/esp_memory_monitor/memory_monitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -940,8 +940,8 @@ void ESPMemoryMonitor::resetOwnedContainers() {
std::hash<TaskHandle_t>{},
std::equal_to<TaskHandle_t>{},
MemoryMonitorAllocator<std::pair<const TaskHandle_t, InternalTaskStackUsage>>(_usePSRAMBuffers));
_leakHistory = MemoryMonitorVector<InternalLeakCheckResult>(MemoryMonitorAllocator<InternalLeakCheckResult>(_usePSRAMBuffers));
_leakCheckpoints = MemoryMonitorVector<uint64_t>(MemoryMonitorAllocator<uint64_t>(_usePSRAMBuffers));
_leakHistory = MemoryMonitorDeque<InternalLeakCheckResult>(MemoryMonitorAllocator<InternalLeakCheckResult>(_usePSRAMBuffers));
_leakCheckpoints = MemoryMonitorDeque<uint64_t>(MemoryMonitorAllocator<uint64_t>(_usePSRAMBuffers));
}

ESPMemoryMonitor::InternalLeakCheckResult ESPMemoryMonitor::buildLeakCheckLocked(const std::string& label) {
Expand All @@ -950,9 +950,13 @@ ESPMemoryMonitor::InternalLeakCheckResult ESPMemoryMonitor::buildLeakCheckLocked
return result;
}

const size_t leakHistoryLimit = std::max<size_t>(1, _config.maxLeakChecksInHistory);
const uint64_t latestTs = _history.back().timestampUs;
const uint64_t startTs = _leakCheckpoints.empty() ? _history.front().timestampUs : _leakCheckpoints.back();
_leakCheckpoints.push_back(latestTs);
while (_leakCheckpoints.size() > leakHistoryLimit) {
_leakCheckpoints.pop_front();
}

auto computeAverages = [&](uint64_t from, uint64_t to) {
struct RegionAvg {
Expand Down Expand Up @@ -1004,6 +1008,9 @@ ESPMemoryMonitor::InternalLeakCheckResult ESPMemoryMonitor::buildLeakCheckLocked
result.deltas.push_back(delta);
}
_leakHistory.push_back(result);
while (_leakHistory.size() > leakHistoryLimit) {
_leakHistory.pop_front();
}
return result;
}

Expand Down Expand Up @@ -1039,6 +1046,9 @@ ESPMemoryMonitor::InternalLeakCheckResult ESPMemoryMonitor::buildLeakCheckLocked
}

_leakHistory.push_back(result);
while (_leakHistory.size() > leakHistoryLimit) {
_leakHistory.pop_front();
}
return result;
}

Expand Down
5 changes: 3 additions & 2 deletions src/esp_memory_monitor/memory_monitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ struct MemoryMonitorConfig {
float stackWarnFraction = 0.25f;
float stackCriticalFraction = 0.10f;
size_t leakNoiseBytes = 1024;
size_t maxLeakChecksInHistory = 16;
bool usePSRAMBuffers = false;
};

Expand Down Expand Up @@ -415,8 +416,8 @@ class ESPMemoryMonitor {
MemoryMonitorVector<TagBudget> _tagBudgets;
MemoryMonitorUnorderedMap<MemoryMonitorString, TaskStackThreshold> _taskThresholds;
MemoryMonitorUnorderedMap<TaskHandle_t, InternalTaskStackUsage> _knownTasks;
MemoryMonitorVector<InternalLeakCheckResult> _leakHistory;
MemoryMonitorVector<uint64_t> _leakCheckpoints;
MemoryMonitorDeque<InternalLeakCheckResult> _leakHistory;
MemoryMonitorDeque<uint64_t> _leakCheckpoints;
bool _panicHookInstalled = false;
};

Expand Down