From a771c3f6de2b64d84d3c7cd3c265f29173507cf4 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Mon, 13 Jul 2026 10:25:25 +0200 Subject: [PATCH] chore(diag): log internal DRAM next to PSRAM-dominated heap totals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "free heap 5.8MB" in the crash log was PSRAM and masked the actual internal-RAM exhaustion. git_sync's publish/commit/push lines now carry the MALLOC_CAP_INTERNAL reading, and boot brackets the palette file-list build with it — small Strings are forced internal by the 16 KB SPIRAM malloc threshold, so the 1098-path list is a suspected ~60-70 KB DRAM consumer competing with Wi-Fi/TLS; the log decides whether interning into one PSRAM buffer is worth it. --- firmware/src/git_sync.rs | 21 ++++++++++++++++----- firmware/src/main.rs | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/firmware/src/git_sync.rs b/firmware/src/git_sync.rs index fdd3ac3..8c06ad2 100644 --- a/firmware/src/git_sync.rs +++ b/firmware/src/git_sync.rs @@ -248,9 +248,10 @@ fn publish_cycle( /// error, surfaced as such. fn publish_once(paths: &BTreeSet) -> Result { log::info!( - "publish started — {} dirty path(s), free heap {}", + "publish started — {} dirty path(s), free heap {} ({} internal)", paths.len(), - free_heap() + free_heap(), + internal_free_heap() ); let repo = Repository::open(REPO_DIR).with_context(|| { format!("opening git repo at {REPO_DIR} — provision the card with a clone (just init) whose origin is your remote") @@ -313,8 +314,9 @@ fn publish_once(paths: &BTreeSet) -> Result { } log::info!( - "push done — free heap {}, min-ever {}", + "push done — free heap {} ({} internal), min-ever {}", free_heap(), + internal_free_heap(), min_free_heap() ); Ok(PublishOutcome::Pushed(short(oid))) @@ -388,11 +390,12 @@ fn stage_and_commit(repo: &Repository, paths: &BTreeSet) -> Result u32 { unsafe { sys::esp_get_free_heap_size() } } +/// Free INTERNAL RAM (DRAM), excluding PSRAM. `free_heap` is dominated by the +/// 8 MB PSRAM pool and masks internal exhaustion — which is what actually +/// killed the first real-repo push (mbedTLS's ssl_setup could not get its +/// ~33 KB while Wi-Fi + USB + editor + libgit2 were resident). +fn internal_free_heap() -> u32 { + unsafe { sys::heap_caps_get_free_size(sys::MALLOC_CAP_INTERNAL) as u32 } +} + fn min_free_heap() -> u32 { unsafe { sys::esp_get_minimum_free_heap_size() } } diff --git a/firmware/src/main.rs b/firmware/src/main.rs index 075ca84..3ca10c8 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -123,7 +123,18 @@ fn main() -> anyhow::Result<()> { ed.set_notice(format!("loaded {name}")); // Feed the file palette (Ctrl-P). Enumerated once at boot — the v0.5 slices // that create/delete files (`:enew`, delete) will re-feed it then. + // Bracketed with internal-DRAM readings: each path is a small String, kept + // internal by the SPIRAM malloc threshold (16 KB), so the list competes + // with Wi-Fi/TLS for DRAM. Estimate to confirm: ~60-70 KB at 1098 files — + // this number decides whether interning the paths into one shared buffer + // (a single >16 KB alloc, which lands in PSRAM) is worth the refactor. + let dram_before = internal_free_heap(); ed.set_file_list(enumerate_files()); + let dram_after = internal_free_heap(); + log::info!( + "file list: internal heap {dram_before} -> {dram_after} ({} KB consumed)", + dram_before.saturating_sub(dram_after) / 1024 + ); // Editor preferences (.typoena.toml, git-tracked). Read before the first // render so `line_numbers` shapes the opening frame. A missing / unreadable / // partial file falls back to defaults, so a fresh card just works. @@ -611,6 +622,13 @@ fn walk_files(dir: &std::path::Path, depth: usize, out: &mut Vec) { } } +/// Free internal DRAM (excludes the 8 MB PSRAM pool, which dominates the total +/// free-heap number and masks DRAM exhaustion). Same reading `git_sync` logs. +fn internal_free_heap() -> u32 { + use esp_idf_svc::sys; + unsafe { sys::heap_caps_get_free_size(sys::MALLOC_CAP_INTERNAL) as u32 } +} + /// A file's display name — its basename without extension (`/sd/repo/notes.md` /// → `notes`), for the snackbar. Falls back to the raw path if it has no stem. fn file_stem(path: &str) -> &str {