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 fastest = results
186 .first()
187 .expect("At least one kernel needed.")
188 .outcome
189 .as_ref()
190 .expect("At least one kernel has to succeed.");
191
192 if recording {
193 write_record(logger, key, results, log_context, fastest);
194 }
195 write_log(logger, level, key, results, log_context, fastest);
196}
197
198#[cfg_attr(not(std_io), allow(unused_variables))]
200fn write_record<K: AutotuneKey>(
201 logger: &mut Logger,
202 key: &K,
203 results: &[AutotuneResult],
204 log_context: Option<&AutotuneLogContext>,
205 fastest: &AutotuneOutcome,
206) {
207 #[cfg(std_io)]
208 {
209 let record = AutotuneRecord {
210 key: Cow::Borrowed(key),
211 fastest_index: fastest.index,
212 fastest_time: fastest.computation.median,
213 results: Cow::Borrowed(results),
214 log_context: log_context.map(Cow::Borrowed),
215 checks: log_context
216 .and_then(|c| c.checks.as_deref())
217 .map(Cow::Borrowed),
218 };
219
220 let msg = serde_json::to_string(&record).unwrap_or_else(|err| {
221 format!("{{\"error\": \"Failed to serialize the autotune record: {err}\"}}")
222 });
223 logger.log_autotune_record(&msg);
224 }
225 #[cfg(not(std_io))]
226 {
227 logger.log_autotune_record(
228 &"{\"error\": \"Recording autotune is not available without std_io\"}",
229 );
230 }
231}
232
233fn write_log<K: AutotuneKey>(
235 logger: &mut Logger,
236 level: AutotuneLogLevel,
237 key: &K,
238 results: &[AutotuneResult],
239 log_context: Option<&AutotuneLogContext>,
240 fastest: &AutotuneOutcome,
241) {
242 match level {
243 AutotuneLogLevel::Minimal => {
244 let top_times = results
245 .iter()
246 .filter_map(|r| {
247 r.outcome
248 .as_ref()
249 .ok()
250 .map(|o| (o.index, o.computation.median))
251 })
252 .take(3)
253 .collect::<Vec<_>>();
254
255 let context_str = log_context
256 .map(|c| format!(", context: {}", c))
257 .unwrap_or_default();
258 logger.log_autotune(&format!(
259 "Fastest result {}-{key}. Top 3 times: {top_times:?}{context_str}",
260 fastest.name,
261 ));
262 }
263 AutotuneLogLevel::Full => {
264 let mut context_str = String::new();
265 if let Some(ctx) = log_context {
266 use core::fmt::Write;
267 if let Some(b) = &ctx.bounds {
268 let _ = writeln!(
269 &mut context_str,
270 "Calculated bounds: {:?} - limit: {:?}",
271 b, ctx.limit
272 );
273 }
274 let _ = write!(&mut context_str, "{}", ctx);
275 }
276
277 logger.log_autotune(&format!(
278 "Fastest result {}-{key}.\nContext:\n{context_str}",
279 fastest.name,
280 ));
281
282 for result in results.iter() {
283 match &result.outcome {
284 Ok(val) => {
285 logger.log_autotune(&format!("{val}"));
286 }
287 Err(err) => logger.log_autotune(&format!("{err}")),
288 }
289 }
290 }
291 AutotuneLogLevel::Disabled => {}
292 }
293}