Skip to content

Commit 3addcf4

Browse files
committed
unstable feature usage metrics
1 parent 6503543 commit 3addcf4

File tree

7 files changed

+145
-1
lines changed

7 files changed

+145
-1
lines changed

Cargo.lock

+2
Original file line numberDiff line numberDiff line change
@@ -3684,6 +3684,8 @@ version = "0.0.0"
36843684
dependencies = [
36853685
"rustc_data_structures",
36863686
"rustc_span",
3687+
"serde",
3688+
"serde_json",
36873689
]
36883690

36893691
[[package]]

compiler/rustc_driver_impl/src/lib.rs

+11
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ use rustc_session::lint::{Lint, LintId};
6060
use rustc_session::output::collect_crate_types;
6161
use rustc_session::{EarlyDiagCtxt, Session, config, filesearch};
6262
use rustc_span::FileName;
63+
use rustc_span::def_id::LOCAL_CRATE;
6364
use rustc_span::source_map::FileLoader;
6465
use rustc_target::json::ToJson;
6566
use rustc_target::spec::{Target, TargetTuple};
@@ -430,6 +431,16 @@ fn run_compiler(
430431
// Make sure name resolution and macro expansion is run.
431432
queries.global_ctxt()?.enter(|tcx| tcx.resolver_for_lowering());
432433

434+
if let Some(metrics_dir) = &sess.opts.unstable_opts.metrics_dir {
435+
compiler.enter(|queries| -> Result<(), ErrorGuaranteed> {
436+
queries.global_ctxt()?.enter(|tcxt| {
437+
let crate_id = tcxt.stable_crate_id(LOCAL_CRATE);
438+
tcxt.features().dump_feature_usage_metrics(metrics_dir, crate_id)
439+
});
440+
Ok(())
441+
})?;
442+
}
443+
433444
if callbacks.after_expansion(compiler, queries) == Compilation::Stop {
434445
return early_exit();
435446
}

compiler/rustc_feature/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ edition = "2021"
77
# tidy-alphabetical-start
88
rustc_data_structures = { path = "../rustc_data_structures" }
99
rustc_span = { path = "../rustc_span" }
10+
serde = { version = "1.0.125", features = [ "derive" ] }
11+
serde_json = "1.0.59"
1012
# tidy-alphabetical-end

compiler/rustc_feature/src/unstable.rs

+57
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
//! List of the unstable feature gates.
22
3+
use std::path::Path;
4+
35
use rustc_data_structures::fx::FxHashSet;
46
use rustc_span::Span;
7+
use rustc_span::def_id::StableCrateId;
58
use rustc_span::symbol::{Symbol, sym};
69

710
use super::{Feature, to_nonzero};
@@ -649,6 +652,60 @@ declare_features! (
649652
// -------------------------------------------------------------------------
650653
);
651654

655+
impl Features {
656+
pub fn dump_feature_usage_metrics(&self, metrics_dir: &Path, _crate_id: StableCrateId) {
657+
#[derive(serde::Serialize)]
658+
struct LibFeature {
659+
symbol: String,
660+
}
661+
662+
#[derive(serde::Serialize)]
663+
struct LangFeature {
664+
symbol: String,
665+
since: Option<String>,
666+
}
667+
668+
#[derive(serde::Serialize)]
669+
struct FeatureUsage {
670+
lib_features: Vec<LibFeature>,
671+
lang_features: Vec<LangFeature>,
672+
}
673+
674+
// TODO (DECIDE): How fine grained do we want to track feature usage?
675+
// Jane Preference: i want to track usage for code that gets used, not code
676+
// that is in development, but I don't know how we'd hook into this for code
677+
// that doesn't participate in the crates.io ecosystem, those crates won't even
678+
// necessarily have releases or versioning norms that match other crates, so we
679+
// may have to just track per compilation and aggressively collapse metrics to
680+
// avoid unnecessary disk usage.
681+
// TODO avoid filename collisions between runs
682+
// let path = format!("unstable_feature_usage-{crate_id:?}.json");
683+
let feature_metrics_file = metrics_dir.join("unstable_feature_usage.json");
684+
println!("{}", feature_metrics_file.display());
685+
let feature_metrics_file = std::fs::File::create(feature_metrics_file).unwrap();
686+
let feature_metrics_file = std::io::BufWriter::new(feature_metrics_file);
687+
688+
let lib_features = self
689+
.enabled_lib_features
690+
.iter()
691+
.map(|EnabledLibFeature { gate_name, .. }| LibFeature { symbol: gate_name.to_string() })
692+
.collect();
693+
694+
let lang_features = self
695+
.enabled_lang_features
696+
.iter()
697+
.map(|EnabledLangFeature { gate_name, stable_since, .. }| LangFeature {
698+
symbol: gate_name.to_string(),
699+
since: stable_since.map(|since| since.to_string()),
700+
})
701+
.collect();
702+
703+
let feature_usage = FeatureUsage { lib_features, lang_features };
704+
705+
let _ = serde_json::to_writer(feature_metrics_file, &feature_usage);
706+
}
707+
}
708+
652709
/// Some features are not allowed to be used together at the same time, if
653710
/// the two are present, produce an error.
654711
///

compiler/rustc_session/src/options.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1889,7 +1889,7 @@ options! {
18891889
meta_stats: bool = (false, parse_bool, [UNTRACKED],
18901890
"gather metadata statistics (default: no)"),
18911891
metrics_dir: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
1892-
"stores metrics about the errors being emitted by rustc to disk"),
1892+
"the directory metrics emitted by rustc are dumped into (implicitly enables default set of metrics)"),
18931893
mir_emit_retag: bool = (false, parse_bool, [TRACKED],
18941894
"emit Retagging MIR statements, interpreted e.g., by miri; implies -Zmir-opt-level=0 \
18951895
(default: no)"),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#![feature(ascii_char)] // random lib feature
2+
#![feature(box_patterns)] // random lang feature
3+
4+
// picked arbitrary unstable features, just need a random lib and lang feature, ideally ones that
5+
// won't be stabilized any time soon so we don't have to update this test
6+
7+
fn main() {
8+
println!("foobar");
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//! This test checks if unstable feature usage metric dump files `unstable-feature-usage*.json` work
2+
//! as expected.
3+
//!
4+
//! - Basic sanity checks on a default ICE dump.
5+
//!
6+
//! See <https://github.com/rust-lang/rust/issues/129485>.
7+
//!
8+
//! # Test history
9+
//!
10+
//! - forked from dump-ice-to-disk test, which has flakeyness issues on i686-mingw, I'm assuming
11+
//! those will be present in this test as well on the same platform
12+
13+
//@ ignore-windows
14+
//FIXME(#128911): still flakey on i686-mingw.
15+
16+
use std::path::{Path, PathBuf};
17+
18+
use run_make_support::{
19+
cwd, has_extension, has_prefix, rfs, run_in_tmpdir, rustc, serde_json, shallow_find_files,
20+
};
21+
22+
fn find_feature_usage_metrics<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> {
23+
shallow_find_files(dir, |path| {
24+
has_prefix(path, "unstable_feature_usage") && has_extension(path, "json")
25+
})
26+
}
27+
28+
fn main() {
29+
test_metrics_dump();
30+
}
31+
32+
#[track_caller]
33+
fn test_metrics_dump() {
34+
run_in_tmpdir(|| {
35+
let metrics_dir = cwd().join("metrics");
36+
rustc()
37+
.input("lib.rs")
38+
.env("RUST_BACKTRACE", "short")
39+
.arg(format!("-Zmetrics-dir={}", metrics_dir.display()))
40+
.run();
41+
let mut metrics = find_feature_usage_metrics(&metrics_dir);
42+
let json_path =
43+
metrics.pop().expect("there should be exactly metrics file in the output directory");
44+
45+
assert_eq!(
46+
0,
47+
metrics.len(),
48+
"there should be exactly one metrics file in the output directory"
49+
);
50+
51+
let message = rfs::read_to_string(json_path);
52+
let parsed: serde_json::Value =
53+
serde_json::from_str(&message).expect("metrics should be dumped as json");
54+
let expected = serde_json::json!(
55+
{
56+
"lib_features":[{"symbol":"ascii_char"}],
57+
"lang_features":[{"symbol":"box_patterns","since":null}]
58+
}
59+
);
60+
61+
assert_eq!(expected, parsed);
62+
});
63+
}

0 commit comments

Comments
 (0)