Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 874f57b

Browse files
fspoetteltguichaoua
andauthored
feat: make cargo time incremental by default (#53)
Co-authored-by: Tristan Guichaoua <33934311+tguichaoua@users.noreply.github.com>
1 parent 4c42321 commit 874f57b

File tree

15 files changed

+758
-301
lines changed

15 files changed

+758
-301
lines changed

‎.cargo/config.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ read = "run --quiet --release -- read"
66

77
solve = "run --quiet --release -- solve"
88
all = "run --quiet --release -- all"
9-
time = "run --quiet --release -- all --release --time"
9+
time = "run --quiet --release -- time"
1010

1111
[env]
1212
AOC_YEAR = "2023"

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ data/puzzles/*
2424

2525
# Dhat
2626
dhat-heap.json
27+
28+
# Benchmarks
29+
30+
data/timings.json

‎Cargo.lock‎

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ inherits = "release"
1515
debug = 1
1616

1717
[features]
18+
dhat-heap = ["dhat"]
1819
today = ["chrono"]
1920
test_lib = []
20-
dhat-heap = ["dhat"]
2121

2222
[dependencies]
2323
chrono = { version = "0.4.31", optional = true }
24-
pico-args = "0.5.0"
2524
dhat = { version = "0.3.2", optional = true }
25+
pico-args = "0.5.0"
26+
tinyjson = "2"

‎README.md‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,15 @@ cargo all
116116
# Total: 0.20ms
117117
```
118118

119-
This runs all solutions sequentially and prints output to the command-line. Same as for the `solve` command, the `--release` flag runs an optimized build.
119+
This runs all solutions sequentially and prints output to the command-line. Same as for the `solve` command, the `--release` flag runs an optimized build and the `--time` flag outputs benchmarks.
120120

121-
#### Update readme benchmarks
121+
###➡️ Update readme benchmarks
122122

123-
The template can output a table with solution times to your readme. In order to generate a benchmarking table, run `cargo time`. If everything goes well, the command will output "_Successfully updated README with benchmarks._" after the execution finishes and the readme will be updated.
123+
The template can write benchmark times to the README via the `cargo time`command.
124124

125-
Please note that these are not "scientific" benchmarks, understand them as a fun approximation. 😉 Timings, especially in the microseconds range, might change a bit between invocations.
125+
By default, this command checks for missing benchmarks, runs those solutions, and then updates the table. If you want to (re-)time all solutions, run `cargo time --all`. If you want to (re-)time one specific solution, run `cargo time <day>`.
126+
127+
Please note that these are not _scientific_ benchmarks, understand them as a fun approximation. 😉 Timings, especially in the microseconds range, might change a bit between invocations.
126128

127129
### ➡️ Run all tests
128130

‎src/main.rs‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use advent_of_code::template::commands::{all, download, read, scaffold, solve};
1+
use advent_of_code::template::commands::{all, download, read, scaffold, solve, time};
22
use args::{parse, AppArguments};
33

44
#[cfg(feature = "today")]
@@ -32,6 +32,10 @@ mod args {
3232
release: bool,
3333
time: bool,
3434
},
35+
Time {
36+
all: bool,
37+
day: Option<Day>,
38+
},
3539
#[cfg(feature = "today")]
3640
Today,
3741
}
@@ -44,6 +48,14 @@ mod args {
4448
release: args.contains("--release"),
4549
time: args.contains("--time"),
4650
},
51+
Some("time") => {
52+
let all = args.contains("--all");
53+
54+
AppArguments::Time {
55+
all,
56+
day: args.opt_free_from_str()?,
57+
}
58+
}
4759
Some("download") => AppArguments::Download {
4860
day: args.free_from_str()?,
4961
},
@@ -90,6 +102,7 @@ fn main() {
90102
}
91103
Ok(args) => match args {
92104
AppArguments::All { release, time } => all::handle(release, time),
105+
AppArguments::Time { day, all } => time::handle(day, all),
93106
AppArguments::Download { day } => download::handle(day),
94107
AppArguments::Read { day } => read::handle(day),
95108
AppArguments::Scaffold { day, download } => {

‎src/template/aoc_cli.rs‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ pub enum AocCommandError {
1111
CommandNotFound,
1212
CommandNotCallable,
1313
BadExitStatus(Output),
14-
IoError,
1514
}
1615

1716
impl Display for AocCommandError {
@@ -22,7 +21,6 @@ impl Display for AocCommandError {
2221
AocCommandError::BadExitStatus(_) => {
2322
write!(f, "aoc-cli exited with a non-zero status.")
2423
}
25-
AocCommandError::IoError => write!(f, "could not write output files to file system."),
2624
}
2725
}
2826
}

‎src/template/commands/all.rs‎

Lines changed: 2 additions & 251 deletions
Original file line numberDiff line numberDiff line change
@@ -1,254 +1,5 @@
1-
use std::io;
2-
3-
use crate::template::{
4-
all_days,
5-
readme_benchmarks::{self, Timings},
6-
Day, ANSI_BOLD, ANSI_ITALIC, ANSI_RESET,
7-
};
1+
use crate::template::{all_days, run_multi::run_multi};
82

93
pub fn handle(is_release: bool, is_timed: bool) {
10-
let mut timings: Vec<Timings> = vec![];
11-
12-
all_days().for_each(|day| {
13-
if day > 1 {
14-
println!();
15-
}
16-
17-
println!("{ANSI_BOLD}Day {day}{ANSI_RESET}");
18-
println!("------");
19-
20-
let output = child_commands::run_solution(day, is_timed, is_release).unwrap();
21-
22-
if output.is_empty() {
23-
println!("Not solved.");
24-
} else {
25-
let val = child_commands::parse_exec_time(&output, day);
26-
timings.push(val);
27-
}
28-
});
29-
30-
if is_timed {
31-
let total_millis = timings.iter().map(|x| x.total_nanos).sum::<f64>() / 1_000_000_f64;
32-
33-
println!("\n{ANSI_BOLD}Total:{ANSI_RESET} {ANSI_ITALIC}{total_millis:.2}ms{ANSI_RESET}");
34-
35-
if is_release {
36-
match readme_benchmarks::update(timings, total_millis) {
37-
Ok(()) => println!("Successfully updated README with benchmarks."),
38-
Err(_) => {
39-
eprintln!("Failed to update readme with benchmarks.");
40-
}
41-
}
42-
}
43-
}
44-
}
45-
46-
#[derive(Debug)]
47-
pub enum Error {
48-
BrokenPipe,
49-
Parser(String),
50-
IO(io::Error),
51-
}
52-
53-
impl From<std::io::Error> for Error {
54-
fn from(e: std::io::Error) -> Self {
55-
Error::IO(e)
56-
}
57-
}
58-
59-
#[must_use]
60-
pub fn get_path_for_bin(day: Day) -> String {
61-
format!("./src/bin/{day}.rs")
62-
}
63-
64-
/// All solutions live in isolated binaries.
65-
/// This module encapsulates interaction with these binaries, both invoking them as well as parsing the timing output.
66-
mod child_commands {
67-
use super::{get_path_for_bin, Error};
68-
use crate::template::Day;
69-
use std::{
70-
io::{BufRead, BufReader},
71-
path::Path,
72-
process::{Command, Stdio},
73-
thread,
74-
};
75-
76-
/// Run the solution bin for a given day
77-
pub fn run_solution(day: Day, is_timed: bool, is_release: bool) -> Result<Vec<String>, Error> {
78-
// skip command invocation for days that have not been scaffolded yet.
79-
if !Path::new(&get_path_for_bin(day)).exists() {
80-
return Ok(vec![]);
81-
}
82-
83-
let day_padded = day.to_string();
84-
let mut args = vec!["run", "--quiet", "--bin", &day_padded];
85-
86-
if is_release {
87-
args.push("--release");
88-
}
89-
90-
if is_timed {
91-
// mirror `--time` flag to child invocations.
92-
args.push("--");
93-
args.push("--time");
94-
}
95-
96-
// spawn child command with piped stdout/stderr.
97-
// forward output to stdout/stderr while grabbing stdout lines.
98-
99-
let mut cmd = Command::new("cargo")
100-
.args(&args)
101-
.stdout(Stdio::piped())
102-
.stderr(Stdio::piped())
103-
.spawn()?;
104-
105-
let stdout = BufReader::new(cmd.stdout.take().ok_or(super::Error::BrokenPipe)?);
106-
let stderr = BufReader::new(cmd.stderr.take().ok_or(super::Error::BrokenPipe)?);
107-
108-
let mut output = vec![];
109-
110-
let thread = thread::spawn(move || {
111-
stderr.lines().for_each(|line| {
112-
eprintln!("{}", line.unwrap());
113-
});
114-
});
115-
116-
for line in stdout.lines() {
117-
let line = line.unwrap();
118-
println!("{line}");
119-
output.push(line);
120-
}
121-
122-
thread.join().unwrap();
123-
cmd.wait()?;
124-
125-
Ok(output)
126-
}
127-
128-
pub fn parse_exec_time(output: &[String], day: Day) -> super::Timings {
129-
let mut timings = super::Timings {
130-
day,
131-
part_1: None,
132-
part_2: None,
133-
total_nanos: 0_f64,
134-
};
135-
136-
output
137-
.iter()
138-
.filter_map(|l| {
139-
if !l.contains(" samples)") {
140-
return None;
141-
}
142-
143-
let Some((timing_str, nanos)) = parse_time(l) else {
144-
eprintln!("Could not parse timings from line: {l}");
145-
return None;
146-
};
147-
148-
let part = l.split(':').next()?;
149-
Some((part, timing_str, nanos))
150-
})
151-
.for_each(|(part, timing_str, nanos)| {
152-
if part.contains("Part 1") {
153-
timings.part_1 = Some(timing_str.into());
154-
} else if part.contains("Part 2") {
155-
timings.part_2 = Some(timing_str.into());
156-
}
157-
158-
timings.total_nanos += nanos;
159-
});
160-
161-
timings
162-
}
163-
164-
fn parse_to_float(s: &str, postfix: &str) -> Option<f64> {
165-
s.split(postfix).next()?.parse().ok()
166-
}
167-
168-
fn parse_time(line: &str) -> Option<(&str, f64)> {
169-
// for possible time formats, see: https://github.com/rust-lang/rust/blob/1.64.0/library/core/src/time.rs#L1176-L1200
170-
let str_timing = line
171-
.split(" samples)")
172-
.next()?
173-
.split('(')
174-
.last()?
175-
.split('@')
176-
.next()?
177-
.trim();
178-
179-
let parsed_timing = match str_timing {
180-
s if s.contains("ns") => s.split("ns").next()?.parse::<f64>().ok(),
181-
s if s.contains("μs") => parse_to_float(s, "μs").map(|x| x * 1000_f64),
182-
s if s.contains("ms") => parse_to_float(s, "ms").map(|x| x * 1_000_000_f64),
183-
s => parse_to_float(s, "s").map(|x| x * 1_000_000_000_f64),
184-
}?;
185-
186-
Some((str_timing, parsed_timing))
187-
}
188-
189-
/// copied from: https://github.com/rust-lang/rust/blob/1.64.0/library/std/src/macros.rs#L328-L333
190-
#[cfg(feature = "test_lib")]
191-
macro_rules! assert_approx_eq {
192-
($a:expr, $b:expr) => {{
193-
let (a, b) = (&$a, &$b);
194-
assert!(
195-
(*a - *b).abs() < 1.0e-6,
196-
"{} is not approximately equal to {}",
197-
*a,
198-
*b
199-
);
200-
}};
201-
}
202-
203-
#[cfg(feature = "test_lib")]
204-
mod tests {
205-
use super::parse_exec_time;
206-
207-
use crate::day;
208-
209-
#[test]
210-
fn test_well_formed() {
211-
let res = parse_exec_time(
212-
&[
213-
"Part 1: 0 (74.13ns @ 100000 samples)".into(),
214-
"Part 2: 10 (74.13ms @ 99999 samples)".into(),
215-
"".into(),
216-
],
217-
day!(1),
218-
);
219-
assert_approx_eq!(res.total_nanos, 74130074.13_f64);
220-
assert_eq!(res.part_1.unwrap(), "74.13ns");
221-
assert_eq!(res.part_2.unwrap(), "74.13ms");
222-
}
223-
224-
#[test]
225-
fn test_patterns_in_input() {
226-
let res = parse_exec_time(
227-
&[
228-
"Part 1: @ @ @ ( ) ms (2s @ 5 samples)".into(),
229-
"Part 2: 10s (100ms @ 1 samples)".into(),
230-
"".into(),
231-
],
232-
day!(1),
233-
);
234-
assert_approx_eq!(res.total_nanos, 2100000000_f64);
235-
assert_eq!(res.part_1.unwrap(), "2s");
236-
assert_eq!(res.part_2.unwrap(), "100ms");
237-
}
238-
239-
#[test]
240-
fn test_missing_parts() {
241-
let res = parse_exec_time(
242-
&[
243-
"Part 1: ✖ ".into(),
244-
"Part 2: ✖ ".into(),
245-
"".into(),
246-
],
247-
day!(1),
248-
);
249-
assert_approx_eq!(res.total_nanos, 0_f64);
250-
assert_eq!(res.part_1.is_none(), true);
251-
assert_eq!(res.part_2.is_none(), true);
252-
}
253-
}
4+
run_multi(all_days().collect(), is_release, is_timed);
2545
}

‎src/template/commands/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pub mod download;
33
pub mod read;
44
pub mod scaffold;
55
pub mod solve;
6+
pub mod time;

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /