diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 454510177e8..3f47c85775a 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -14,16 +14,17 @@ use hir_def::{body::BodySourceMap, expr::ExprId, FunctionId};
 use hir_ty::{TyExt, TypeWalk};
 use ide::{Analysis, AnalysisHost, LineCol, RootDatabase};
 use ide_db::base_db::{
-    salsa::{self, ParallelDatabase},
-    SourceDatabaseExt,
+    salsa::{self, debug::DebugQueryTable, ParallelDatabase},
+    SourceDatabase, SourceDatabaseExt,
 };
 use itertools::Itertools;
 use oorandom::Rand32;
+use profile::{Bytes, StopWatch};
 use project_model::CargoConfig;
 use rayon::prelude::*;
 use rustc_hash::FxHashSet;
 use stdx::format_to;
-use syntax::AstNode;
+use syntax::{AstNode, SyntaxNode};
 use vfs::{Vfs, VfsPath};
 
 use crate::cli::{
@@ -33,7 +34,6 @@ use crate::cli::{
     progress_report::ProgressReport,
     report_metric, Result, Verbosity,
 };
-use profile::StopWatch;
 
 /// Need to wrap Snapshot to provide `Clone` impl for `map_with`
 struct Snap<DB>(DB);
@@ -137,6 +137,21 @@ impl flags::AnalysisStats {
             eprintln!("{}", profile::countme::get_all());
         }
 
+        if self.source_stats {
+            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<_>>() {
+                if let Some((val, _)) = db.parse_macro_expansion(e.key).value {
+                    total_macro_file_size += syntax_len(val.syntax_node())
+                }
+            }
+            eprintln!("source files: {}, macro files: {}", total_file_size, total_macro_file_size,);
+        }
+
         if self.memory_usage && verbosity.is_verbose() {
             print_memory_usage(host, vfs);
         }
@@ -361,3 +376,9 @@ fn shuffle<T>(rng: &mut Rand32, slice: &mut [T]) {
 fn percentage(n: u64, total: u64) -> u64 {
     (n * 100).checked_div(total).unwrap_or(100)
 }
+
+fn syntax_len(node: SyntaxNode) -> usize {
+    // Macro expanded code doesn't contain whitespace, so erase *all* whitespace
+    // to make macro and non-macro code comparable.
+    node.to_string().replace(|it: char| it.is_ascii_whitespace(), "").len()
+}
diff --git a/crates/rust-analyzer/src/cli/flags.rs b/crates/rust-analyzer/src/cli/flags.rs
index f8babe4bc3d..e2e250143c0 100644
--- a/crates/rust-analyzer/src/cli/flags.rs
+++ b/crates/rust-analyzer/src/cli/flags.rs
@@ -7,7 +7,7 @@ use ide_ssr::{SsrPattern, SsrRule};
 use crate::cli::Verbosity;
 
 xflags::xflags! {
-    src "./src/bin/flags.rs"
+    src "./src/cli/flags.rs"
 
     /// LSP server for the Rust programming language.
     cmd rust-analyzer {
@@ -60,6 +60,8 @@ xflags::xflags! {
             optional --parallel
             /// Collect memory usage statistics.
             optional --memory-usage
+            /// 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
@@ -156,6 +158,7 @@ pub struct AnalysisStats {
     pub randomize: bool,
     pub parallel: bool,
     pub memory_usage: bool,
+    pub source_stats: bool,
     pub only: Option<String>,
     pub with_deps: bool,
     pub no_sysroot: bool,
@@ -190,9 +193,15 @@ pub struct ProcMacro;
 impl RustAnalyzer {
     pub const HELP: &'static str = Self::HELP_;
 
+    #[allow(dead_code)]
     pub fn from_env() -> xflags::Result<Self> {
         Self::from_env_()
     }
+
+    #[allow(dead_code)]
+    pub fn from_vec(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> {
+        Self::from_vec_(args)
+    }
 }
 // generated end