1use crate::config::{Logger, autotune::AutotuneLogLevel};
2use crate::tune::{AutotuneKey, AutotuneOutcome, AutotuneResult};
3#[cfg(std_io)]
4use alloc::borrow::Cow;
5use alloc::format;
6use alloc::string::String;
7use alloc::vec::Vec;
8use core::time::Duration;
9
10#[derive(Debug, Clone)]
12#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
13pub enum AutotuneLogEvent {
14 TuningStep(String, Duration),
16 ShortCircuit(String),
19}
20
21#[derive(Debug, Clone, Default)]
23#[cfg_attr(std_io, derive(serde::Serialize, serde::Deserialize))]
24pub struct AutotuneLogContext {
25 pub bounds: Option<crate::tune::Bounds>,
27 pub limit: Option<Duration>,
29 pub events: Vec<AutotuneLogEvent>,
31 pub checks: Option<Vec<crate::tune::log::CheckResult>>,
33}
34
35impl AutotuneLogContext {
36 pub fn new(logger: &mut Logger) -> Option<Self> {
40 let logging = !matches!(logger.log_level_autotune(), AutotuneLogLevel::Disabled);
41 if logging || logger.autotune_recording_enabled() {
42 Some(Self {
43 bounds: None,
44 limit: None,
45 events: Vec::new(),
46 checks: None,
47 })
48 } else {
49 None
50 }
51 }
52}
53
54impl core::fmt::Display for AutotuneLogContext {
55 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56 for event in &self.events {
57 match event {
58 AutotuneLogEvent::TuningStep(step, duration) => {
59 write!(f, "\n - Tuning: {step} (compilation & bench: {duration:?})")?
60 }
61 AutotuneLogEvent::ShortCircuit(name) => write!(
62 f,
63 "\nShort circuiting autotune. {name} is close enough to peak throughput."
64 )?,
65 }
66 }
67 Ok(())
68 }
69}
70
71pub trait AutotuneLoggerExt {
73 fn push_short_circuit(&mut self, name: String);
75 fn push_tuning_step(&mut self, name: String, duration: Duration);
77 fn set_bounds(&mut self, bounds: Option<crate::tune::Bounds>);
79 fn set_limit(&mut self, limit: Option<Duration>);
81 fn set_checks(&mut self, checks: impl FnOnce() -> Vec<CheckResult>);
83 fn log_result<K: AutotuneKey>(&self, logger: &mut Logger, key: &K, results: &[AutotuneResult]);
85}
86
87macro_rules! impl_autotune_logger_ext {
92 ($ty:ty, $as_mut:ident, $as_ref:ident) => {
93 impl AutotuneLoggerExt for $ty {
94 fn push_short_circuit(&mut self, name: String) {
95 if let Some(ctx) = self.$as_mut() {
96 ctx.events.push(AutotuneLogEvent::ShortCircuit(name));
97 }
98 }
99
100 fn push_tuning_step(&mut self, name: String, duration: Duration) {
101 if let Some(ctx) = self.$as_mut() {
102 ctx.events
103 .push(AutotuneLogEvent::TuningStep(name, duration));
104 }
105 }
106
107 fn set_bounds(&mut self, bounds: Option<crate::tune::Bounds>) {
108 if let Some(ctx) = self.$as_mut() {
109 ctx.bounds = bounds;
110 }
111 }
112
113 fn set_limit(&mut self, limit: Option<Duration>) {
114 if let Some(ctx) = self.$as_mut() {
115 ctx.limit = limit;
116 }
117 }
118
119 fn set_checks(&mut self, checks: impl FnOnce() -> Vec<CheckResult>) {
120 if let Some(ctx) = self.$as_mut() {
121 ctx.checks = Some(checks());
122 }
123 }
124
125 fn log_result<K: AutotuneKey>(
126 &self,
127 logger: &mut Logger,
128 key: &K,
129 results: &[AutotuneResult],
130 ) {
131 log_result(logger, key, results, self.$as_ref());
132 }
133 }
134 };
135}
136
137impl_autotune_logger_ext!(Option<AutotuneLogContext>, as_mut, as_ref);
138impl_autotune_logger_ext!(Option<&'_ mut AutotuneLogContext>, as_deref_mut, as_deref);
139
140#[cfg(std_io)]
143#[derive(serde::Serialize, serde::Deserialize)]
144#[serde(bound(deserialize = "K: Clone + serde::Deserialize<'de>"))]
145pub struct AutotuneRecord<'a, K: Clone> {
146 pub key: Cow<'a, K>,
148 pub fastest_index: usize,
150 pub fastest_time: Duration,
152 pub results: Cow<'a, [AutotuneResult]>,
154 pub log_context: Option<Cow<'a, AutotuneLogContext>>,
156 pub checks: Option<Cow<'a, [CheckResult]>>,
158}
159
160#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
162pub struct CheckResult {
163 pub name: String,
165 pub passed: bool,
167}
168
169fn log_result<K: AutotuneKey>(
172 logger: &mut Logger,
173 key: &K,
174 results: &[AutotuneResult],
175 log_context: Option<&AutotuneLogContext>,
176) {
177 let level = logger.log_level_autotune();
178 let recording = logger.autotune_recording_enabled();
179 if matches!(level, AutotuneLogLevel::Disabled) && !recording {
180 return;
181 }
182
183 let Some(fastest) = results
190 .first()
191 .and_then(|result| result.outcome.as_ref().ok())
192 else {
193 return;
194 };
195
196 if recording {
197 write_record(logger, key, results, log_context, fastest);
198 }
199 write_log(logger, level, key, results, log_context, fastest);
200}
201
202#[cfg_attr(not(std_io), allow(unused_variables))]
204fn write_record<K: AutotuneKey>(
205 logger: &mut Logger,
206 key: &K,
207 results: &[AutotuneResult],
208 log_context: Option<&AutotuneLogContext>,
209 fastest: &AutotuneOutcome,
210) {
211 #[cfg(std_io)]
212 {
213 let record = AutotuneRecord {
214 key: Cow::Borrowed(key),
215 fastest_index: fastest.index,
216 fastest_time: fastest.computation.median,
217 results: Cow::Borrowed(results),
218 log_context: log_context.map(Cow::Borrowed),
219 checks: log_context
220 .and_then(|c| c.checks.as_deref())
221 .map(Cow::Borrowed),
222 };
223
224 let msg = serde_json::to_string(&record).unwrap_or_else(|err| {
225 format!("{{\"error\": \"Failed to serialize the autotune record: {err}\"}}")
226 });
227 logger.log_autotune_record(&msg);
228 }
229 #[cfg(not(std_io))]
230 {
231 logger.log_autotune_record(
232 &"{\"error\": \"Recording autotune is not available without std_io\"}",
233 );
234 }
235}
236
237fn write_log<K: AutotuneKey>(
239 logger: &mut Logger,
240 level: AutotuneLogLevel,
241 key: &K,
242 results: &[AutotuneResult],
243 log_context: Option<&AutotuneLogContext>,
244 fastest: &AutotuneOutcome,
245) {
246 match level {
247 AutotuneLogLevel::Minimal => {
248 let top_times = results
249 .iter()
250 .filter_map(|r| {
251 r.outcome
252 .as_ref()
253 .ok()
254 .map(|o| (o.index, o.computation.median))
255 })
256 .take(3)
257 .collect::<Vec<_>>();
258
259 let context_str = log_context
260 .map(|c| format!(", context: {}", c))
261 .unwrap_or_default();
262 logger.log_autotune(&format!(
263 "Fastest result {}-{key}. Top 3 times: {top_times:?}{context_str}",
264 fastest.name,
265 ));
266 }
267 AutotuneLogLevel::Full => {
268 let mut context_str = String::new();
269 if let Some(ctx) = log_context {
270 use core::fmt::Write;
271 if let Some(b) = &ctx.bounds {
272 let _ = writeln!(
273 &mut context_str,
274 "Calculated bounds: {:?} - limit: {:?}",
275 b, ctx.limit
276 );
277 }
278 let _ = write!(&mut context_str, "{}", ctx);
279 }
280
281 logger.log_autotune(&format!(
282 "Fastest result {}-{key}.\nContext:\n{context_str}",
283 fastest.name,
284 ));
285
286 for result in results.iter() {
287 match &result.outcome {
288 Ok(val) => {
289 logger.log_autotune(&format!("{val}"));
290 }
291 Err(err) => logger.log_autotune(&format!("{err}")),
292 }
293 }
294 }
295 AutotuneLogLevel::Disabled => {}
296 }
297}