From 28d9d1bb41ee2a3525599b83c6a5edfdcc9b051e Mon Sep 17 00:00:00 2001 From: Jane Losare-Lusby Date: Fri, 6 Sep 2024 13:05:01 -0700 Subject: [PATCH] unstable feature usage metrics --- Cargo.lock | 2 + compiler/rustc_driver_impl/src/lib.rs | 10 +++ compiler/rustc_feature/Cargo.toml | 2 + compiler/rustc_feature/src/unstable.rs | 55 ++++++++++++++++ compiler/rustc_session/src/options.rs | 2 +- .../unstable-feature-usage-metrics/lib.rs | 9 +++ .../unstable-feature-usage-metrics/rmake.rs | 63 +++++++++++++++++++ 7 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 tests/run-make/unstable-feature-usage-metrics/lib.rs create mode 100644 tests/run-make/unstable-feature-usage-metrics/rmake.rs diff --git a/Cargo.lock b/Cargo.lock index d67141996827b..8cf85ff66dd0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3681,6 +3681,8 @@ version = "0.0.0" dependencies = [ "rustc_data_structures", "rustc_span", + "serde", + "serde_json", ] [[package]] diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index cb2fa6e9d746b..cf002eb2e5da2 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -432,6 +432,16 @@ fn run_compiler( // Make sure name resolution and macro expansion is run. queries.global_ctxt()?.enter(|tcx| tcx.resolver_for_lowering()); + // TODO: DECIDE where to put metrics in dump + if let Some(metrics_dir) = &sess.opts.unstable_opts.metrics_dir { + compiler.enter(|queries| -> Result<(), ErrorGuaranteed> { + queries + .global_ctxt()? + .enter(|tcxt| tcxt.features().dump_feature_usage_metrics(metrics_dir)); + Ok(()) + })?; + } + if callbacks.after_expansion(compiler, queries) == Compilation::Stop { return early_exit(); } diff --git a/compiler/rustc_feature/Cargo.toml b/compiler/rustc_feature/Cargo.toml index 9df320e1279ed..77de7fabd4f92 100644 --- a/compiler/rustc_feature/Cargo.toml +++ b/compiler/rustc_feature/Cargo.toml @@ -7,4 +7,6 @@ edition = "2021" # tidy-alphabetical-start rustc_data_structures = { path = "../rustc_data_structures" } rustc_span = { path = "../rustc_span" } +serde = { version = "1.0.125", features = [ "derive" ] } +serde_json = "1.0.59" # tidy-alphabetical-end diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index fba65883550b5..cf50927a30949 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -1,5 +1,7 @@ //! List of the unstable feature gates. +use std::path::Path; + use rustc_data_structures::fx::FxHashSet; use rustc_span::symbol::{sym, Symbol}; use rustc_span::Span; @@ -649,6 +651,59 @@ declare_features! ( // ------------------------------------------------------------------------- ); +impl Features { + pub fn dump_feature_usage_metrics(&self, metrics_dir: &Path) { + #[derive(serde::Serialize)] + struct LibFeature { + symbol: String, + } + + #[derive(serde::Serialize)] + struct LangFeature { + symbol: String, + since: Option, + } + + #[derive(serde::Serialize)] + struct FeatureUsage { + lib_features: Vec, + lang_features: Vec, + } + + // TODO (DECIDE): How fine grained do we want to track feature usage? + // Jane Preference: i want to track usage for code that gets used, not code + // that is in development, but I don't know how we'd hook into this for code + // that doesn't participate in the crates.io ecosystem, those crates won't even + // necessarily have releases or versioning norms that match other crates, so we + // may have to just track per compilation and aggressively collapse metrics to + // avoid unnecessary disk usage. + // TODO avoid filename collisions between runs + let feature_metrics_file = metrics_dir.join("unstable_feature_usage.json"); + println!("{}", feature_metrics_file.display()); + let feature_metrics_file = std::fs::File::create(feature_metrics_file).unwrap(); + let feature_metrics_file = std::io::BufWriter::new(feature_metrics_file); + + let lib_features = self + .declared_lib_features + .iter() + .map(|(symbol, _)| LibFeature { symbol: symbol.to_string() }) + .collect(); + + let lang_features = self + .declared_lang_features + .iter() + .map(|(symbol, _, since)| LangFeature { + symbol: symbol.to_string(), + since: since.map(|since| since.to_string()), + }) + .collect(); + + let feature_usage = FeatureUsage { lib_features, lang_features }; + + let _ = serde_json::to_writer(feature_metrics_file, &feature_usage); + } +} + /// Some features are not allowed to be used together at the same time, if /// the two are present, produce an error. /// diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index a57dc80b3168d..995fc45ab9ad8 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1845,7 +1845,7 @@ options! { meta_stats: bool = (false, parse_bool, [UNTRACKED], "gather metadata statistics (default: no)"), metrics_dir: Option = (None, parse_opt_pathbuf, [UNTRACKED], - "stores metrics about the errors being emitted by rustc to disk"), + "the directory metrics emitted by rustc are dumped into (implicitly enables default set of metrics)"), mir_emit_retag: bool = (false, parse_bool, [TRACKED], "emit Retagging MIR statements, interpreted e.g., by miri; implies -Zmir-opt-level=0 \ (default: no)"), diff --git a/tests/run-make/unstable-feature-usage-metrics/lib.rs b/tests/run-make/unstable-feature-usage-metrics/lib.rs new file mode 100644 index 0000000000000..2202d722c497e --- /dev/null +++ b/tests/run-make/unstable-feature-usage-metrics/lib.rs @@ -0,0 +1,9 @@ +#![feature(ascii_char)] // random lib feature +#![feature(box_patterns)] // random lang feature + +// picked arbitrary unstable features, just need a random lib and lang feature, ideally ones that +// won't be stabilized any time soon so we don't have to update this test + +fn main() { + println!("foobar"); +} diff --git a/tests/run-make/unstable-feature-usage-metrics/rmake.rs b/tests/run-make/unstable-feature-usage-metrics/rmake.rs new file mode 100644 index 0000000000000..5e1ed3489cddd --- /dev/null +++ b/tests/run-make/unstable-feature-usage-metrics/rmake.rs @@ -0,0 +1,63 @@ +//! This test checks if unstable feature usage metric dump files `unstable-feature-usage*.json` work +//! as expected. +//! +//! - Basic sanity checks on a default ICE dump. +//! +//! See . +//! +//! # Test history +//! +//! - forked from dump-ice-to-disk test, which has flakeyness issues on i686-mingw, I'm assuming +//! those will be present in this test as well on the same platform + +//@ ignore-windows +//FIXME(#128911): still flakey on i686-mingw. + +use std::path::{Path, PathBuf}; + +use run_make_support::{ + cwd, has_extension, has_prefix, rfs, run_in_tmpdir, rustc, serde_json, shallow_find_files, +}; + +fn find_feature_usage_metrics>(dir: P) -> Vec { + shallow_find_files(dir, |path| { + has_prefix(path, "unstable_feature_usage") && has_extension(path, "json") + }) +} + +fn main() { + test_metrics_dump(); +} + +#[track_caller] +fn test_metrics_dump() { + run_in_tmpdir(|| { + let metrics_dir = cwd().join("metrics"); + rustc() + .input("lib.rs") + .env("RUST_BACKTRACE", "short") + .arg(format!("-Zmetrics-dir={}", metrics_dir.display())) + .run(); + let mut metrics = find_feature_usage_metrics(&metrics_dir); + let json_path = + metrics.pop().expect("there should be exactly metrics file in the output directory"); + + assert_eq!( + 0, + metrics.len(), + "there should be exactly one metrics file in the output directory" + ); + + let message = rfs::read_to_string(json_path); + let parsed: serde_json::Value = + serde_json::from_str(&message).expect("metrics should be dumped as json"); + let expected = serde_json::json!( + { + "lib_features":[{"symbol":"ascii_char"}], + "lang_features":[{"symbol":"box_patterns","since":null}] + } + ); + + assert_eq!(expected, parsed); + }); +}