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**_
This commit is contained in:
jpirnay
2026-02-24 18:12:01 +01:00
committed by GitHub
parent d6d0cc869e
commit 7d97687a5b
2 changed files with 42 additions and 38 deletions

View File

@@ -78,6 +78,7 @@ MAX_POINTS = 50
time_data: deque[str] = deque(maxlen=MAX_POINTS) time_data: deque[str] = deque(maxlen=MAX_POINTS)
free_mem_data: deque[float] = deque(maxlen=MAX_POINTS) free_mem_data: deque[float] = deque(maxlen=MAX_POINTS)
total_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 data_lock: threading.Lock = threading.Lock() # Prevent reading while writing
# Global shutdown flag # Global shutdown flag
@@ -172,21 +173,26 @@ def get_color_for_line(line: str) -> str:
return Fore.WHITE 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. Extracts memory stats from MEM log lines.
Format: [MEM] Free: 196344 bytes, Total: 226412 bytes, Min Free: 112620 bytes 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: <digits>' and 'Total: <digits>' def _find(pattern: str) -> int | None:
match = re.search(r"Free:\s*(\d+).*Total:\s*(\d+)", line) m = re.search(pattern, line)
if match: if m:
try: try:
free_bytes = int(match.group(1)) return int(m.group(1))
total_bytes = int(match.group(2))
return free_bytes, total_bytes
except ValueError: except ValueError:
return None, None pass
return None, None 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: 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 # Check for Memory Line
if "[MEM]" in formatted_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: if free_val is not None and total_val is not None:
with data_lock: with data_lock:
time_data.append(pc_time) time_data.append(pc_time)
free_mem_data.append(free_val / 1024) # Convert to KB free_mem_data.append(free_val / 1024)
total_mem_data.append(total_val / 1024) # Convert to KB total_mem_data.append(total_val / 1024)
max_alloc_data.append((max_alloc_val or 0) / 1024)
# Apply filters # Apply filters
if filter_keyword and filter_keyword not in formatted_line.lower(): if filter_keyword and filter_keyword not in formatted_line.lower():
continue continue
@@ -309,6 +316,7 @@ def update_graph(frame) -> list: # pylint: disable=unused-argument
""" """
Called by Matplotlib animation to redraw the memory usage chart. Called by Matplotlib animation to redraw the memory usage chart.
Monitors the global shutdown event and closes the plot when shutdown is requested. 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(): if shutdown_event.is_set():
plt.close("all") plt.close("all")
@@ -318,32 +326,28 @@ def update_graph(frame) -> list: # pylint: disable=unused-argument
if not time_data: if not time_data:
return [] return []
# Convert deques to lists for plotting
x = list(time_data) x = list(time_data)
y_free = list(free_mem_data) y_free = list(free_mem_data)
y_total = list(total_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 ax1.plot(x, y_total, label="Total RAM (KB)", color="red", linestyle="--")
plt.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):
# Plot Free RAM ax1.plot(x, y_max_alloc, label="Max Alloc (KB)", color="orange", linestyle="-.")
plt.plot(x, y_free, label="Free RAM (KB)", color="green", marker="o") ax1.fill_between(x, y_free, color="green", alpha=0.1)
ax1.set_title("ESP32 Memory Monitor")
# Fill area under Free RAM ax1.set_ylabel("Memory (KB)")
plt.fill_between(x, y_free, color="green", alpha=0.1) ax1.set_xlabel("Time")
ax1.legend(loc="upper left")
plt.title("ESP32 Memory Monitor") ax1.grid(True, linestyle=":", alpha=0.6)
plt.ylabel("Memory (KB)") plt.setp(ax1.get_xticklabels(), rotation=45, ha="right")
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()
fig.tight_layout()
return [] return []

View File

@@ -378,8 +378,8 @@ void loop() {
renderer.setFadingFix(SETTINGS.fadingFix); renderer.setFadingFix(SETTINGS.fadingFix);
if (Serial && millis() - lastMemPrint >= 10000) { if (Serial && millis() - lastMemPrint >= 10000) {
LOG_INF("MEM", "Free: %d bytes, Total: %d bytes, Min Free: %d bytes", ESP.getFreeHeap(), ESP.getHeapSize(), LOG_INF("MEM", "Free: %d bytes, Total: %d bytes, Min Free: %d bytes, MaxAlloc: %d bytes", ESP.getFreeHeap(),
ESP.getMinFreeHeap()); ESP.getHeapSize(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap());
lastMemPrint = millis(); lastMemPrint = millis();
} }