Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unstable feature usage metrics #130236

Merged
merged 2 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3684,6 +3684,8 @@ version = "0.0.0"
dependencies = [
"rustc_data_structures",
"rustc_span",
"serde",
"serde_json",
]

[[package]]
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_driver_impl/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
24 changes: 23 additions & 1 deletion compiler/rustc_driver_impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,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,
Expand Down Expand Up @@ -102,7 +103,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" }
Expand Down Expand Up @@ -430,6 +431,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();
}
Expand Down Expand Up @@ -474,6 +479,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));
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_driver_impl/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::error::Error;

use rustc_macros::{Diagnostic, Subdiagnostic};

#[derive(Diagnostic)]
Expand Down Expand Up @@ -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>,
}
2 changes: 2 additions & 0 deletions compiler/rustc_feature/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
50 changes: 50 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -649,6 +651,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.
///
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1068,7 +1068,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
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1889,7 +1889,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)"),
Expand Down
9 changes: 9 additions & 0 deletions tests/run-make/unstable-feature-usage-metrics/lib.rs
Original file line number Diff line number Diff line change
@@ -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");
}
87 changes: 87 additions & 0 deletions tests/run-make/unstable-feature-usage-metrics/rmake.rs
Original file line number Diff line number Diff line change
@@ -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");

assert_eq!(
0,
metrics.len(),
yaahc marked this conversation as resolved.
Show resolved Hide resolved
"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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we use dyn Error/IOError we don't display the file path that we couldn't open. That's not ideal but ok for now. Let's revisit in the future.

)
.assert_stdout_not_contains("internal compiler error");
});
}
Loading