1use std::sync::Arc;
2
3use anyhow::{Result, anyhow, bail};
4
5use crate::{
6 fallback_to_default, fallback_to_option,
7 git::GitHash,
8 run::{
9 config::RunConfig,
10 key::{BenchmarkingJobParameters, CustomParameters, RunParameters},
11 sub_command::insert::{ForceInvalidOpt, InsertBenchmarkingJobOpts},
12 },
13 serde_types::priority::{NonComparableNumber, Priority},
14 utillib::{arc::CloneArc, fallback::FallingBackTo},
15};
16
17use super::{
18 config::{BenchmarkingCommand, JobTemplate},
19 working_directory_pool::WorkingDirectoryId,
20};
21
22#[derive(Debug, PartialEq, Clone, clap::Args, serde::Serialize, serde::Deserialize)]
23#[serde(deny_unknown_fields)]
24#[serde(rename = "BenchmarkingJobSettings")]
25pub struct BenchmarkingJobSettingsOpts {
26 #[clap(short, long)]
29 count: Option<u8>,
30
31 #[clap(short, long)]
34 error_budget: Option<u8>,
35}
36
37pub struct BenchmarkingJobSettings {
38 count: u8,
39 error_budget: u8,
40}
41
42impl Default for BenchmarkingJobSettings {
43 fn default() -> Self {
44 Self {
45 count: 5,
46 error_budget: 3,
47 }
48 }
49}
50
51impl FallingBackTo for BenchmarkingJobSettingsOpts {
52 fn falling_back_to(
53 self,
54 fallback: &BenchmarkingJobSettingsOpts,
55 ) -> BenchmarkingJobSettingsOpts {
56 let Self {
57 count,
58 error_budget,
59 } = self;
60 fallback_to_option!(fallback.count);
61 fallback_to_option!(fallback.error_budget);
62 BenchmarkingJobSettingsOpts {
63 count,
64 error_budget,
65 }
66 }
67}
68
69impl From<BenchmarkingJobSettingsOpts> for BenchmarkingJobSettings {
70 fn from(value: BenchmarkingJobSettingsOpts) -> Self {
71 let BenchmarkingJobSettingsOpts {
72 count,
73 error_budget,
74 } = value;
75 let default = BenchmarkingJobSettings::default();
76 fallback_to_default!(default.count);
77 fallback_to_default!(default.error_budget);
78 Self {
79 count,
80 error_budget,
81 }
82 }
83}
84
85#[derive(Debug, PartialEq, Clone, clap::Args)]
86pub struct BenchmarkingJobReasonOpt {
87 #[clap(long)]
91 pub reason: Option<String>,
92}
93
94#[derive(Debug)]
95pub struct BenchmarkingJobOpts {
96 pub insert_benchmarking_job_opts: InsertBenchmarkingJobOpts,
98 pub commit_id: GitHash,
99}
100
101#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
103#[serde(deny_unknown_fields)]
104pub struct BenchmarkingJobPublic {
105 pub reason: Option<String>,
106 pub run_parameters: Arc<RunParameters>,
107 pub command: Arc<BenchmarkingCommand>,
108}
109
110#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
112#[serde(deny_unknown_fields)]
113pub struct BenchmarkingJobState {
114 pub remaining_count: u8,
115 pub remaining_error_budget: u8,
116 pub last_working_directory: Option<WorkingDirectoryId>,
117}
118
119#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
120#[serde(deny_unknown_fields)]
121pub struct BenchmarkingJob {
122 #[serde(flatten)]
123 pub public: BenchmarkingJobPublic,
124 #[serde(flatten)]
125 pub state: BenchmarkingJobState,
126 priority: Priority,
127 current_boost: Priority,
128}
129
130impl BenchmarkingJob {
131 pub fn new(
134 public: BenchmarkingJobPublic,
135 state: BenchmarkingJobState,
136 priority: Priority,
137 current_boost: Priority,
138 ) -> Self {
139 Self {
140 public,
141 state,
142 priority,
143 current_boost,
144 }
145 }
146
147 pub fn check_and_init(
156 &mut self,
157 config: &RunConfig,
158 override_opts: &InsertBenchmarkingJobOpts,
159 override_commit: Option<&GitHash>,
160 force: &ForceInvalidOpt,
161 ) -> Result<()> {
162 {
166 let BenchmarkingJobState {
167 remaining_count,
168 remaining_error_budget,
169 last_working_directory,
170 } = &mut self.state;
171
172 *last_working_directory = None;
173
174 let InsertBenchmarkingJobOpts {
175 reason,
176 benchmarking_job_settings,
177 priority,
178 initial_boost,
179 } = override_opts;
180
181 {
182 let BenchmarkingJobSettings {
183 count,
184 error_budget,
185 } = benchmarking_job_settings
186 .clone()
187 .falling_back_to(&config.benchmarking_job_settings)
188 .into();
189
190 *remaining_count = count;
191 *remaining_error_budget = error_budget;
192 }
193
194 if let Some(initial_boost) = initial_boost {
195 self.current_boost = initial_boost.clone();
196 }
197 if let Some(priority) = priority {
198 self.priority = priority.clone();
199 }
200
201 if let Some(reason) = &reason.reason {
202 self.public.reason = Some(reason.into());
203 }
204
205 if let Some(override_commit) = override_commit {
206 let mut run_parameters: RunParameters = (*self.public.run_parameters).clone();
207 run_parameters.commit_id = override_commit.clone();
208 self.public.run_parameters = Arc::new(run_parameters);
209 }
210 }
211
212 if !force.force_invalid {
214 let Self {
215 public:
216 BenchmarkingJobPublic {
217 reason: _,
218 run_parameters,
219 command,
220 },
221 state: _,
222 priority: _,
223 current_boost: _,
224 } = self;
225
226 let target_name = &command.target_name;
227 let target = config.targets.get(target_name).ok_or_else(|| {
228 anyhow!("target {:?} not found in the config", target_name.as_str())
229 })?;
230
231 let _ = CustomParameters::checked_from(
232 &run_parameters.custom_parameters.keyvals(),
233 &target.allowed_custom_parameters,
234 )?;
235
236 if *command != target.benchmarking_command {
237 bail!(
238 "command for target {:?} is expected to be {:?}, but is: {:?}",
239 target_name.as_str(),
240 target.benchmarking_command,
241 command
242 );
243 }
244 }
245
246 Ok(())
247 }
248
249 pub fn priority(&self) -> Result<Priority, NonComparableNumber> {
250 self.priority + self.current_boost
251 }
252
253 pub fn clone_for_queue_reinsertion(&self, state: BenchmarkingJobState) -> Self {
256 let Self {
257 public,
258 priority,
259 current_boost: _,
260 state: _,
261 } = self;
262 Self {
263 public: public.clone(),
264 state,
265 priority: *priority,
266 current_boost: Priority::NORMAL,
267 }
268 }
269
270 pub fn benchmarking_job_parameters(&self) -> BenchmarkingJobParameters {
271 let BenchmarkingJob {
274 public:
275 BenchmarkingJobPublic {
276 reason: _,
277 run_parameters,
278 command,
279 },
280 state: _,
281 priority: _,
282 current_boost: _,
283 } = self;
284 BenchmarkingJobParameters {
285 run_parameters: run_parameters.clone_arc(),
286 command: command.clone_arc(),
287 }
288 }
289}
290
291impl BenchmarkingJobOpts {
292 pub fn complete_jobs(&self, job_template_list: &[JobTemplate]) -> Vec<BenchmarkingJob> {
296 let Self {
297 insert_benchmarking_job_opts:
298 InsertBenchmarkingJobOpts {
299 reason,
300 benchmarking_job_settings,
301 priority: opts_priority,
302 initial_boost: opts_initial_boost,
303 },
304 commit_id,
305 } = self;
306
307 let BenchmarkingJobSettings {
308 count,
309 error_budget,
310 } = benchmarking_job_settings.clone().into();
311
312 job_template_list
313 .iter()
314 .map(|job_template| {
315 let JobTemplate {
316 priority,
317 initial_boost,
318 command,
319 custom_parameters,
320 } = job_template;
321
322 BenchmarkingJob {
323 public: BenchmarkingJobPublic {
324 reason: reason.reason.clone(),
325 run_parameters: Arc::new(RunParameters {
326 commit_id: commit_id.clone(),
327 custom_parameters: custom_parameters.clone_arc(),
328 }),
329 command: command.clone_arc(),
330 },
331 state: BenchmarkingJobState {
332 remaining_count: count,
333 remaining_error_budget: error_budget,
334 last_working_directory: None,
335 },
336 priority: opts_priority.unwrap_or(*priority),
337 current_boost: opts_initial_boost.unwrap_or(*initial_boost),
338 }
339 })
340 .collect()
341 }
342}