Add json output to -Zdump-mono-stats

This allows analyzing the output programatically; for example, finding
the item with the highest `total_estimate`.

I also took the liberty of adding `untracked` tests to `rustc_session` and documentation to the unstable book for `dump-mono-items`.
This commit is contained in:
Joshua Nelson 2022-12-29 21:08:09 +00:00
parent ce85c98575
commit eb53eea609
11 changed files with 108 additions and 23 deletions

View file

@ -4226,6 +4226,8 @@ dependencies = [
"rustc_session",
"rustc_span",
"rustc_target",
"serde",
"serde_json",
"smallvec",
"tracing",
]

View file

@ -3,17 +3,16 @@ use crate::interface::parse_cfgspecs;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::{emitter::HumanReadableErrorType, registry, ColorConfig};
use rustc_session::config::InstrumentCoverage;
use rustc_session::config::Strip;
use rustc_session::config::rustc_optgroups;
use rustc_session::config::{build_configuration, build_session_options, to_crate_config};
use rustc_session::config::{
rustc_optgroups, ErrorOutputType, ExternLocation, LocationDetail, Options, Passes,
};
use rustc_session::config::{
BranchProtection, Externs, OomStrategy, OutputType, OutputTypes, PAuthKey, PacRet,
ProcMacroExecutionStrategy, SymbolManglingVersion, WasiExecModel,
};
use rustc_session::config::{CFGuard, ExternEntry, LinkerPluginLto, LtoCli, SwitchWithOptPath};
use rustc_session::config::{DumpMonoStatsFormat, MirSpanview};
use rustc_session::config::{ErrorOutputType, ExternLocation, LocationDetail, Options, Strip};
use rustc_session::config::{InstrumentCoverage, Passes};
use rustc_session::lint::Level;
use rustc_session::search_paths::SearchPath;
use rustc_session::utils::{CanonicalizedPath, NativeLib, NativeLibKind};
@ -647,6 +646,9 @@ fn test_unstable_options_tracking_hash() {
untracked!(dump_mir_dir, String::from("abc"));
untracked!(dump_mir_exclude_pass_number, true);
untracked!(dump_mir_graphviz, true);
untracked!(dump_mir_spanview, Some(MirSpanview::Statement));
untracked!(dump_mono_stats, SwitchWithOptPath::Enabled(Some("mono-items-dir/".into())));
untracked!(dump_mono_stats_format, DumpMonoStatsFormat::Json);
untracked!(dylib_lto, true);
untracked!(emit_stack_sizes, true);
untracked!(future_incompat_test, true);

View file

@ -6,6 +6,8 @@ edition = "2021"
[lib]
[dependencies]
serde = "1"
serde_json = "1"
smallvec = { version = "1.8.1", features = [ "union", "may_dangle" ] }
tracing = "0.1"
rustc_data_structures = { path = "../rustc_data_structures" }

View file

@ -109,7 +109,7 @@ use rustc_middle::mir::mono::{CodegenUnit, Linkage};
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::TyCtxt;
use rustc_session::config::SwitchWithOptPath;
use rustc_session::config::{DumpMonoStatsFormat, SwitchWithOptPath};
use rustc_span::symbol::Symbol;
use crate::collector::InliningMap;
@ -492,9 +492,11 @@ fn dump_mono_items_stats<'tcx>(
Path::new(".")
};
let filename = format!("{}.mono_items.md", crate_name.unwrap_or("unknown-crate"));
let format = tcx.sess.opts.unstable_opts.dump_mono_stats_format;
let ext = format.extension();
let filename = format!("{}.mono_items.{ext}", crate_name.unwrap_or("unknown-crate"));
let output_path = output_directory.join(&filename);
let file = File::create(output_path)?;
let file = File::create(&output_path)?;
let mut file = BufWriter::new(file);
// Gather instantiated mono items grouped by def_id
@ -508,30 +510,44 @@ fn dump_mono_items_stats<'tcx>(
}
}
#[derive(serde::Serialize)]
struct MonoItem {
name: String,
instantiation_count: usize,
size_estimate: usize,
total_estimate: usize,
}
// Output stats sorted by total instantiated size, from heaviest to lightest
let mut stats: Vec<_> = items_per_def_id
.into_iter()
.map(|(def_id, items)| {
let name = with_no_trimmed_paths!(tcx.def_path_str(def_id));
let instantiation_count = items.len();
let size_estimate = items[0].size_estimate(tcx);
let total_estimate = instantiation_count * size_estimate;
(def_id, instantiation_count, size_estimate, total_estimate)
MonoItem { name, instantiation_count, size_estimate, total_estimate }
})
.collect();
stats.sort_unstable_by_key(|(_, _, _, total_estimate)| cmp::Reverse(*total_estimate));
stats.sort_unstable_by_key(|item| cmp::Reverse(item.total_estimate));
if !stats.is_empty() {
writeln!(
file,
"| Item | Instantiation count | Estimated Cost Per Instantiation | Total Estimated Cost |"
)?;
writeln!(file, "| --- | ---: | ---: | ---: |")?;
for (def_id, instantiation_count, size_estimate, total_estimate) in stats {
let item = with_no_trimmed_paths!(tcx.def_path_str(def_id));
writeln!(
file,
"| {item} | {instantiation_count} | {size_estimate} | {total_estimate} |"
)?;
match format {
DumpMonoStatsFormat::Json => serde_json::to_writer(file, &stats)?,
DumpMonoStatsFormat::Markdown => {
writeln!(
file,
"| Item | Instantiation count | Estimated Cost Per Instantiation | Total Estimated Cost |"
)?;
writeln!(file, "| --- | ---: | ---: | ---: |")?;
for MonoItem { name, instantiation_count, size_estimate, total_estimate } in stats {
writeln!(
file,
"| `{name}` | {instantiation_count} | {size_estimate} | {total_estimate} |"
)?;
}
}
}
}

View file

@ -2975,3 +2975,21 @@ pub enum ProcMacroExecutionStrategy {
/// Run the proc-macro code on a different thread.
CrossThread,
}
/// Which format to use for `-Z dump-mono-stats`
#[derive(Clone, Copy, PartialEq, Hash, Debug)]
pub enum DumpMonoStatsFormat {
/// Pretty-print a markdown table
Markdown,
/// Emit structured JSON
Json,
}
impl DumpMonoStatsFormat {
pub fn extension(self) -> &'static str {
match self {
Self::Markdown => "md",
Self::Json => "json",
}
}
}

