evobench_tools/run/
command_log_file.rs1use std::{
7 borrow::Cow,
8 path::{Path, PathBuf},
9};
10
11use anyhow::Result;
12use chrono::DateTime;
13
14use crate::{
15 ctx, io_utils::output_capture_log::OutputCaptureLog, io_utils::zstd_file::decompressed_file,
16 run::key::BenchmarkingJobParameters,
17};
18
19fn split_off_log_file_params(s: &str) -> Option<(&str, &str, usize)> {
23 let mut line_endings = s.char_indices().filter(|(_, c)| *c == '\n').map(|(i, _)| i);
28 let mut lineno = 1;
29 let mut i = 0; loop {
31 let rest = &s[i..];
32 if let Some((t, _)) = rest.split_once('\t') {
33 if let Ok(_timestamp) = DateTime::parse_from_rfc3339(t) {
34 if i == 0 {
35 return None;
36 }
37 let head = &s[0..i - 1];
38 return Some((head, rest, lineno));
39 }
40 }
41 if let Some(i2) = line_endings.next() {
42 lineno += 1;
43 i = i2 + 1;
44 } else {
45 return None;
46 }
47 }
48}
49
50pub struct CommandLogFile<P: AsRef<Path>> {
53 pub path: P,
54}
55
56impl From<OutputCaptureLog> for CommandLogFile<PathBuf> {
60 fn from(value: OutputCaptureLog) -> Self {
61 Self {
62 path: value.into_path(),
63 }
64 }
65}
66
67impl<P: AsRef<Path>> From<P> for CommandLogFile<P> {
68 fn from(path: P) -> Self {
69 Self { path }
70 }
71}
72
73#[ouroboros::self_referencing]
77pub struct CommandLog<'l, P: AsRef<Path>> {
78 pub log_file: &'l CommandLogFile<P>,
79 pub contents: String,
80 #[borrows(contents)]
81 #[covariant]
82 pub head_and_rest: Option<(&'this str, &'this str, usize)>,
85}
86
87impl<P: AsRef<Path>> CommandLogFile<P> {
88 pub fn command_log<'l>(&'l self) -> Result<CommandLog<'l, P>> {
91 let log_path = self.path.as_ref();
92 let input = decompressed_file(log_path, None)?;
93 let log_contents =
94 std::io::read_to_string(input).map_err(ctx!("reading file {log_path:?}"))?;
95 Ok(CommandLog::new(self, log_contents, |contents| {
96 split_off_log_file_params(contents)
97 }))
98 }
99}
100
101#[derive(thiserror::Error, Debug)]
102pub enum ParseCommandLogError {
103 #[error("parsing command log file {0:?}: {1}")]
104 SerdeError(PathBuf, String),
105 #[error("command log file {0:?} is missing a metadata head")]
106 MissingHead(PathBuf),
107}
108
109impl<'l, P: AsRef<Path>> CommandLog<'l, P> {
110 pub fn path(&self) -> &Path {
111 self.borrow_log_file().path.as_ref()
112 }
113
114 pub fn path_string_lossy<'s>(&'s self) -> Cow<'s, str> {
115 self.path().to_string_lossy()
116 }
117
118 pub fn parse_log_file_params(&self) -> Result<BenchmarkingJobParameters, ParseCommandLogError> {
120 let (head, _rest, _lineno) = self
121 .borrow_head_and_rest()
122 .ok_or_else(|| ParseCommandLogError::MissingHead(self.path().to_owned()))?;
123 serde_yml::from_str(head)
124 .map_err(|e| ParseCommandLogError::SerdeError(self.path().to_owned(), e.to_string()))
125 }
126
127 pub fn log_contents_rest(&self) -> (&str, usize) {
131 if let Some((_, rest, lineno)) = self.borrow_head_and_rest() {
132 (rest, *lineno)
133 } else {
134 (self.borrow_contents(), 1)
135 }
136 }
137}