use std::{fmt, marker::PhantomData}; use hir::{ db::{AstIdMapQuery, AttrsQuery, BlockDefMapQuery, ParseMacroExpansionQuery}, Attr, Attrs, ExpandResult, MacroFile, Module, }; use ide_db::{ base_db::{ salsa::{ debug::{DebugQueryTable, TableEntry}, Query, QueryTable, }, CrateId, FileId, FileTextQuery, ParseQuery, SourceDatabase, SourceRootId, }, symbol_index::ModuleSymbolsQuery, }; use ide_db::{ symbol_index::{LibrarySymbolsQuery, SymbolIndex}, RootDatabase, }; use itertools::Itertools; use profile::{memory_usage, Bytes}; use std::env; 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(FileTextQuery.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()); if env::var("RA_COUNT").is_ok() { format_to!(buf, "\nCounts:\n{}", profile::countme::get_all()); } 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, "\nFile info:\n"); 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 krate in crates { let display_crate = |krate: CrateId| match &crate_graph[krate].display_name { Some(it) => format!("{it}({})", krate.into_raw()), None => format!("{}", krate.into_raw()), }; format_to!(buf, "Crate: {}\n", display_crate(krate)); let deps = crate_graph[krate] .dependencies .iter() .map(|dep| format!("{}={:?}", dep.name, dep.crate_id)) .format(", "); format_to!(buf, "Dependencies: {}\n", deps); } } buf.trim().to_string() } 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 FileTextQuery { 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, _: FileId, value: Option>) { self.total += 1; self.retained += value.is_some() as usize; } } impl StatCollect, M)>> for SyntaxTreeStats { fn collect_entry(&mut self, _: MacroFile, 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()); } }