use std::path::PathBuf;
use std::sync::Arc;
use crate::db::MirDatabase;
use crate::{discover_files, perf_fixture::PerfFixture, AnalysisSession, BatchOptions, PhpVersion};
#[test]
#[ignore = "long-running measurement; run explicitly with --release --ignored"]
fn measure_per_function_inference_size() {
let Some(fixture) = PerfFixture::discover() else {
eprintln!("Skipping: no supported perf fixture found.");
return;
};
let root = fixture.root();
if !fixture.has_full_corpus() {
eprintln!(
"Skipping: fixture not found or incomplete at {}. Provide MIR_PERF_FIXTURE/MIR_LARAVEL_FIXTURE/MIR_SYMFONY_FIXTURE or run `bash crates/mir-analyzer/benches/download-fixtures.sh` first.",
root.display()
);
return;
}
let vendor_files = discover_files(&fixture.vendor_root());
let project_files = discover_files(&fixture.src_root());
let all_files: Vec<PathBuf> = vendor_files
.iter()
.chain(project_files.iter())
.cloned()
.collect();
eprintln!(
"Corpus: {} files ({} vendor + {} project)",
all_files.len(),
vendor_files.len(),
project_files.len()
);
let t_start = std::time::Instant::now();
let mut session = AnalysisSession::new(PhpVersion::LATEST);
session.ensure_all_stubs();
let result = session.analyze_paths(&all_files, &BatchOptions::new());
let elapsed = t_start.elapsed();
eprintln!("Analysis completed in {:?}", elapsed);
let db = session.snapshot_db();
let n_functions = crate::db::workspace_functions(&db).len();
let class_fqcns = crate::db::workspace_classes(&db);
let mut n_methods = 0usize;
for fqcn in class_fqcns.iter() {
let here = crate::db::Fqcn::from_str(&db, fqcn.as_str());
if let Some(class) = crate::db::find_class_like(&db, here) {
n_methods += class.own_methods().len();
}
}
let n_callables = n_functions + n_methods;
let n_issues = result.issues.len();
let n_ref_locs = db.all_reference_location_pairs().len();
let n_symbols = result.symbols.len();
let sizeof_issue = std::mem::size_of::<mir_issues::Issue>();
let sizeof_refloc = std::mem::size_of::<crate::db::RefLoc>();
let sizeof_union = std::mem::size_of::<mir_types::Type>();
let issues_per_fn = n_issues as f64 / n_callables.max(1) as f64;
let refs_per_fn = n_ref_locs as f64 / n_callables.max(1) as f64;
let symbols_per_fn = n_symbols as f64 / n_callables.max(1) as f64;
let avg_issues_bytes = issues_per_fn * sizeof_issue as f64;
let avg_refs_bytes = refs_per_fn * sizeof_refloc as f64;
let avg_return_bytes = sizeof_union as f64;
let salsa_overhead_bytes = 64.0;
let avg_result_bytes =
avg_issues_bytes + avg_refs_bytes + avg_return_bytes + salsa_overhead_bytes;
let total_cache_bytes = avg_result_bytes * n_callables as f64;
eprintln!();
eprintln!("=== Per-function inference measurement ===");
eprintln!("functions (free): {}", n_functions);
eprintln!("methods (own): {}", n_methods);
eprintln!("total callables: {}", n_callables);
eprintln!();
eprintln!("total issues: {}", n_issues);
eprintln!("total ref_locs: {}", n_ref_locs);
eprintln!("total resolved symbols: {}", n_symbols);
eprintln!();
eprintln!(
"per-fn issues: {:.2} (~{:.0} bytes/fn)",
issues_per_fn, avg_issues_bytes
);
eprintln!(
"per-fn ref_locs: {:.2} (~{:.0} bytes/fn)",
refs_per_fn, avg_refs_bytes
);
eprintln!("per-fn symbols: {:.2}", symbols_per_fn);
eprintln!("per-fn return type: ~{} bytes", sizeof_union);
eprintln!("salsa overhead: ~64 bytes/fn");
eprintln!();
eprintln!(
"estimated FunctionInferenceResult: ~{:.0} bytes/fn",
avg_result_bytes
);
eprintln!(
"estimated total cache: ~{:.1} MB ({} callables × ~{:.0} B)",
total_cache_bytes / (1024.0 * 1024.0),
n_callables,
avg_result_bytes
);
eprintln!();
eprintln!("(Cold-start time: {:?})", elapsed);
assert!(n_callables > 1000, "fixture too small to be meaningful");
let mb = total_cache_bytes / (1024.0 * 1024.0);
assert!(
mb < 500.0,
"estimated cache too large ({:.1} MB) — reconsider per-fn caching",
mb
);
let _ = Arc::new(result);
}