From 7d97687a5b9a8e27dcc67517f9eb07618a6fce21 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Tue, 24 Feb 2026 18:12:01 +0100 Subject: [PATCH] feat: Add maxAlloc to memory information (#1152) ## Summary * **What is the goal of this PR?** During debugging of #1092 i desperately needed to monitor the biggest allocatable block of memory on the heap * **What changes are included?** Added informaqtion to debug output, amended monitor utility to pick it up ## Additional Context --- --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ --- scripts/debugging_monitor.py | 76 +++++++++++++++++++----------------- src/main.cpp | 4 +- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/scripts/debugging_monitor.py b/scripts/debugging_monitor.py index 4755e61b..30c3533e 100755 --- a/scripts/debugging_monitor.py +++ b/scripts/debugging_monitor.py @@ -78,6 +78,7 @@ MAX_POINTS = 50 time_data: deque[str] = deque(maxlen=MAX_POINTS) free_mem_data: deque[float] = deque(maxlen=MAX_POINTS) total_mem_data: deque[float] = deque(maxlen=MAX_POINTS) +max_alloc_data: deque[float] = deque(maxlen=MAX_POINTS) data_lock: threading.Lock = threading.Lock() # Prevent reading while writing # Global shutdown flag @@ -172,21 +173,26 @@ def get_color_for_line(line: str) -> str: return Fore.WHITE -def parse_memory_line(line: str) -> tuple[int | None, int | None]: +def parse_memory_line(line: str) -> tuple[int | None, int | None, int | None]: """ - Extracts Free and Total bytes from the specific log line. - Format: [MEM] Free: 196344 bytes, Total: 226412 bytes, Min Free: 112620 bytes + Extracts memory stats from MEM log lines. + Format: Free: N bytes, Total: N bytes, Min Free: N bytes, MaxAlloc: N bytes + Returns: (free_bytes, total_bytes, max_alloc_bytes) """ - # Regex to find 'Free: ' and 'Total: ' - match = re.search(r"Free:\s*(\d+).*Total:\s*(\d+)", line) - if match: - try: - free_bytes = int(match.group(1)) - total_bytes = int(match.group(2)) - return free_bytes, total_bytes - except ValueError: - return None, None - return None, None + def _find(pattern: str) -> int | None: + m = re.search(pattern, line) + if m: + try: + return int(m.group(1)) + except ValueError: + pass + return None + + return ( + _find(r"\bFree:\s*(\d+)"), + _find(r"\bTotal:\s*(\d+)"), + _find(r"\bMaxAlloc:\s*(\d+)"), + ) def serial_worker(ser, kwargs: dict[str, str]) -> None: @@ -265,12 +271,13 @@ def serial_worker(ser, kwargs: dict[str, str]) -> None: # Check for Memory Line if "[MEM]" in formatted_line: - free_val, total_val = parse_memory_line(formatted_line) + free_val, total_val, max_alloc_val = parse_memory_line(formatted_line) if free_val is not None and total_val is not None: with data_lock: time_data.append(pc_time) - free_mem_data.append(free_val / 1024) # Convert to KB - total_mem_data.append(total_val / 1024) # Convert to KB + free_mem_data.append(free_val / 1024) + total_mem_data.append(total_val / 1024) + max_alloc_data.append((max_alloc_val or 0) / 1024) # Apply filters if filter_keyword and filter_keyword not in formatted_line.lower(): continue @@ -309,6 +316,7 @@ def update_graph(frame) -> list: # pylint: disable=unused-argument """ Called by Matplotlib animation to redraw the memory usage chart. Monitors the global shutdown event and closes the plot when shutdown is requested. + Shows DRAM metrics (free, total, max contiguous alloc) and an optional PSRAM subplot. """ if shutdown_event.is_set(): plt.close("all") @@ -318,32 +326,28 @@ def update_graph(frame) -> list: # pylint: disable=unused-argument if not time_data: return [] - # Convert deques to lists for plotting x = list(time_data) y_free = list(free_mem_data) y_total = list(total_mem_data) + y_max_alloc = list(max_alloc_data) - plt.cla() # Clear axis + fig = plt.gcf() + fig.clf() + ax1 = fig.add_subplot(111) - # Plot Total RAM - plt.plot(x, y_total, label="Total RAM (KB)", color="red", linestyle="--") - - # Plot Free RAM - plt.plot(x, y_free, label="Free RAM (KB)", color="green", marker="o") - - # Fill area under Free RAM - plt.fill_between(x, y_free, color="green", alpha=0.1) - - plt.title("ESP32 Memory Monitor") - plt.ylabel("Memory (KB)") - plt.xlabel("Time") - plt.legend(loc="upper left") - plt.grid(True, linestyle=":", alpha=0.6) - - # Rotate date labels - plt.xticks(rotation=45, ha="right") - plt.tight_layout() + ax1.plot(x, y_total, label="Total RAM (KB)", color="red", linestyle="--") + ax1.plot(x, y_free, label="Free RAM (KB)", color="green", marker="o", markersize=3) + if any(v > 0 for v in y_max_alloc): + ax1.plot(x, y_max_alloc, label="Max Alloc (KB)", color="orange", linestyle="-.") + ax1.fill_between(x, y_free, color="green", alpha=0.1) + ax1.set_title("ESP32 Memory Monitor") + ax1.set_ylabel("Memory (KB)") + ax1.set_xlabel("Time") + ax1.legend(loc="upper left") + ax1.grid(True, linestyle=":", alpha=0.6) + plt.setp(ax1.get_xticklabels(), rotation=45, ha="right") + fig.tight_layout() return [] diff --git a/src/main.cpp b/src/main.cpp index ba5d4608..097f0e22 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -378,8 +378,8 @@ void loop() { renderer.setFadingFix(SETTINGS.fadingFix); if (Serial && millis() - lastMemPrint >= 10000) { - LOG_INF("MEM", "Free: %d bytes, Total: %d bytes, Min Free: %d bytes", ESP.getFreeHeap(), ESP.getHeapSize(), - ESP.getMinFreeHeap()); + LOG_INF("MEM", "Free: %d bytes, Total: %d bytes, Min Free: %d bytes, MaxAlloc: %d bytes", ESP.getFreeHeap(), + ESP.getHeapSize(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap()); lastMemPrint = millis(); }