diff --git a/crates/re_query_cache/src/cache_stats.rs b/crates/re_query_cache/src/cache_stats.rs index 78eb0540221f..a03541380873 100644 --- a/crates/re_query_cache/src/cache_stats.rs +++ b/crates/re_query_cache/src/cache_stats.rs @@ -1,11 +1,25 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, sync::atomic::AtomicBool}; use re_log_types::EntityPath; +use re_types_core::ComponentName; use crate::Caches; // --- +/// If `true`, enables the much-more-costly-to-compute per-component stats. +static ENABLE_DETAILED_STATS: AtomicBool = AtomicBool::new(false); + +#[inline] +pub fn detailed_stats() -> bool { + ENABLE_DETAILED_STATS.load(std::sync::atomic::Ordering::Relaxed) +} + +#[inline] +pub fn set_detailed_stats(b: bool) { + ENABLE_DETAILED_STATS.store(b, std::sync::atomic::Ordering::Relaxed); +} + /// Stats for all primary caches. /// /// Fetch them via [`Caches::stats`]. @@ -27,12 +41,24 @@ impl CachesStats { /// Stats for a cached entity. #[derive(Debug, Clone)] pub struct CachedEntityStats { + pub total_times: u64, pub total_size_bytes: u64, - pub num_cached_timestamps: u64, + + /// Only if [`detailed_stats`] returns `true` (see [`set_detailed_stats`]). + pub per_component: Option>, +} + +/// Stats for a cached component. +#[derive(Default, Debug, Clone)] +pub struct CachedComponentStats { + pub total_times: u64, + pub total_values: u64, } impl Caches { /// Computes the stats for all primary caches. + /// + /// `per_component` toggles per-component stats. pub fn stats() -> CachesStats { re_tracing::profile_function!(); @@ -44,19 +70,33 @@ impl Caches { .map(|(key, caches_per_arch)| { (key.entity_path.clone(), { let mut total_size_bytes = 0u64; - let mut num_cached_timestamps = 0u64; + let mut total_times = 0u64; + let mut per_component = detailed_stats().then(BTreeMap::default); for latest_at_cache in caches_per_arch.latest_at_per_archetype.read().values() { let latest_at_cache = latest_at_cache.read(); total_size_bytes += latest_at_cache.total_size_bytes; - num_cached_timestamps = latest_at_cache.per_data_time.len() as _; + total_times = latest_at_cache.per_data_time.len() as _; + + if let Some(per_component) = per_component.as_mut() { + for bucket in latest_at_cache.per_data_time.values() { + for (component_name, data) in &bucket.read().components { + let stats: &mut CachedComponentStats = + per_component.entry(*component_name).or_default(); + stats.total_times += data.dyn_num_entries() as u64; + stats.total_values += data.dyn_num_values() as u64; + } + } + } } CachedEntityStats { total_size_bytes, - num_cached_timestamps, + total_times, + + per_component, } }) }) diff --git a/crates/re_query_cache/src/flat_vec_deque.rs b/crates/re_query_cache/src/flat_vec_deque.rs index 2e89d7e052f3..038ce1037f2a 100644 --- a/crates/re_query_cache/src/flat_vec_deque.rs +++ b/crates/re_query_cache/src/flat_vec_deque.rs @@ -16,6 +16,18 @@ pub trait ErasedFlatVecDeque: std::any::Any { fn into_any(self: Box) -> Box; + /// Dynamically dispatches to [`FlatVecDeque::num_entries`]. + /// + /// This is prefixed with `dyn_` to avoid method dispatch ambiguities that are very hard to + /// avoid even with explicit syntax and that silently lead to infinite recursions. + fn dyn_num_entries(&self) -> usize; + + /// Dynamically dispatches to [`FlatVecDeque::num_values`]. + /// + /// This is prefixed with `dyn_` to avoid method dispatch ambiguities that are very hard to + /// avoid even with explicit syntax and that silently lead to infinite recursions. + fn dyn_num_values(&self) -> usize; + /// Dynamically dispatches to [`FlatVecDeque::remove`]. /// /// This is prefixed with `dyn_` to avoid method dispatch ambiguities that are very hard to @@ -51,6 +63,16 @@ impl ErasedFlatVecDeque for FlatVecDeque { self } + #[inline] + fn dyn_num_entries(&self) -> usize { + self.num_entries() + } + + #[inline] + fn dyn_num_values(&self) -> usize { + self.num_values() + } + #[inline] fn dyn_remove(&mut self, at: usize) { FlatVecDeque::::remove(self, at); diff --git a/crates/re_query_cache/src/lib.rs b/crates/re_query_cache/src/lib.rs index 285b50192024..a11b8a0f6e0f 100644 --- a/crates/re_query_cache/src/lib.rs +++ b/crates/re_query_cache/src/lib.rs @@ -6,7 +6,9 @@ mod flat_vec_deque; mod query; pub use self::cache::{AnyQuery, Caches}; -pub use self::cache_stats::{CachedEntityStats, CachesStats}; +pub use self::cache_stats::{ + detailed_stats, set_detailed_stats, CachedComponentStats, CachedEntityStats, CachesStats, +}; pub use self::flat_vec_deque::{ErasedFlatVecDeque, FlatVecDeque}; pub use self::query::{ query_archetype_pov1, query_archetype_with_history_pov1, MaybeCachedComponentData, diff --git a/crates/re_viewer/src/ui/memory_panel.rs b/crates/re_viewer/src/ui/memory_panel.rs index 5cfbacaba054..26c7caf68c69 100644 --- a/crates/re_viewer/src/ui/memory_panel.rs +++ b/crates/re_viewer/src/ui/memory_panel.rs @@ -1,7 +1,8 @@ use re_data_store::{DataStoreConfig, DataStoreRowStats, DataStoreStats}; use re_format::{format_bytes, format_number}; +use re_log_types::EntityPath; use re_memory::{util::sec_since_start, MemoryHistory, MemoryLimit, MemoryUse}; -use re_query_cache::{CachedEntityStats, CachesStats}; +use re_query_cache::{CachedComponentStats, CachedEntityStats, CachesStats}; use re_renderer::WgpuResourcePoolStatistics; use crate::{env_vars::RERUN_TRACK_ALLOCATIONS, store_hub::StoreHubStats}; @@ -106,7 +107,7 @@ impl MemoryPanel { ui.separator(); ui.collapsing("Primary Cache Resources", |ui| { - Self::caches_stats(ui, caches_stats); + Self::caches_stats(ui, re_ui, caches_stats); }); ui.separator(); @@ -309,13 +310,18 @@ impl MemoryPanel { }); } - fn caches_stats(ui: &mut egui::Ui, caches_stats: &CachesStats) { + fn caches_stats(ui: &mut egui::Ui, re_ui: &re_ui::ReUi, caches_stats: &CachesStats) { + let mut detailed_stats = re_query_cache::detailed_stats(); + re_ui + .checkbox(ui, &mut detailed_stats, "Detailed stats") + .on_hover_text("Show detailed statistics when hovering entity paths below.\nThis will slow down the program."); + re_query_cache::set_detailed_stats(detailed_stats); + egui::Grid::new("cache stats grid") .num_columns(3) .show(ui, |ui| { let CachesStats { latest_at } = caches_stats; - ui.label(egui::RichText::new("Stats").italics()); ui.label("Entity"); ui.label("Entries").on_hover_text( "How many timestamps distinct data timestamps have been cached?", @@ -323,21 +329,50 @@ impl MemoryPanel { ui.label("Size"); ui.end_row(); - fn label_entity_stats(ui: &mut egui::Ui, cache_stats: &CachedEntityStats) { - let &CachedEntityStats { + fn label_entity_stats( + ui: &mut egui::Ui, + cache_stats: &CachedEntityStats, + entity_path: &EntityPath, + ) { + let CachedEntityStats { total_size_bytes, - num_cached_timestamps, + total_times, + per_component, } = cache_stats; - ui.label(re_format::format_number(num_cached_timestamps as _)); - ui.label(re_format::format_bytes(total_size_bytes as _)); + let res = ui.label(entity_path.to_string()); + if let Some(per_component) = per_component.as_ref() { + res.on_hover_ui_at_pointer(|ui| { + egui::Grid::new("component cache stats grid") + .num_columns(3) + .show(ui, |ui| { + ui.label("Component"); + ui.label("Entries"); + ui.label("Count"); + ui.end_row(); + + for (component_name, stats) in per_component { + let &CachedComponentStats { + total_times, + total_values, + } = stats; + + ui.label(component_name.to_string()); + ui.label(re_format::format_number(total_times as _)); + ui.label(re_format::format_number(total_values as _)); + ui.end_row(); + } + }); + }); + } + + ui.label(re_format::format_number(*total_times as _)); + ui.label(re_format::format_bytes(*total_size_bytes as _)); + ui.end_row(); } for (entity_path, stats) in latest_at { - ui.label(entity_path.to_string()); - ui.label(""); - label_entity_stats(ui, stats); - ui.end_row(); + label_entity_stats(ui, stats, entity_path); } }); }