Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #19359 from davidbarsky/davidbarsky/more-stats-in-analysis-stats
analysis-stats: emit lines of code and item tree counts for workspace; dependencies
Lukas Wirth 2025-03-25
parent 3ed13b4 · parent 19b62b2 · commit 2afe50b
-rw-r--r--crates/hir-def/src/item_tree.rs25
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs195
-rw-r--r--crates/rust-analyzer/src/cli/flags.rs3
3 files changed, 192 insertions, 31 deletions
diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs
index ea87b0f700..2debbb1ee4 100644
--- a/crates/hir-def/src/item_tree.rs
+++ b/crates/hir-def/src/item_tree.rs
@@ -218,6 +218,22 @@ impl ItemTree {
Attrs::filter(db, krate, self.raw_attrs(of).clone())
}
+ /// Returns a count of a few, expensive items.
+ ///
+ /// For more detail, see [`ItemTreeDataStats`].
+ pub fn item_tree_stats(&self) -> ItemTreeDataStats {
+ match self.data {
+ Some(ref data) => ItemTreeDataStats {
+ traits: data.traits.len(),
+ impls: data.impls.len(),
+ mods: data.mods.len(),
+ macro_calls: data.macro_calls.len(),
+ macro_rules: data.macro_rules.len(),
+ },
+ None => ItemTreeDataStats::default(),
+ }
+ }
+
pub fn pretty_print(&self, db: &dyn DefDatabase, edition: Edition) -> String {
pretty::print_item_tree(db, self, edition)
}
@@ -329,6 +345,15 @@ struct ItemTreeData {
}
#[derive(Default, Debug, Eq, PartialEq)]
+pub struct ItemTreeDataStats {
+ pub traits: usize,
+ pub impls: usize,
+ pub mods: usize,
+ pub macro_calls: usize,
+ pub macro_rules: usize,
+}
+
+#[derive(Default, Debug, Eq, PartialEq)]
pub struct ItemTreeSourceMaps {
all_concatenated: Box<[TypesSourceMap]>,
structs_offset: u32,
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 29331000a1..66334e7738 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -2,7 +2,8 @@
//! errors.
use std::{
- env,
+ env, fmt,
+ ops::AddAssign,
time::{SystemTime, UNIX_EPOCH},
};
@@ -118,29 +119,80 @@ impl flags::AnalysisStats {
}
let mut item_tree_sw = self.stop_watch();
- let mut num_item_trees = 0;
let source_roots = krates
.iter()
.cloned()
.map(|krate| db.file_source_root(krate.root_file(db)).source_root_id(db))
.unique();
+
+ let mut dep_loc = 0;
+ let mut workspace_loc = 0;
+ let mut dep_item_trees = 0;
+ let mut workspace_item_trees = 0;
+
+ let mut workspace_item_stats = PrettyItemStats::default();
+ let mut dep_item_stats = PrettyItemStats::default();
+
for source_root_id in source_roots {
let source_root = db.source_root(source_root_id).source_root(db);
- if !source_root.is_library || self.with_deps {
- for file_id in source_root.iter() {
- if let Some(p) = source_root.path_for_file(&file_id) {
- if let Some((_, Some("rs"))) = p.name_and_extension() {
- db.file_item_tree(EditionedFileId::current_edition(file_id).into());
- num_item_trees += 1;
+ for file_id in source_root.iter() {
+ if let Some(p) = source_root.path_for_file(&file_id) {
+ if let Some((_, Some("rs"))) = p.name_and_extension() {
+ // measure workspace/project code
+ if !source_root.is_library || self.with_deps {
+ let length = db.file_text(file_id).text(db).lines().count();
+ let item_stats = db
+ .file_item_tree(EditionedFileId::current_edition(file_id).into())
+ .item_tree_stats()
+ .into();
+
+ workspace_loc += length;
+ workspace_item_trees += 1;
+ workspace_item_stats += item_stats;
+ } else {
+ let length = db.file_text(file_id).text(db).lines().count();
+ let item_stats = db
+ .file_item_tree(EditionedFileId::current_edition(file_id).into())
+ .item_tree_stats()
+ .into();
+
+ dep_loc += length;
+ dep_item_trees += 1;
+ dep_item_stats += item_stats;
}
}
}
}
}
- eprintln!(" item trees: {num_item_trees}");
+ eprintln!(" item trees: {workspace_item_trees}");
let item_tree_time = item_tree_sw.elapsed();
+
+ eprintln!(
+ " dependency lines of code: {}, item trees: {}",
+ UsizeWithUnderscore(dep_loc),
+ UsizeWithUnderscore(dep_item_trees),
+ );
+ eprintln!(" dependency item stats: {}", dep_item_stats);
+
+ // FIXME(salsa-transition): bring back stats for ParseQuery (file size)
+ // and ParseMacroExpansionQuery (macro expansion "file") size whenever we implement
+ // Salsa's memory usage tracking works with tracked functions.
+
+ // let mut total_file_size = Bytes::default();
+ // for e in ide_db::base_db::ParseQuery.in_db(db).entries::<Vec<_>>() {
+ // total_file_size += syntax_len(db.parse(e.key).syntax_node())
+ // }
+
+ // let mut total_macro_file_size = Bytes::default();
+ // for e in hir::db::ParseMacroExpansionQuery.in_db(db).entries::<Vec<_>>() {
+ // let val = db.parse_macro_expansion(e.key).value.0;
+ // total_macro_file_size += syntax_len(val.syntax_node())
+ // }
+ // eprintln!("source files: {total_file_size}, macro files: {total_macro_file_size}");
+
eprintln!("{:<20} {}", "Item Tree Collection:", item_tree_time);
report_metric("item tree time", item_tree_time.time.as_millis() as u64, "ms");
+ eprintln!(" Total Statistics:");
let mut crate_def_map_sw = self.stop_watch();
let mut num_crates = 0;
@@ -163,11 +215,16 @@ impl flags::AnalysisStats {
shuffle(&mut rng, &mut visit_queue);
}
- eprint!(" crates: {num_crates}");
+ eprint!(" crates: {num_crates}");
let mut num_decls = 0;
let mut bodies = Vec::new();
let mut adts = Vec::new();
let mut file_ids = Vec::new();
+
+ let mut num_traits = 0;
+ let mut num_macro_rules_macros = 0;
+ let mut num_proc_macros = 0;
+
while let Some(module) = visit_queue.pop() {
if visited_modules.insert(module) {
file_ids.extend(module.as_source_file_id(db));
@@ -189,6 +246,14 @@ impl flags::AnalysisStats {
bodies.push(DefWithBody::from(c));
}
ModuleDef::Static(s) => bodies.push(DefWithBody::from(s)),
+ ModuleDef::Trait(_) => num_traits += 1,
+ ModuleDef::Macro(m) => match m.kind(db) {
+ hir::MacroKind::Declarative => num_macro_rules_macros += 1,
+ hir::MacroKind::Derive
+ | hir::MacroKind::Attr
+ | hir::MacroKind::ProcMacro => num_proc_macros += 1,
+ _ => (),
+ },
_ => (),
};
}
@@ -217,6 +282,26 @@ impl flags::AnalysisStats {
.filter(|it| matches!(it, DefWithBody::Const(_) | DefWithBody::Static(_)))
.count(),
);
+
+ eprintln!(" Workspace:");
+ eprintln!(
+ " traits: {num_traits}, macro_rules macros: {num_macro_rules_macros}, proc_macros: {num_proc_macros}"
+ );
+ eprintln!(
+ " lines of code: {}, item trees: {}",
+ UsizeWithUnderscore(workspace_loc),
+ UsizeWithUnderscore(workspace_item_trees),
+ );
+ eprintln!(" usages: {}", workspace_item_stats);
+
+ eprintln!(" Dependencies:");
+ eprintln!(
+ " lines of code: {}, item trees: {}",
+ UsizeWithUnderscore(dep_loc),
+ UsizeWithUnderscore(dep_item_trees),
+ );
+ eprintln!(" declarations: {}", dep_item_stats);
+
let crate_def_map_time = crate_def_map_sw.elapsed();
eprintln!("{:<20} {}", "Item Collection:", crate_def_map_time);
report_metric("crate def map time", crate_def_map_time.time.as_millis() as u64, "ms");
@@ -264,24 +349,6 @@ impl flags::AnalysisStats {
}
report_metric("total memory", total_span.memory.allocated.megabytes() as u64, "MB");
- if self.source_stats {
- // FIXME(salsa-transition): bring back stats for ParseQuery (file size)
- // and ParseMacroExpansionQuery (mcaro expansion "file") size whenever we implement
- // Salsa's memory usage tracking works with tracked functions.
-
- // let mut total_file_size = Bytes::default();
- // for e in ide_db::base_db::ParseQuery.in_db(db).entries::<Vec<_>>() {
- // total_file_size += syntax_len(db.parse(e.key).syntax_node())
- // }
-
- // let mut total_macro_file_size = Bytes::default();
- // for e in hir::db::ParseMacroExpansionQuery.in_db(db).entries::<Vec<_>>() {
- // let val = db.parse_macro_expansion(e.key).value.0;
- // total_macro_file_size += syntax_len(val.syntax_node())
- // }
- // eprintln!("source files: {total_file_size}, macro files: {total_macro_file_size}");
- }
-
if verbosity.is_verbose() {
print_memory_usage(host, vfs);
}
@@ -1217,6 +1284,78 @@ fn percentage(n: u64, total: u64) -> u64 {
(n * 100).checked_div(total).unwrap_or(100)
}
+#[derive(Default, Debug, Eq, PartialEq)]
+struct UsizeWithUnderscore(usize);
+
+impl fmt::Display for UsizeWithUnderscore {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let num_str = self.0.to_string();
+
+ if num_str.len() <= 3 {
+ return write!(f, "{}", num_str);
+ }
+
+ let mut result = String::new();
+
+ for (count, ch) in num_str.chars().rev().enumerate() {
+ if count > 0 && count % 3 == 0 {
+ result.push('_');
+ }
+ result.push(ch);
+ }
+
+ let result = result.chars().rev().collect::<String>();
+ write!(f, "{}", result)
+ }
+}
+
+impl std::ops::AddAssign for UsizeWithUnderscore {
+ fn add_assign(&mut self, other: UsizeWithUnderscore) {
+ self.0 += other.0;
+ }
+}
+
+#[derive(Default, Debug, Eq, PartialEq)]
+struct PrettyItemStats {
+ traits: UsizeWithUnderscore,
+ impls: UsizeWithUnderscore,
+ mods: UsizeWithUnderscore,
+ macro_calls: UsizeWithUnderscore,
+ macro_rules: UsizeWithUnderscore,
+}
+
+impl From<hir_def::item_tree::ItemTreeDataStats> for PrettyItemStats {
+ fn from(value: hir_def::item_tree::ItemTreeDataStats) -> Self {
+ Self {
+ traits: UsizeWithUnderscore(value.traits),
+ impls: UsizeWithUnderscore(value.impls),
+ mods: UsizeWithUnderscore(value.mods),
+ macro_calls: UsizeWithUnderscore(value.macro_calls),
+ macro_rules: UsizeWithUnderscore(value.macro_rules),
+ }
+ }
+}
+
+impl AddAssign for PrettyItemStats {
+ fn add_assign(&mut self, rhs: Self) {
+ self.traits += rhs.traits;
+ self.impls += rhs.impls;
+ self.mods += rhs.mods;
+ self.macro_calls += rhs.macro_calls;
+ self.macro_rules += rhs.macro_rules;
+ }
+}
+
+impl fmt::Display for PrettyItemStats {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "traits: {}, impl: {}, mods: {}, macro calls: {}, macro rules: {}",
+ self.traits, self.impls, self.mods, self.macro_calls, self.macro_rules
+ )
+ }
+}
+
// FIXME(salsa-transition): bring this back whenever we implement
// Salsa's memory usage tracking to work with tracked functions.
// fn syntax_len(node: SyntaxNode) -> usize {
diff --git a/crates/rust-analyzer/src/cli/flags.rs b/crates/rust-analyzer/src/cli/flags.rs
index ff24602144..13075d4994 100644
--- a/crates/rust-analyzer/src/cli/flags.rs
+++ b/crates/rust-analyzer/src/cli/flags.rs
@@ -62,8 +62,6 @@ xflags::xflags! {
optional --randomize
/// Run type inference in parallel.
optional --parallel
- /// Print the total length of all source and macro files (whitespace is not counted).
- optional --source-stats
/// Only analyze items matching this path.
optional -o, --only path: String
@@ -231,7 +229,6 @@ pub struct AnalysisStats {
pub output: Option<OutputFormat>,
pub randomize: bool,
pub parallel: bool,
- pub source_stats: bool,
pub only: Option<String>,
pub with_deps: bool,
pub no_sysroot: bool,