about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias KrΓΌger <matthias.krueger@famsik.de>2024-11-21 11:58:36 +0100
committerGitHub <noreply@github.com>2024-11-21 11:58:36 +0100
commitfe5403f517617e1ecdf1cd3cd33bf205727f92c9 (patch)
treeb7c9380d41a326807565657bda20807f167f0bf0
parent0b1bf71a71c2a1d34c212285362530ec2c4e4775 (diff)
parent0a14f712d7a2cf42e2e5705b2f5a8f1b8e67c7c2 (diff)
downloadrust-fe5403f517617e1ecdf1cd3cd33bf205727f92c9.tar.gz
rust-fe5403f517617e1ecdf1cd3cd33bf205727f92c9.zip
Rollup merge of #130236 - yaahc:unstable-feature-usage, r=estebank
unstable feature usage metrics

example output

```
test-lib on ξ‚  master [?] is πŸ“¦ v0.1.0 via πŸ¦€ v1.80.1
❯ cat src/lib.rs
───────┬───────────────────────────────────────────────────────
       β”‚ File: src/lib.rs
───────┼───────────────────────────────────────────────────────
   1   β”‚ #![feature(unix_set_mark)]
   2   β”‚ pub fn add(left: u64, right: u64) -> u64 {
   3   β”‚     left + right
   4   β”‚ }
   5   β”‚
   6   β”‚ #[cfg(test)]
   7   β”‚ mod tests {
   8   β”‚     use super::*;
   9   β”‚
  10   β”‚     #[test]
  11   β”‚     fn it_works() {
  12   β”‚         let result = add(2, 2);
  13   β”‚         assert_eq!(result, 4);
  14   β”‚     }
  15   β”‚ }
───────┴───────────────────────────────────────────────────────

test-lib on ξ‚  master [?] is πŸ“¦ v0.1.0 via πŸ¦€ v1.80.1
❯ cargo +stage1 rustc -- -Zmetrics-dir=$PWD/metrics
   Compiling test-lib v0.1.0 (/home/yaahc/tmp/test-lib)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s

test-lib on ξ‚  master [?] is πŸ“¦ v0.1.0 via πŸ¦€ v1.80.1
❯ cat metrics/unstable_feature_usage.json
───────┬─────────────────────────────────────────────────────────────────────
       β”‚ File: metrics/unstable_feature_usage.json
───────┼─────────────────────────────────────────────────────────────────────
   1   β”‚ {"lib_features":[{"symbol":"unix_set_mark"}],"lang_features":[]}
   ```

   related to https://github.com/rust-lang/rust/issues/129485
-rw-r--r--Cargo.lock2
-rw-r--r--compiler/rustc_driver_impl/messages.ftl2
-rw-r--r--compiler/rustc_driver_impl/src/lib.rs24
-rw-r--r--compiler/rustc_driver_impl/src/session_diagnostics.rs8
-rw-r--r--compiler/rustc_feature/Cargo.toml2
-rw-r--r--compiler/rustc_feature/src/unstable.rs50
-rw-r--r--compiler/rustc_session/src/config.rs2
-rw-r--r--compiler/rustc_session/src/options.rs2
-rw-r--r--tests/run-make/unstable-feature-usage-metrics/lib.rs9
-rw-r--r--tests/run-make/unstable-feature-usage-metrics/rmake.rs87
10 files changed, 185 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c8d26559728..51e21f6dc82 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3685,6 +3685,8 @@ version = "0.0.0"
 dependencies = [
  "rustc_data_structures",
  "rustc_span",
+ "serde",
+ "serde_json",
 ]
 
 [[package]]
diff --git a/compiler/rustc_driver_impl/messages.ftl b/compiler/rustc_driver_impl/messages.ftl
index 31837e01764..05e11c4527f 100644
--- a/compiler/rustc_driver_impl/messages.ftl
+++ b/compiler/rustc_driver_impl/messages.ftl
@@ -23,3 +23,5 @@ driver_impl_rlink_rustc_version_mismatch = .rlink file was produced by rustc ver
 driver_impl_rlink_unable_to_read = failed to read rlink file: `{$err}`
 
 driver_impl_rlink_wrong_file_type = The input does not look like a .rlink file
