Skip to content

Commit c46423d

Browse files
authored
feat(report): cargo report timings HTML replay (#16377)
### What does this PR try to resolve? This implements `cargo report timings` HTML replay feature. The help text looks like: ``` Reports the build timings of previous builds (unstable) Usage: cargo report timings [OPTIONS] Options: --open Opens the timing report in a browser ... ``` Part of <#15844> ### How to test and review this PR? Open questions: * [ ] The replayed HTML report will be written to * If in a Cargo workspace, write to `<artifact-dir>/cargo-timings/` * Otherwise, write to a temp directory * [ ] The log discovery logic * Currently look at the newest log file for the workspace. * If not in a workspace, pick the newest log file in the log directory. * [ ] Snapshots HTML report and log files are added. * They are huge in size. Should we not do this? * Benefit: HTML snapshots help us know better when it is broken ### Things left (but should do it follow-up PRs) * [ ] `cargo help` hasn't yet worked with with nested commands. * [ ] There are some more information required by HTML report, but these don't affect the major use of the report IMO. * [ ] fresh/dirty/total units * [ ] information of root units * [ ] enabled features for each unit * [ ] `--target` platform * [ ] CPU usage metrics * [x] number of available parallelism #16378
2 parents 331a495 + 44887e4 commit c46423d

File tree

20 files changed

+6509
-86
lines changed

20 files changed

+6509
-86
lines changed

.ignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@ src/etc/man
1212
!src/doc/src/commands/index.md
1313
!src/doc/src/commands/manifest-commands.md
1414
!src/doc/src/commands/package-commands.md
15-
!src/doc/src/commands/publishg-commands.md
15+
!src/doc/src/commands/publishing-commands.md
16+
17+
# Snapshots of HTML reports and log files are just too large
18+
tests/testsuite/**/*.jsonl
19+
tests/testsuite/**/*.html

crates/cargo-test-support/src/paths.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,41 @@ pub fn cargo_home() -> PathBuf {
119119
home().join(".cargo")
120120
}
121121

122+
/// Path to the current test's `$CARGO_LOG`
123+
///
124+
/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/home/.cargo/log`
125+
pub fn log_dir() -> PathBuf {
126+
cargo_home().join("log")
127+
}
128+
129+
/// Path to the current test's `$CARGO_LOG` file
130+
///
131+
/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/home/.cargo/log/<id>.jsonl`
132+
///
133+
/// This also asserts the number of log files is exactly the same as `idx + 1`.
134+
pub fn log_file(idx: usize) -> PathBuf {
135+
let log_dir = log_dir();
136+
137+
let entries = std::fs::read_dir(&log_dir).unwrap();
138+
let mut log_files: Vec<_> = entries
139+
.filter_map(Result::ok)
140+
.filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("jsonl"))
141+
.collect();
142+
143+
// Sort them to get chronological order
144+
log_files.sort_unstable_by(|a, b| a.file_name().to_str().cmp(&b.file_name().to_str()));
145+
146+
assert_eq!(
147+
idx + 1,
148+
log_files.len(),
149+
"unexpected number of log files: {}, expected {}",
150+
log_files.len(),
151+
idx + 1
152+
);
153+
154+
log_files[idx].path()
155+
}
156+
122157
/// Common path and file operations
123158
pub trait CargoPathExt {
124159
fn to_url(&self) -> url::Url;

src/bin/cargo/commands/report.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use crate::command_prelude::*;
2-
use cargo::core::compiler::future_incompat::{OnDiskReports, REPORT_PREAMBLE};
2+
3+
use cargo::CargoResult;
4+
use cargo::core::compiler::future_incompat::OnDiskReports;
5+
use cargo::core::compiler::future_incompat::REPORT_PREAMBLE;
36
use cargo::drop_println;
7+
use cargo::ops;
48

59
pub fn cli() -> Command {
610
subcommand("report")
@@ -23,11 +27,28 @@ pub fn cli() -> Command {
2327
)
2428
.arg_package("Package to display a report for"),
2529
)
30+
.subcommand(
31+
subcommand("timings")
32+
.about("Reports the build timings of previous builds (unstable)")
33+
.arg(flag("open", "Opens the timing report in a browser")),
34+
)
2635
}
2736

