use std::{fmt, marker::PhantomData}; use hir::{ db::{AstIdMapQuery, AttrsQuery, BlockDefMapQuery, ParseMacroExpansionQuery}, Attr, Attrs, ExpandResult, MacroFileId, Module, }; use ide_db::{ base_db::{ salsa::{ debug::{DebugQueryTable, TableEntry}, Query, QueryTable, }, CompressedFileTextQuery, CrateData, ParseQuery, SourceDatabase, SourceRootId, }, symbol_index::ModuleSymbolsQuery, }; use ide_db::{ symbol_index::{LibrarySymbolsQuery, SymbolIndex}, RootDatabase, }; use itertools::Itertools; use profile::{memory_usage, Bytes}; use span::{EditionedFileId, FileId}; use stdx::format_to; use syntax::{ast, Parse, SyntaxNode}; use triomphe::Arc; // Feature: Status // // Shows internal statistic about memory usage of rust-analyzer. // // |=== // | Editor | Action Name // // | VS Code | **rust-analyzer: Status** // |=== // image::https://user-images.githubusercontent.com/48062697/113065584-05f34500-91b1-11eb-98cc-5c196f76be7f.gif[] pub(crate) fn status(db: &RootDatabase, file_id: Option) -> String { let mut buf = String::new(); format_to!(buf, "{}\n", collect_query(CompressedFileTextQuery.in_db(db))); format_to!(buf, "{}\n", collect_query(ParseQuery.in_db(db))); format_to!(buf, "{}\n", collect_query(ParseMacroExpansionQuery.in_db(db))); format_to!(buf, "{}\n", collect_query(LibrarySymbolsQuery.in_db(db))); format_to!(buf, "{}\n", collect_query(ModuleSymbolsQuery.in_db(db))); format_to!(buf, "{} in total\n", memory_usage()); format_to!(buf, "\nDebug info:\n"); format_to!(buf, "{}\n", collect_query(AttrsQuery.in_db(db))); format_to!(buf, "{} ast id maps\n", collect_query_count(AstIdMapQuery.in_db(db))); format_to!(buf, "{} block def maps\n", collect_query_count(BlockDefMapQuery.in_db(db))); if let Some(file_id) = file_id { format_to!(buf, "\nCrates for file {}:\n", file_id.index()); let crates = crate::parent_module::crates_for(db, file_id); if crates.is_empty() { format_to!(buf, "Does not belong to any crate"); } let crate_graph = db.crate_graph(); for crate_id in crates { let CrateData { root_file_id, edition, version, display_name, cfg_options, potential_cfg_options, env, dependencies, origin, is_proc_macro, } = &crate_graph[crate_id]; format_to!( buf, "Crate: {}\n", match display_name { Some(it) => format!("{it}({})", crate_id.into_raw()), None => format!("{}", crate_id.into_raw()), } ); format_to!(buf, " Root module file id: {}\n", root_file_id.index()); format_to!(buf, " Edition: {}\n", edition); format_to!(buf, " Version: {}\n", version.as_deref().unwrap_or("n/a")); format_to!(buf, " Enabled cfgs: {:?}\n", cfg_options); format_to!(buf, " Potential cfgs: {:?}\n", potential_cfg_options); format_to!(buf, " Env: {:?}\n", env); format_to!(buf, " Origin: {:?}\n", origin); format_to!(buf, " Is a proc macro crate: {}\n", is_proc_macro); let deps = dependencies .iter() .map(|dep| format!("{}={}", dep.name, dep.crate_id.into_raw())) .format(", "); format_to!(buf, " Dependencies: {}\n", deps); } } buf.trim().to_owned() } fn collect_query<'q, Q>(table: QueryTable<'q, Q>) -> ::Collector where QueryTable<'q, Q>: DebugQueryTable, Q: QueryCollect, ::Storage: 'q, ::Collector: StatCollect< as DebugQueryTable>::Key, as DebugQueryTable>::Value, >, { struct StatCollectorWrapper(C); impl, K, V> FromIterator> for StatCollectorWrapper { fn from_iter(iter: T) -> StatCollectorWrapper where T: IntoIterator>, { let mut res = C::default(); for entry in iter { res.collect_entry(entry.key, entry.value); } StatCollectorWrapper(res) } } table.entries::::Collector>>().0 } fn collect_query_count<'q, Q>(table: QueryTable<'q, Q>) -> usize where QueryTable<'q, Q>: DebugQueryTable, Q: Query, ::Storage: 'q, { struct EntryCounter(usize); impl FromIterator> for EntryCounter { fn from_iter(iter: T) -> EntryCounter where T: IntoIterator>, { EntryCounter(iter.into_iter().count()) } } table.entries::().0 } trait QueryCollect: Query { type Collector; } impl QueryCollect for LibrarySymbolsQuery { type Collector = SymbolsStats; } impl QueryCollect for ParseQuery { type Collector = SyntaxTreeStats; } impl QueryCollect for ParseMacroExpansionQuery { type Collector = SyntaxTreeStats; } impl QueryCollect for CompressedFileTextQuery { type Collector = FilesStats; } impl QueryCollect for ModuleSymbolsQuery { type Collector = SymbolsStats; } impl QueryCollect for AttrsQuery { type Collector = AttrsStats; } trait StatCollect: Default { fn collect_entry(&mut self, key: K, value: Option); } #[derive(Default)] struct FilesStats { total: usize, size: Bytes, } impl fmt::Display for FilesStats { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "{} of files", self.size) } } impl StatCollect> for FilesStats { fn collect_entry(&mut self, _: FileId, value: Option>) { self.total += 1; self.size += value.unwrap().len(); } } #[derive(Default)] pub(crate) struct SyntaxTreeStats { total: usize, pub(crate) retained: usize, } impl fmt::Display for SyntaxTreeStats { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!( fmt, "{} trees, {} preserved{}", self.total, self.retained, if MACROS { " (macros)" } else { "" } ) } } impl StatCollect> for SyntaxTreeStats { fn collect_entry(&mut self, _: EditionedFileId, value: Option>) { self.total += 1; self.retained += value.is_some() as usize; } } impl StatCollect, M)>> for SyntaxTreeStats { fn collect_entry( &mut self, _: MacroFileId, value: Option, M)>>, ) { self.total += 1; self.retained += value.is_some() as usize; } } struct SymbolsStats { total: usize, size: Bytes, phantom: PhantomData, } impl Default for SymbolsStats { fn default() -> Self { Self { total: Default::default(), size: Default::default(), phantom: PhantomData } } } impl fmt::Display for SymbolsStats { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "{} of module index symbols ({})", self.size, self.total) } } impl fmt::Display for SymbolsStats { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "{} of library index symbols ({})", self.size, self.total) } } impl StatCollect> for SymbolsStats { fn collect_entry(&mut self, _: Key, value: Option>) { if let Some(symbols) = value { self.total += symbols.len(); self.size += symbols.memory_size(); } } } #[derive(Default)] struct AttrsStats { entries: usize, total: usize, } impl fmt::Display for AttrsStats { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { let size = self.entries * std::mem::size_of::() + self.total * std::mem::size_of::(); let size = Bytes::new(size as _); write!( fmt, "{} attribute query entries, {} total attributes ({} for storing entries)", self.entries, self.total, size ) } } impl StatCollect for AttrsStats { fn collect_entry(&mut self, _: Key, value: Option) { self.entries += 1; self.total += value.map_or(0, |it| it.len()); } }