+
+driver_impl_unstable_feature_usage = cannot dump feature usage metrics: {$error}
diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs
index d2333454f28..c270ce16726 100644
--- a/compiler/rustc_driver_impl/src/lib.rs
+++ b/compiler/rustc_driver_impl/src/lib.rs
@@ -51,6 +51,7 @@ use rustc_interface::{Linker, Queries, interface, passes};
 use rustc_lint::unerased_lint_store;
 use rustc_metadata::creader::MetadataLoader;
 use rustc_metadata::locator;
+use rustc_middle::ty::TyCtxt;
 use rustc_parse::{new_parser_from_file, new_parser_from_source_str, unwrap_or_emit_fatal};
 use rustc_session::config::{
     CG_OPTIONS, ErrorOutputType, Input, OutFileName, OutputType, UnstableOptions, Z_OPTIONS,
@@ -103,7 +104,7 @@ mod signal_handler {
 
 use crate::session_diagnostics::{
     RLinkEmptyVersionNumber, RLinkEncodingVersionMismatch, RLinkRustcVersionMismatch,
-    RLinkWrongFileType, RlinkCorruptFile, RlinkNotAFile, RlinkUnableToRead,
+    RLinkWrongFileType, RlinkCorruptFile, RlinkNotAFile, RlinkUnableToRead, UnstableFeatureUsage,
 };
 
 rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
@@ -431,6 +432,10 @@ fn run_compiler(
             // Make sure name resolution and macro expansion is run.
             queries.global_ctxt()?.enter(|tcx| tcx.resolver_for_lowering());
 
+            if let Some(metrics_dir) = &sess.opts.unstable_opts.metrics_dir {
+                queries.global_ctxt()?.enter(|tcxt| dump_feature_usage_metrics(tcxt, metrics_dir));
+            }
+
             if callbacks.after_expansion(compiler, queries) == Compilation::Stop {
                 return early_exit();
             }
@@ -475,6 +480,23 @@ fn run_compiler(
     })
 }
 
+fn dump_feature_usage_metrics(tcxt: TyCtxt<'_>, metrics_dir: &PathBuf) {
+    let output_filenames = tcxt.output_filenames(());
+    let mut metrics_file_name = std::ffi::OsString::from("unstable_feature_usage_metrics-");
+    let mut metrics_path = output_filenames.with_directory_and_extension(metrics_dir, "json");
+    let metrics_file_stem =
+        metrics_path.file_name().expect("there should be a valid default output filename");
+    metrics_file_name.push(metrics_file_stem);
+    metrics_path.pop();
+    metrics_path.push(metrics_file_name);
+    if let Err(error) = tcxt.features().dump_feature_usage_metrics(metrics_path) {
+        // FIXME(yaahc): once metrics can be enabled by default we will want "failure to emit
+        // default metrics" to only produce a warning when metrics are enabled by default and emit
+        // an error only when the user manually enables metrics
+        tcxt.dcx().emit_err(UnstableFeatureUsage { error });
+    }
+}
+
 // Extract output directory and file from matches.
 fn make_output(matches: &getopts::Matches) -> (Option<PathBuf>, Option<OutFileName>) {
     let odir = matches.opt_str("out-dir").map(|o| PathBuf::from(&o));
diff --git a/compiler/rustc_driver_impl/src/session_diagnostics.rs b/compiler/rustc_driver_impl/src/session_diagnostics.rs
index 449878f28c4..e06c56539d1 100644
--- a/compiler/rustc_driver_impl/src/session_diagnostics.rs
+++ b/compiler/rustc_driver_impl/src/session_diagnostics.rs
@@ -1,3 +1,5 @@
+use std::error::Error;
+
 use rustc_macros::{Diagnostic, Subdiagnostic};
 
 #[derive(Diagnostic)]
@@ -93,3 +95,9 @@ pub(crate) struct IceFlags {
 #[derive(Diagnostic)]
 #[diag(driver_impl_ice_exclude_cargo_defaults)]
 pub(crate) struct IceExcludeCargoDefaults;
+
+#[derive(Diagnostic)]
+#[diag(driver_impl_unstable_feature_usage)]
+pub(crate) struct UnstableFeatureUsage {
+    pub error: Box<dyn Error>,
+}
diff --git a/compiler/rustc_feature/Cargo.toml b/compiler/rustc_feature/Cargo.toml
index 9df320e1279..77de7fabd4f 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 2acebebb419..e3dc73c1401 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::PathBuf;
+
 use rustc_data_structures::fx::FxHashSet;
 use rustc_span::Span;
 use rustc_span::symbol::{Symbol, sym};
@@ -651,6 +653,54 @@ declare_features! (
     // -------------------------------------------------------------------------
 );
 
+impl Features {
+    pub fn dump_feature_usage_metrics(
+        &self,
+        metrics_path: PathBuf,
+    ) -> Result<(), Box<dyn std::error::Error>> {
+        #[derive(serde::Serialize)]
+        struct LibFeature {
+            symbol: String,
+        }
+
+        #[derive(serde::Serialize)]
+        struct LangFeature {
+            symbol: String,
+            since: Option<String>,
+        }
+
+        #[derive(serde::Serialize)]
+        struct FeatureUsage {
+            lib_features: Vec<LibFeature>,
+            lang_features: Vec<LangFeature>,
+        }
+
+        let metrics_file = std::fs::File::create(metrics_path)?;
+        let metrics_file = std::io::BufWriter::new(metrics_file);
+
+        let lib_features = self
+            .enabled_lib_features
+            .iter()
+            .map(|EnabledLibFeature { gate_name, .. }| LibFeature { symbol: gate_name.to_string() })
+            .collect();
+
+        let lang_features = self
+            .enabled_lang_features
+            .iter()
+            .map(|EnabledLangFeature { gate_name, stable_since, .. }| LangFeature {
+                symbol: gate_name.to_string(),
+                since: stable_since.map(|since| since.to_string()),
+            })
+            .collect();
+
+        let feature_usage = FeatureUsage { lib_features, lang_features };
+
+        serde_json::to_writer(metrics_file, &feature_usage)?;
+
+        Ok(())
+    }
+}
+
 /// 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/config.rs b/compiler/rustc_session/src/config.rs
index f6e6fd33c48..d60c56fee75 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -1069,7 +1069,7 @@ impl OutputFilenames {
         self.with_directory_and_extension(&self.out_directory, extension)
     }
 
-    fn with_directory_and_extension(&self, directory: &PathBuf, extension: &str) -> PathBuf {
+    pub fn with_directory_and_extension(&self, directory: &PathBuf, extension: &str) -> PathBuf {
         let mut path = directory.join(&self.filestem);
         path.set_extension(extension);
         path
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index d94b503de18..cbfe5d22f1d 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -1887,7 +1887,7 @@ options! {
     meta_stats: bool = (false, parse_bool, [UNTRACKED],
         "gather metadata statistics (default: no)"),
     metrics_dir: Option<PathBuf> = (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 00000000000..2202d722c49
--- /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 00000000000..1397548a6fc
--- /dev/null
+++ b/tests/run-make/unstable-feature-usage-metrics/rmake.rs
@@ -0,0 +1,87 @@
+//! 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 <https://github.com/rust-lang/rust/issues/129485>.
+//!
+//! # 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::rfs::create_dir_all;
+use run_make_support::{
+    cwd, filename_contains, has_extension, rfs, run_in_tmpdir, rustc, serde_json,
+    shallow_find_files,
+};
+
+fn find_feature_usage_metrics<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> {
+    shallow_find_files(dir, |path| {
+        if filename_contains(path, "unstable_feature_usage") && has_extension(path, "json") {
+            true
+        } else {
+            dbg!(path);
+            false
+        }
+    })
+}
+
+fn main() {
+    test_metrics_dump();
+    test_metrics_errors();
+}
+
+#[track_caller]
+fn test_metrics_dump() {
+    run_in_tmpdir(|| {
+        let metrics_dir = cwd().join("metrics");
+        create_dir_all(&metrics_dir);
+        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 one metrics file in the output directory");
+
+        // After the `pop` above, there should be no files left.
+        assert!(
+            metrics.is_empty(),
+            "there should be no more than 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);
+    });
+}
+
+#[track_caller]
+fn test_metrics_errors() {
+    run_in_tmpdir(|| {
+        rustc()
+            .input("lib.rs")
+            .env("RUST_BACKTRACE", "short")
+            .arg("-Zmetrics-dir=invaliddirectorythatdefinitelydoesntexist")
+            .run_fail()
+            .assert_stderr_contains(
+                "error: cannot dump feature usage metrics: No such file or directory",
+            )
+            .assert_stdout_not_contains("internal compiler error");
+    });
+}