metrale_bench/
headless.rs1use std::path::PathBuf;
13use std::time::Duration;
14
15use anyhow::Result;
16
17use crate::benchmark::BenchmarkDescriptor;
18use crate::coherence::CoherencePolicy;
19use crate::executor::{BenchmarkExecutor, ExecutorMessage};
20use crate::history::{self, RunRecord, RunSource};
21use crate::params::ParamValues;
22use crate::plugin::{PluginEvent, TargetEndpoint};
23use crate::result::{BenchmarkResult, VerdictKind};
24
25#[derive(Clone, Debug)]
27pub struct HeadlessOptions {
28 pub poll: Duration,
30 pub save: bool,
33 pub source: RunSource,
34 pub metrale_version: String,
36 pub coherence: CoherencePolicy,
39 pub temp_ceilings: Option<crate::hardware::policy::TempCeilings>,
42}
43
44impl HeadlessOptions {
45 pub fn cli(metrale_version: impl Into<String>) -> Self {
46 Self {
47 poll: Duration::from_millis(250),
48 save: true,
49 source: RunSource::Cli,
50 metrale_version: metrale_version.into(),
51 coherence: CoherencePolicy::Probe,
52 temp_ceilings: None,
53 }
54 }
55}
56
57pub struct RunRequest {
59 pub descriptor: &'static BenchmarkDescriptor,
60 pub values: ParamValues,
61 pub target: TargetEndpoint,
64 pub options: HeadlessOptions,
65}
66
67pub trait RunReporter {
70 fn started(&mut self, _request: &RunRequest) {}
71 fn event(&mut self, _event: &PluginEvent) {}
72 fn frame(&mut self, _frame: &BenchmarkResult) {}
73}
74
75pub struct SilentReporter;
78impl RunReporter for SilentReporter {}
79
80#[derive(Debug)]
81pub struct RunOutcome {
82 pub record: RunRecord,
83 pub saved_to: Option<PathBuf>,
85 pub cancelled: bool,
86}
87
88impl RunOutcome {
89 pub fn exit_code(&self) -> i32 {
92 if self.cancelled
93 || !matches!(
94 self.record.frame.status,
95 crate::result::RunStatus::Completed
96 )
97 {
98 return 1;
99 }
100 match self.record.verdict_kind() {
101 Some(VerdictKind::Fail) => 2,
102 _ => 0,
103 }
104 }
105}
106
107pub fn run_blocking(
114 executor: &BenchmarkExecutor,
115 request: RunRequest,
116 reporter: &mut dyn RunReporter,
117 should_cancel: &dyn Fn() -> bool,
118) -> Result<RunOutcome> {
119 let specs = request.descriptor.build().parameters();
120 request.values.validate_against(&specs)?;
121
122 reporter.started(&request);
123 let run = executor.start(
124 request.descriptor,
125 request.values.clone(),
126 request.target.clone(),
127 request.options.coherence,
128 request.options.temp_ceilings,
129 );
130
131 let mut terminal: Option<BenchmarkResult> = None;
132 loop {
133 for message in run.drain() {
134 dispatch(message, reporter, &mut terminal);
135 }
136 if should_cancel() && !run.is_cancelled() {
137 run.cancel();
138 reporter.event(&PluginEvent::Status(
139 "cancelling — stopping after the request in flight".into(),
140 ));
141 }
142 if run.is_finished() {
143 break;
144 }
145 std::thread::sleep(request.options.poll);
146 }
147
148 loop {
151 let messages = run.drain();
152 if messages.is_empty() {
153 break;
154 }
155 for message in messages {
156 dispatch(message, reporter, &mut terminal);
157 }
158 std::thread::sleep(Duration::from_millis(10));
159 }
160
161 let cancelled = run.is_cancelled();
162 let frame = terminal.unwrap_or_else(|| {
163 BenchmarkResult::failed(
164 "run",
165 "the run ended without a terminal frame",
166 Duration::ZERO,
167 )
168 });
169
170 let mut record = RunRecord::new(
171 request.descriptor,
172 &request.values,
173 &request.target,
174 request.target.serve_overrides.clone(),
175 request.options.source,
176 &request.options.metrale_version,
177 frame,
178 );
179 let saved_to = if request.options.save {
180 Some(history::save(executor.artifacts(), &mut record)?)
181 } else {
182 None
183 };
184
185 Ok(RunOutcome {
186 record,
187 saved_to,
188 cancelled,
189 })
190}
191
192fn dispatch(
193 message: ExecutorMessage,
194 reporter: &mut dyn RunReporter,
195 terminal: &mut Option<BenchmarkResult>,
196) {
197 match message {
198 ExecutorMessage::Event(e) => reporter.event(&e),
199 ExecutorMessage::Frame(f) => {
200 reporter.frame(&f);
201 if f.status.is_terminal() {
202 *terminal = Some(*f);
203 }
204 }
205 }
206}
207
208#[cfg(test)]
209#[path = "headless_tests.rs"]
210mod tests;