View file

@ -377,6 +377,7 @@ mod desc {
pub const parse_linker_flavor: &str = ::rustc_target::spec::LinkerFlavorCli::one_of();
pub const parse_optimization_fuel: &str = "crate=integer";
pub const parse_mir_spanview: &str = "`statement` (default), `terminator`, or `block`";
pub const parse_dump_mono_stats: &str = "`markdown` (default) or `json`";
pub const parse_instrument_coverage: &str =
"`all` (default), `except-unused-generics`, `except-unused-functions`, or `off`";
pub const parse_unpretty: &str = "`string` or `string=string`";
@ -820,6 +821,21 @@ mod parse {
true
}
pub(crate) fn parse_dump_mono_stats(slot: &mut DumpMonoStatsFormat, v: Option<&str>) -> bool {
match v {
None => true,
Some("json") => {
*slot = DumpMonoStatsFormat::Json;
true
}
Some("markdown") => {
*slot = DumpMonoStatsFormat::Markdown;
true
}
Some(_) => false,
}
}
pub(crate) fn parse_instrument_coverage(
slot: &mut Option<InstrumentCoverage>,
v: Option<&str>,
@ -1295,7 +1311,9 @@ options! {
an additional `.html` file showing the computed coverage spans."),
dump_mono_stats: SwitchWithOptPath = (SwitchWithOptPath::Disabled,
parse_switch_with_opt_path, [UNTRACKED],
"output statistics about monomorphization collection (format: markdown)"),
"output statistics about monomorphization collection"),
dump_mono_stats_format: DumpMonoStatsFormat = (DumpMonoStatsFormat::Markdown, parse_dump_mono_stats, [UNTRACKED],
"the format to use for -Z dump-mono-stats (`markdown` (default) or `json`)"),
dwarf_version: Option<u32> = (None, parse_opt_number, [TRACKED],
"version of DWARF debug information to emit (default: 2 or 4, depending on platform)"),
dylib_lto: bool = (false, parse_bool, [UNTRACKED],

View file

@ -0,0 +1,6 @@
# `dump-mono-stats-format`
--------------------
The `-Z dump-mono-stats-format` compiler flag controls what file format to use for `-Z dump-mono-stats`.
The default is markdown; currently JSON is also supported. JSON can be useful for programatically manipulating the results (e.g. to find the item that took the longest to compile).

View file

@ -0,0 +1,14 @@
# `dump-mono-stats`
--------------------
The `-Z dump-mono-stats` compiler flag generates a file with a list of the monomorphized items in the current crate.
It is useful for investigating compile times.
It accepts an optional directory where the file will be located. If no directory is specified, the file will be placed in the current directory.
See also `-Z dump-mono-stats-format` and `-Z print-mono-items`. Unlike `print-mono-items`,
`dump-mono-stats` aggregates monomorphized items by definition and includes a size estimate of how
large the item is when codegened.
See <https://rustc-dev-guide.rust-lang.org/backend/monomorph.html> for an overview of monomorphized items.

View file

@ -0,0 +1,5 @@
include ../../run-make-fulldeps/tools.mk
all:
$(RUSTC) --crate-type lib foo.rs -Z dump-mono-stats=$(TMPDIR) -Zdump-mono-stats-format=json
cat $(TMPDIR)/foo.mono_items.json | $(CGREP) '"name":"bar"'

View file

@ -0,0 +1 @@
pub fn bar() {}

View file

@ -35,7 +35,8 @@
-Z dump-mir-exclude-pass-number=val -- exclude the pass number when dumping MIR (used in tests) (default: no)
-Z dump-mir-graphviz=val -- in addition to `.mir` files, create graphviz `.dot` files (and with `-Z instrument-coverage`, also create a `.dot` file for the MIR-derived coverage graph) (default: no)
-Z dump-mir-spanview=val -- in addition to `.mir` files, create `.html` files to view spans for all `statement`s (including terminators), only `terminator` spans, or computed `block` spans (one span encompassing a block's terminator and all statements). If `-Z instrument-coverage` is also enabled, create an additional `.html` file showing the computed coverage spans.
-Z dump-mono-stats=val -- output statistics about monomorphization collection (format: markdown)
-Z dump-mono-stats=val -- output statistics about monomorphization collection
-Z dump-mono-stats-format=val -- the format to use for -Z dump-mono-stats (`markdown` (default) or `json`)
-Z dwarf-version=val -- version of DWARF debug information to emit (default: 2 or 4, depending on platform)
-Z dylib-lto=val -- enables LTO for dylib crate type
-Z emit-stack-sizes=val -- emit a section containing stack size metadata (default: no)