euv_performance/runner/
fn.rs1use super::*;
2
3pub fn run() -> Result<(), Box<dyn Error>> {
9 let config: Config = Config::parse(env::args().skip(1))?;
10 if config.get_help() {
11 println!("{USAGE}");
12 return Ok(());
13 }
14 let Output {
15 status,
16 stdout,
17 stderr,
18 }: Output = Command::new(COMPILER).arg(COMPILER_FLAG).output()?;
19 if !status.success() {
20 return Err(format!(
21 "compiler metadata failed: {}",
22 String::from_utf8_lossy(&stderr)
23 )
24 .into());
25 }
26 let mut measurements: Vec<Measurement> = Vec::with_capacity(config.get_suites().len());
27 for &suite in config.get_suites() {
28 measurements.push(measure(&config, suite)?);
29 }
30 if config.get_suites().as_slice() == DEFAULT_SUITES.as_slice()
31 && measurements.len() != DEFAULT_SUITES.len()
32 {
33 return Err(format!("incomplete default suite coverage: {}", measurements.len()).into());
34 }
35 let mode: &'static str = if cfg!(feature = "allocation-tracking") {
36 MODE_ALLOCATIONS
37 } else {
38 MODE_TIMING
39 };
40 let environment: Environment = Environment::new(
41 SOURCE_ROOT.to_string(),
42 OS.to_string(),
43 ARCH.to_string(),
44 available_parallelism().map(NonZeroUsize::get).ok(),
45 String::from_utf8(stdout)?,
46 SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(),
47 LIMITATIONS.to_string(),
48 );
49 let report: Report = Report::new(Header::new(2, mode, environment), config, measurements);
50 for measurement in report.get_measurements() {
51 for sample in measurement.get_samples() {
52 eprintln!(
53 "{:?} round={} elapsed_ns={:?} p50_ns={:?} p95_ns={:?} p99_ns={:?} allocations={:?} work=({},{},{}) checksum={}",
54 measurement.get_suite(),
55 sample.get_round(),
56 sample.get_timings().try_get_elapsed_ns(),
57 sample.get_timings().try_get_p50_ns(),
58 sample.get_timings().try_get_p95_ns(),
59 sample.get_timings().try_get_p99_ns(),
60 sample.try_get_allocations(),
61 sample.get_work().get_core_ticks(),
62 sample.get_work().get_engine_ticks(),
63 sample.get_work().get_chart_ticks(),
64 sample.get_work().get_checksum()
65 );
66 }
67 }
68 let json: String = to_string_pretty(&report)?;
69 if let Some(path) = report.get_config().try_get_output() {
70 if let Some(parent) = path.parent()
71 && !parent.as_os_str().is_empty()
72 {
73 fs::create_dir_all(parent)?;
74 }
75 fs::write(path, json)?;
76 eprintln!("report={}", path.display());
77 } else {
78 println!("{json}");
79 }
80 Ok(())
81}
82
83pub(crate) fn measure(config: &Config, suite: Suite) -> Result<Measurement, Box<dyn Error>> {
94 let mut samples: Vec<Sample> = Vec::with_capacity(config.get_rounds());
95 let mut latencies: Vec<u64> = Vec::with_capacity(config.get_ticks());
96 let before: AllocationStats = AllocationStats::capture(true);
97 let started: Option<Instant> = start_clock();
98 let mut workload: Workload = Workload::create(suite, config.get_scale());
99 let initialization_ns: Option<u64> = elapsed(started);
100 let initialized: AllocationStats = AllocationStats::capture(false);
101 let initialization_allocations: Option<AllocationDelta> =
102 AllocationStats::delta(before, initialized);
103 let warmup_start: AllocationStats = AllocationStats::capture(true);
104 let mut frame: u64 = 0;
105 for _ in 0..config.get_warmup() {
106 black_box(workload.tick(frame));
107 frame += 1;
108 }
109 let warmup_allocations: Option<AllocationDelta> =
110 AllocationStats::delta(warmup_start, AllocationStats::capture(false));
111 for round in 0..config.get_rounds() {
112 latencies.clear();
113 let mut work: Work = Work::default();
114 let start: AllocationStats = AllocationStats::capture(true);
115 let started: Option<Instant> = start_clock();
116 for _ in 0..config.get_ticks() {
117 let tick_start: Option<Instant> = start_clock();
118 let tick_work: Work = workload.tick(frame);
119 if let Some(tick_ns) = elapsed(tick_start) {
120 latencies.push(tick_ns);
121 }
122 work.add(tick_work);
123 frame += 1;
124 }
125 let elapsed_ns: Option<u64> = elapsed(started);
126 let end: AllocationStats = AllocationStats::capture(false);
127 validate_work(suite, config.get_ticks(), work)?;
128 latencies.sort_unstable();
129 let timings: Timings = Timings::new(
130 elapsed_ns,
131 percentile(&latencies, 50),
132 percentile(&latencies, 95),
133 percentile(&latencies, 99),
134 elapsed_ns.map(|nanoseconds: u64| {
135 config.get_ticks() as f64 * 1_000_000_000.0 / nanoseconds.max(1) as f64
136 }),
137 );
138 samples.push(Sample::new(
139 round,
140 config.get_ticks(),
141 timings,
142 AllocationStats::delta(start, end),
143 work,
144 ));
145 }
146 drop(workload);
147 let after_drop: AllocationStats = AllocationStats::capture(false);
148 Ok(Measurement::new(
149 suite,
150 initialization_ns,
151 initialization_allocations,
152 warmup_allocations,
153 cfg!(feature = "allocation-tracking")
154 .then_some(after_drop.get_live() as i64 - before.get_live() as i64),
155 samples,
156 ))
157}
158
159pub(crate) fn validate_work(suite: Suite, ticks: usize, work: Work) -> Result<(), Box<dyn Error>> {
171 let expected_core: u64 = if matches!(suite, Suite::Core | Suite::Mixed) {
172 ticks as u64
173 } else {
174 0
175 };
176 let expected_engine: u64 = if matches!(suite, Suite::Engine | Suite::Mixed) {
177 ticks as u64
178 } else {
179 0
180 };
181 let expected_chart: u64 = if matches!(suite, Suite::Chart | Suite::Mixed) {
182 ticks as u64
183 } else {
184 0
185 };
186 if (
187 work.get_core_ticks(),
188 work.get_engine_ticks(),
189 work.get_chart_ticks(),
190 ) != (expected_core, expected_engine, expected_chart)
191 || work.get_checksum() == 0
192 {
193 return Err(format!("missing or unexpected subsystem work in {suite:?}: {work:?}").into());
194 }
195 Ok(())
196}
197
198fn percentile(sorted: &[u64], percent: usize) -> Option<u64> {
209 let index: usize = (sorted.len() * percent).div_ceil(100).saturating_sub(1);
210 sorted.get(index).copied()
211}
212
213fn start_clock() -> Option<Instant> {
219 if cfg!(feature = "allocation-tracking") {
220 None
221 } else {
222 Some(Instant::now())
223 }
224}
225
226fn elapsed(started: Option<Instant>) -> Option<u64> {
236 started.map(|started: Instant| started.elapsed().as_nanos() as u64)
237}