2837
pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
2938
match args.subcommand() {
3039
Some(("future-incompatibilities", args)) => report_future_incompatibilities(gctx, args),
40+
Some(("timings", args)) => {
41+
gctx.cli_unstable().fail_if_stable_command(
42+
gctx,
43+
"report timings",
44+
15844,
45+
"build-analysis",
46+
gctx.cli_unstable().build_analysis,
47+
)?;
48+
let opts = timings_opts(gctx, args)?;
49+
ops::report_timings(gctx, opts)?;
50+
Ok(())
51+
}
3152
Some((cmd, _)) => {
3253
unreachable!("unexpected command {}", cmd)
3354
}
@@ -49,3 +70,12 @@ fn report_future_incompatibilities(gctx: &GlobalContext, args: &ArgMatches) -> C
4970
drop(gctx.shell().print_ansi_stdout(report.as_bytes()));
5071
Ok(())
5172
}
73+
74+
fn timings_opts<'a>(
75+
gctx: &'a GlobalContext,
76+
args: &ArgMatches,
77+
) -> CargoResult<ops::ReportTimingsOptions<'a>> {
78+
let open_result = args.get_flag("open");
79+
80+
Ok(ops::ReportTimingsOptions { open_result, gctx })
81+
}

src/cargo/ops/cargo_doc.rs

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1+
use crate::core::Workspace;
12
use crate::core::compiler::{Compilation, CompileKind};
2-
use crate::core::{Shell, Workspace, shell::Verbosity};
3+
use crate::core::shell::Verbosity;
34
use crate::ops;
45
use crate::util;
56
use crate::util::CargoResult;
6-
use crate::util::context::{GlobalContext, PathAndArgs};
77

88
use anyhow::{Error, bail};
99
use cargo_util::ProcessBuilder;
1010

1111
use std::ffi::OsString;
12-
use std::path::Path;
1312
use std::path::PathBuf;
14-
use std::process::Command;
1513
use std::str::FromStr;
1614

1715
/// Format of rustdoc [`--output-format`][1].
@@ -75,14 +73,7 @@ pub fn doc(ws: &Workspace<'_>, options: &DocOptions) -> CargoResult<()> {
7573
let path = path_by_output_format(&compilation, &kind, &name, &options.output_format);
7674

7775
if path.exists() {
78-
let config_browser = {
79-
let cfg: Option<PathAndArgs> = ws.gctx().get("doc.browser")?;
80-
cfg.map(|path_args| (path_args.path.resolve_program(ws.gctx()), path_args.args))
81-
};
82-
let mut shell = ws.gctx().shell();
83-
let link = shell.err_file_hyperlink(&path);
84-
shell.status("Opening", format!("{link}{}{link:#}", path.display()))?;
85-
open_docs(&path, &mut shell, config_browser, ws.gctx())?;
76+
util::open::open(&path, ws.gctx())?;
8677
}
8778
} else if ws.gctx().shell().verbosity() == Verbosity::Verbose {
8879
for name in &compilation.root_crate_names {
@@ -216,33 +207,3 @@ fn path_by_output_format(
216207
.join("index.html")
217208
}
218209
}
219-
220-
fn open_docs(
221-
path: &Path,
222-
shell: &mut Shell,
223-
config_browser: Option<(PathBuf, Vec<String>)>,
224-
gctx: &GlobalContext,
225-
) -> CargoResult<()> {
226-
let browser =
227-
config_browser.or_else(|| Some((PathBuf::from(gctx.get_env_os("BROWSER")?), Vec::new())));
228-
229-
match browser {
230-
Some((browser, initial_args)) => {
231-
if let Err(e) = Command::new(&browser).args(initial_args).arg(path).status() {
232-
shell.warn(format!(
233-
"Couldn't open docs with {}: {}",
234-
browser.to_string_lossy(),
235-
e
236-
))?;
237-
}
238-
}
239-
None => {
240-
if let Err(e) = opener::open(&path) {
241-
let e = e.into();
242-
crate::display_warning_with_error("couldn't open docs", &e, shell);
243-
}
244-
}
245-
};
246-
247-
Ok(())
248-
}

src/cargo/ops/cargo_report/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod timings;

0 commit comments

Comments
 (0)