use std::sync::Arc;
use std::time::Instant;
use mir_analyzer::{
discover_files, perf_fixture::PerfFixture, AnalysisSession, IndexCancel, Name, PhpVersion,
};
fn main() {
let Some(fixture) = PerfFixture::discover() else {
eprintln!("No supported perf fixture found.");
std::process::exit(2);
};
let fixture_root = fixture.root();
if !fixture.has_full_corpus() {
eprintln!(
"Perf fixture not found or incomplete at {}; provide MIR_PERF_FIXTURE/MIR_LARAVEL_FIXTURE/MIR_SYMFONY_FIXTURE or run `bash {}/benches/download-fixtures.sh`",
fixture_root.display(),
env!("CARGO_MANIFEST_DIR")
);
std::process::exit(2);
}
let psr4 = Arc::new(
mir_analyzer::composer::Psr4Map::from_composer(fixture_root)
.expect("failed to load composer.json"),
);
let mut session = AnalysisSession::new(PhpVersion::LATEST).with_psr4(psr4);
session.ensure_all_stubs();
let project_files = discover_files(&fixture.src_root());
let vendor_files = discover_files(&fixture.vendor_root());
eprintln!(
"loaded {} project files, {} vendor files",
project_files.len(),
vendor_files.len()
);
let workspace: Vec<(Arc<str>, Arc<str>)> = project_files
.iter()
.chain(vendor_files.iter())
.filter_map(|p| {
let src = std::fs::read_to_string(p).ok()?;
Some((
Arc::<str>::from(p.to_string_lossy().as_ref()),
Arc::<str>::from(src),
))
})
.collect();
session.set_workspace_files(workspace);
let project_paths: Vec<Arc<str>> = project_files
.iter()
.map(|p| Arc::<str>::from(p.to_string_lossy().as_ref()))
.collect();
let target = Name::class("Illuminate\\Support\\Str");
let gen_before = session.index_generation();
let t0 = Instant::now();
let refs = session
.indexed_references_to(
&target,
&project_paths,
false,
mir_analyzer::ReferenceIncludes::Plain,
&|| false,
)
.expect("not cancelled");
let cold_elapsed = t0.elapsed();
eprintln!(
"cold indexed_references_to({target:?}): {:.3}s, {} references found, {} revision bumps",
cold_elapsed.as_secs_f64(),
refs.len(),
session.index_generation() - gen_before
);
let t1 = Instant::now();
let refs_warm = session
.indexed_references_to(
&target,
&project_paths,
false,
mir_analyzer::ReferenceIncludes::Plain,
&|| false,
)
.expect("not cancelled");
eprintln!(
"warm repeat: {:.3}s, {} references found",
t1.elapsed().as_secs_f64(),
refs_warm.len()
);
assert_eq!(
refs.len(),
refs_warm.len(),
"warm repeat must match cold results"
);
let cancel = IndexCancel::new();
let dependents_target = project_files
.iter()
.find(|p| p.to_string_lossy().ends_with("Arr.php"))
.expect("Arr.php present in fixture");
let t2 = Instant::now();
let dependents =
session.reanalyze_dependents_cancellable(&dependents_target.to_string_lossy(), &cancel);
eprintln!(
"reanalyze_dependents(Arr.php): {:.3}s, {} dependents",
t2.elapsed().as_secs_f64(),
dependents.len()
);
let t3 = Instant::now();
let subtypes = session.subtype_files("Illuminate\\Support\\ServiceProvider");
eprintln!(
"subtype_files(ServiceProvider) cold: {:.3}s, {} files",
t3.elapsed().as_secs_f64(),
subtypes.len()
);
let t4 = Instant::now();
let n = 5;
for _ in 0..n {
let repeat = session.subtype_files("Illuminate\\Support\\ServiceProvider");
assert_eq!(repeat.len(), subtypes.len());
}
eprintln!(
"subtype_files(ServiceProvider) repeat: {:.3}s avg over {n}",
t4.elapsed().as_secs_f64() / n as f64
);
let psr4b = Arc::new(
mir_analyzer::composer::Psr4Map::from_composer(fixture_root)
.expect("failed to load composer.json"),
);
let mut session2 = AnalysisSession::new(PhpVersion::LATEST).with_psr4(psr4b);
session2.ensure_all_stubs();
let project_only: Vec<(Arc<str>, Arc<str>)> = project_files
.iter()
.filter_map(|p| {
let src = std::fs::read_to_string(p).ok()?;
Some((
Arc::<str>::from(p.to_string_lossy().as_ref()),
Arc::<str>::from(src),
))
})
.collect();
session2.set_workspace_files(project_only);
let gen_before = session2.index_generation();
let t5 = Instant::now();
let refs2 = session2
.indexed_references_to(
&target,
&project_paths,
false,
mir_analyzer::ReferenceIncludes::Plain,
&|| false,
)
.expect("not cancelled");
eprintln!(
"cold references, vendor-unregistered (php-lsp shape): {:.3}s, {} references, {} revision bumps",
t5.elapsed().as_secs_f64(),
refs2.len(),
session2.index_generation() - gen_before
);
let psr4c = Arc::new(
mir_analyzer::composer::Psr4Map::from_composer(fixture_root)
.expect("failed to load composer.json"),
);
let mut session3 = AnalysisSession::new(PhpVersion::LATEST).with_psr4(psr4c);
session3.ensure_all_stubs();
let workspace3: Vec<(Arc<str>, Arc<str>)> = project_files
.iter()
.chain(vendor_files.iter())
.filter_map(|p| {
let src = std::fs::read_to_string(p).ok()?;
Some((
Arc::<str>::from(p.to_string_lossy().as_ref()),
Arc::<str>::from(src),
))
})
.collect();
let all_paths: Vec<Arc<str>> = workspace3.iter().map(|(p, _)| p.clone()).collect();
session3.set_workspace_files(workspace3);
session3.rebuild_workspace_symbol_index();
let t6 = Instant::now();
let subs1 =
session3.indexed_subtype_classes("Illuminate\\Support\\ServiceProvider", &all_paths, false);
let scans1 = session3.class_mention_stats().scans_recorded;
eprintln!(
"cold indexed_subtype_classes(ServiceProvider), unanalyzed workspace: {:.3}s, {} subtypes, {} mention scans recorded",
t6.elapsed().as_secs_f64(),
subs1.len(),
scans1
);
let t7 = Instant::now();
let subs2 = session3.indexed_subtype_classes(
"Illuminate\\Contracts\\Support\\Arrayable",
&all_paths,
false,
);
let scans2 = session3.class_mention_stats().scans_recorded;
eprintln!(
"cold indexed_subtype_classes(Arrayable), same session: {:.3}s, {} subtypes, {} new mention scans",
t7.elapsed().as_secs_f64(),
subs2.len(),
scans2 - scans1
);
let m_target = Name::method("Illuminate\\Support\\Str", "studly");
let t8 = Instant::now();
let mrefs = session3
.indexed_references_to(
&m_target,
&all_paths,
false,
mir_analyzer::ReferenceIncludes::Plain,
&|| false,
)
.expect("not cancelled");
let scans3 = session3.class_mention_stats().scans_recorded;
eprintln!(
"cold references(Str::studly), novel member needle: {:.3}s, {} references, {} new mention scans",
t8.elapsed().as_secs_f64(),
mrefs.len(),
scans3 - scans2
);
let edited = all_paths[0].clone();
let edited_text: Arc<str> = {
let orig = std::fs::read_to_string(edited.as_ref()).expect("read edited file");
Arc::from(format!("{orig}\n// bench edit\n").as_str())
};
session3.set_file_text(edited.clone(), edited_text);
let t9 = Instant::now();
let mrefs2 = session3
.indexed_references_to(
&m_target,
&all_paths,
false,
mir_analyzer::ReferenceIncludes::Plain,
&|| false,
)
.expect("not cancelled");
let scans4 = session3.class_mention_stats().scans_recorded;
eprintln!(
"references(Str::studly) after one file edit: {:.3}s, {} references, {} new mention scans",
t9.elapsed().as_secs_f64(),
mrefs2.len(),
scans4 - scans3
);
let c_target = Name::method("Illuminate\\Support\\Str", "__construct");
let t10 = Instant::now();
let crefs = session3
.indexed_references_to(
&c_target,
&project_paths,
false,
mir_analyzer::ReferenceIncludes::Plain,
&|| false,
)
.expect("not cancelled");
let scans5 = session3.class_mention_stats().scans_recorded;
eprintln!(
"cold references(Str::__construct), raw needles: {:.3}s, {} references, {} new mention scans",
t10.elapsed().as_secs_f64(),
crefs.len(),
scans5 - scans4
);
let t11 = Instant::now();
let crefs2 = session3
.indexed_references_to(
&c_target,
&project_paths[..project_paths.len() - 1],
false,
mir_analyzer::ReferenceIncludes::Plain,
&|| false,
)
.expect("not cancelled");
let scans6 = session3.class_mention_stats().scans_recorded;
eprintln!(
"references(Str::__construct) repeat (memo missed): {:.3}s, {} references, {} new mention scans",
t11.elapsed().as_secs_f64(),
crefs2.len(),
scans6 - scans5
);
let stats = session3.class_mention_stats();
eprintln!(
"mention index footprint: {} universe names | {} files covered | {} mentions (~{:.1} MB entries) | scanner {:.1} MB | {} scans recorded",
stats.universe_names,
stats.files_covered,
stats.total_mentions,
(stats.total_mentions * 8 + stats.files_covered * 64) as f64 / 1e6,
stats.scanner_bytes as f64 / 1e6,
stats.scans_recorded
);
}