notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit f25735f89e2be69c358b0e1011f41fe5a576936c
parent 738b5e71dac78ac7896312ea89dee5edc9482c33
Author: William Casarin <jb55@jb55.com>
Date:   Tue,  8 Jul 2025 13:27:37 -0700

debug: add memory debug window

enable with:

$ cargo build --release --features memory

and then click the memory widget on the chrome sidepanel

currently doesn't track C allocations... we should fix that

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
MCargo.lock | 150++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
MCargo.toml | 1+
Mcrates/notedeck_chrome/Cargo.toml | 2++
Mcrates/notedeck_chrome/src/chrome.rs | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mcrates/notedeck_chrome/src/notedeck.rs | 11+++++++++++
5 files changed, 288 insertions(+), 7 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -172,7 +172,7 @@ dependencies = [ "objc2-foundation 0.3.1", "parking_lot", "percent-encoding", - "windows-sys 0.52.0", + "windows-sys 0.59.0", "x11rb", ] @@ -1358,7 +1358,7 @@ version = "0.31.1" source = "git+https://github.com/damus-io/egui?rev=73a831ed43d3a8592611e2948b505add88d8aba2#73a831ed43d3a8592611e2948b505add88d8aba2" dependencies = [ "bytemuck", - "emath", + "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=73a831ed43d3a8592611e2948b505add88d8aba2)", "serde", ] @@ -1407,7 +1407,7 @@ dependencies = [ "ahash", "backtrace", "bitflags 2.9.1", - "emath", + "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=73a831ed43d3a8592611e2948b505add88d8aba2)", "epaint", "log", "nohash-hasher", @@ -1536,6 +1536,12 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "emath" version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" + +[[package]] +name = "emath" +version = "0.31.1" source = "git+https://github.com/damus-io/egui?rev=73a831ed43d3a8592611e2948b505add88d8aba2#73a831ed43d3a8592611e2948b505add88d8aba2" dependencies = [ "bytemuck", @@ -1623,6 +1629,15 @@ dependencies = [ ] [[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", +] + +[[package]] name = "epaint" version = "0.31.1" source = "git+https://github.com/damus-io/egui?rev=73a831ed43d3a8592611e2948b505add88d8aba2#73a831ed43d3a8592611e2948b505add88d8aba2" @@ -1631,7 +1646,7 @@ dependencies = [ "ahash", "bytemuck", "ecolor", - "emath", + "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=73a831ed43d3a8592611e2948b505add88d8aba2)", "epaint_default_fonts", "log", "nohash-hasher", @@ -2626,6 +2641,15 @@ dependencies = [ ] [[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2856,6 +2880,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] +name = "log-once" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d8a05e3879b317b1b6dbf353e5bba7062bedcc59815267bb23eaa0c576cebf0" +dependencies = [ + "log", +] + +[[package]] name = "loop9" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2929,6 +2962,16 @@ dependencies = [ ] [[package]] +name = "memory-stats" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c73f5c649995a115e1a0220b35e4df0a1294500477f97a91d0660fb5abeb574a" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] name = "metal" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3275,6 +3318,7 @@ dependencies = [ "profiling", "puffin", "puffin_egui", + "re_memory", "serde", "serde_json", "strum", @@ -3387,6 +3431,15 @@ dependencies = [ ] [[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4427,6 +4480,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" [[package]] +name = "re_format" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2911aed8ef5b658871fc109072e056d77e8a8af9c438f9662ed5c88ac6850313" +dependencies = [ + "num-traits", +] + +[[package]] +name = "re_log" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1179e5409f57aebeedcea10d5cc4bea2bf5a9f237901501cb9a0e505ae06112f" +dependencies = [ + "env_filter", + "log", + "log-once", + "parking_lot", + "tracing", +] + +[[package]] +name = "re_memory" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee6d1efd5f0478116ca816ee1689995c7878107fe0ee06b7aecee8cd454ce00" +dependencies = [ + "ahash", + "backtrace", + "emath 0.31.1 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.14.0", + "memory-stats", + "nohash-hasher", + "once_cell", + "parking_lot", + "re_format", + "re_log", + "re_tracing", + "smallvec", + "sysinfo", + "wasm-bindgen", + "web-time 1.1.0", +] + +[[package]] +name = "re_tracing" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f4ddc0564f0ae8fbe75a5ade3c66c9ddecf51bfcdf6aa4fa8b9ccce695a8fc" +dependencies = [ + "puffin", +] + +[[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5253,6 +5360,20 @@ dependencies = [ ] [[package]] +name = "sysinfo" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "windows 0.52.0", +] + +[[package]] name = "system-deps" version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -6351,7 +6472,7 @@ dependencies = [ "wasm-bindgen", "web-sys", "wgpu-types", - "windows", + "windows 0.58.0", ] [[package]] @@ -6411,6 +6532,16 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" @@ -6421,6 +6552,15 @@ dependencies = [ [[package]] name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" diff --git a/Cargo.toml b/Cargo.toml @@ -73,6 +73,7 @@ lightning-invoice = "0.33.1" secp256k1 = "0.30.0" hashbrown = "0.15.2" openai-api-rs = "6.0.3" +re_memory = "0.23.4" [profile.small] inherits = 'release' diff --git a/crates/notedeck_chrome/Cargo.toml b/crates/notedeck_chrome/Cargo.toml @@ -28,6 +28,7 @@ tracing-appender = { workspace = true } tracing-subscriber = { workspace = true } tracing = { workspace = true } profiling = { workspace = true } +re_memory = { workspace = true, optional = true } [dev-dependencies] tempfile = { workspace = true } @@ -45,6 +46,7 @@ path = "src/notedeck.rs" [features] default = [] +memory = ["re_memory"] puffin = ["profiling/profile-with-puffin", "dep:puffin"] debug-widget-callstack = ["egui/callstack"] debug-interactive-widgets = [] diff --git a/crates/notedeck_chrome/src/chrome.rs b/crates/notedeck_chrome/src/chrome.rs @@ -19,6 +19,9 @@ pub struct Chrome { open: bool, tab_selected: i32, apps: Vec<NotedeckApp>, + + #[cfg(feature = "memory")] + show_memory_debug: bool, } impl Default for Chrome { @@ -28,6 +31,9 @@ impl Default for Chrome { tab_selected: 0, open: true, apps: vec![], + + #[cfg(feature = "memory")] + show_memory_debug: false, } } } @@ -220,7 +226,7 @@ impl Chrome { }); ui.with_layout(Layout::bottom_up(egui::Align::Center), |ui| { - if let Some(action) = bottomup_sidebar(app_ctx, ui) { + if let Some(action) = bottomup_sidebar(self, app_ctx, ui) { got_action = Some(action); } }); @@ -656,7 +662,11 @@ fn pfp_button(ctx: &mut AppContext, ui: &mut egui::Ui) -> egui::Response { /// The section of the chrome sidebar that starts at the /// bottom and goes up -fn bottomup_sidebar(ctx: &mut AppContext, ui: &mut egui::Ui) -> Option<ChromePanelAction> { +fn bottomup_sidebar( + _chrome: &mut Chrome, + ctx: &mut AppContext, + ui: &mut egui::Ui, +) -> Option<ChromePanelAction> { ui.add_space(8.0); let pfp_resp = pfp_button(ctx, ui); @@ -701,6 +711,26 @@ fn bottomup_sidebar(ctx: &mut AppContext, ui: &mut egui::Ui) -> Option<ChromePan "{:10.1}", ctx.frame_history.mean_frame_time() * 1e3 )); + + #[cfg(feature = "memory")] + { + let mem_use = re_memory::MemoryUse::capture(); + if let Some(counted) = mem_use.counted { + let memory_resp = ui.label(format!("{}", format_bytes(counted as f64))); + if memory_resp.clicked() { + _chrome.show_memory_debug = !_chrome.show_memory_debug; + } else if memory_resp.hovered() { + notedeck_ui::show_pointer(ui); + } + } + if let Some(resident) = mem_use.resident { + ui.weak(format!("{}", format_bytes(resident as f64))); + } + + if _chrome.show_memory_debug { + egui::Window::new("Memory Debug").show(ui.ctx(), memory_debug_ui); + } + } } if pfp_resp.hovered() @@ -725,3 +755,100 @@ fn bottomup_sidebar(ctx: &mut AppContext, ui: &mut egui::Ui) -> Option<ChromePan None } } + +#[cfg(feature = "memory")] +fn memory_debug_ui(ui: &mut egui::Ui) { + let Some(stats) = &re_memory::accounting_allocator::tracking_stats() else { + ui.label("re_memory::accounting_allocator::set_tracking_callstacks(true); not set!!"); + return; + }; + + egui::ScrollArea::vertical().show(ui, |ui| { + ui.label(format!( + "track_size_threshold {}", + stats.track_size_threshold + )); + ui.label(format!( + "untracked {} {}", + stats.untracked.count, + format_bytes(stats.untracked.size as f64) + )); + ui.label(format!( + "stochastically_tracked {} {}", + stats.stochastically_tracked.count, + format_bytes(stats.stochastically_tracked.size as f64), + )); + ui.label(format!( + "fully_tracked {} {}", + stats.fully_tracked.count, + format_bytes(stats.fully_tracked.size as f64) + )); + ui.label(format!( + "overhead {} {}", + stats.overhead.count, + format_bytes(stats.overhead.size as f64) + )); + + ui.separator(); + + for (i, callstack) in stats.top_callstacks.iter().enumerate() { + let full_bt = format!("{}", callstack.readable_backtrace); + let mut lines = full_bt.lines().skip(5); + let bt_header = lines.nth(0).map_or("??", |v| v); + let header = format!( + "#{} {bt_header} {}x {}", + i + 1, + callstack.extant.count, + format_bytes(callstack.extant.size as f64) + ); + + egui::CollapsingHeader::new(header) + .id_salt(("mem_cs", i)) + .show(ui, |ui| { + ui.label(lines.collect::<Vec<_>>().join("\n")); + }); + } + }); +} + +/// Pretty format a number of bytes by using SI notation (base2), e.g. +/// +/// ``` +/// # use re_format::format_bytes; +/// assert_eq!(format_bytes(123.0), "123 B"); +/// assert_eq!(format_bytes(12_345.0), "12.1 KiB"); +/// assert_eq!(format_bytes(1_234_567.0), "1.2 MiB"); +/// assert_eq!(format_bytes(123_456_789.0), "118 MiB"); +/// ``` +#[cfg(feature = "memory")] +pub fn format_bytes(number_of_bytes: f64) -> String { + /// The minus character: <https://www.compart.com/en/unicode/U+2212> + /// Looks slightly different from the normal hyphen `-`. + const MINUS: char = '−'; + + if number_of_bytes < 0.0 { + format!("{MINUS}{}", format_bytes(-number_of_bytes)) + } else if number_of_bytes == 0.0 { + "0 B".to_owned() + } else if number_of_bytes < 1.0 { + format!("{number_of_bytes} B") + } else if number_of_bytes < 20.0 { + let is_integer = number_of_bytes.round() == number_of_bytes; + if is_integer { + format!("{number_of_bytes:.0} B") + } else { + format!("{number_of_bytes:.1} B") + } + } else if number_of_bytes < 10.0_f64.exp2() { + format!("{number_of_bytes:.0} B") + } else if number_of_bytes < 20.0_f64.exp2() { + let decimals = (10.0 * number_of_bytes < 20.0_f64.exp2()) as usize; + format!("{:.*} KiB", decimals, number_of_bytes / 10.0_f64.exp2()) + } else if number_of_bytes < 30.0_f64.exp2() { + let decimals = (10.0 * number_of_bytes < 30.0_f64.exp2()) as usize; + format!("{:.*} MiB", decimals, number_of_bytes / 20.0_f64.exp2()) + } else { + let decimals = (10.0 * number_of_bytes < 40.0_f64.exp2()) as usize; + format!("{:.*} GiB", decimals, number_of_bytes / 30.0_f64.exp2()) + } +} diff --git a/crates/notedeck_chrome/src/notedeck.rs b/crates/notedeck_chrome/src/notedeck.rs @@ -1,6 +1,14 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#[cfg(feature = "memory")] +use re_memory::AccountingAllocator; + +#[cfg(feature = "memory")] +#[global_allocator] +static GLOBAL: AccountingAllocator<std::alloc::System> = + AccountingAllocator::new(std::alloc::System); + use notedeck::{DataPath, DataPathType, Notedeck}; use notedeck_chrome::{ setup::{generate_native_options, setup_chrome}, @@ -66,6 +74,9 @@ fn setup_logging(path: &DataPath) -> Option<WorkerGuard> { #[cfg(not(target_arch = "wasm32"))] #[tokio::main] async fn main() { + #[cfg(feature = "memory")] + re_memory::accounting_allocator::set_tracking_callstacks(true); + let base_path = DataPath::default_base_or_cwd(); let path = DataPath::new(base_path.clone());