evobench_tools/evaluator/
all_outputs_all_fields_table.rs1use std::{
2 fs::File,
3 io::{BufWriter, Write},
4 ops::Deref,
5 path::PathBuf,
6};
7
8use anyhow::{Result, anyhow, bail};
9use cj_path_util::path_util::AppendToPath;
10use run_git::path_util::add_extension;
11
12use crate::{
13 config_file::ron_to_file_pretty,
14 evaluator::data::log_data_tree::LogDataTree,
15 evaluator::options::TILE_COUNT,
16 info,
17 io_utils::tempfile_utils::TempfileOptions,
18 join::KeyVal,
19 stats_tables::{
20 stats::StatsField,
21 tables::{excel_table_view::excel_file_write, table_view::TableView},
22 },
23 util::tree::Tree,
24 warn,
25};
26
27use super::{
28 all_fields_table::{
29 AllFieldsTable, AllFieldsTableKind, AllFieldsTableKindParams, KeyRuntimeDetails,
30 SingleRunStats, SummaryStats,
31 },
32 options::{CheckedOutputOptionsMapCase, EvaluationOpts, OutputVariants},
33};
34
35pub struct AllFieldsTableWithOutputPathOrBase<Kind: AllFieldsTableKind> {
36 aft: AllFieldsTable<Kind>,
37 output_path_or_base: PathBuf,
39 is_final_file: bool,
42}
43
44pub struct AllOutputsAllFieldsTable<Kind: AllFieldsTableKind>(
49 OutputVariants<AllFieldsTableWithOutputPathOrBase<Kind>>,
50);
51
52impl<Kind: AllFieldsTableKind> Deref for AllOutputsAllFieldsTable<Kind> {
53 type Target = OutputVariants<AllFieldsTableWithOutputPathOrBase<Kind>>;
54
55 fn deref(&self) -> &Self::Target {
56 &self.0
57 }
58}
59
60fn key_details_for(
61 case: CheckedOutputOptionsMapCase,
62 evaluation_opts: &EvaluationOpts,
63) -> KeyRuntimeDetails {
64 let EvaluationOpts {
65 key_width,
66 show_thread_number,
67 show_reversed,
68 } = evaluation_opts;
69
70 let (
71 normal_separator,
72 reverse_separator,
73 show_probe_names,
74 show_paths_without_thread_number,
75 show_paths_reversed_too,
76 key_column_width,
77 skip_process,
78 prefix,
79 );
80 match case {
81 CheckedOutputOptionsMapCase::Excel => {
82 normal_separator = " > ";
83 reverse_separator = " < ";
84 show_probe_names = true;
85 show_paths_without_thread_number = true;
86 show_paths_reversed_too = *show_reversed;
87 key_column_width = Some(*key_width);
88 skip_process = false;
89 prefix = None;
90 }
91 CheckedOutputOptionsMapCase::Flame => {
92 normal_separator = ";";
93 reverse_separator = ";";
94 show_probe_names = false;
95 show_paths_without_thread_number = !*show_thread_number;
96 show_paths_reversed_too = false;
97 key_column_width = None;
98 skip_process = true;
99 prefix = Some("");
100 }
101 }
102
103 KeyRuntimeDetails {
104 normal_separator,
105 reverse_separator,
106 show_probe_names,
107 show_paths_without_thread_number,
108 show_paths_with_thread_number: *show_thread_number,
109 show_paths_reversed_too,
110 key_column_width,
111 skip_process,
112 prefix,
113 }
114}
115
116impl AllOutputsAllFieldsTable<SingleRunStats> {
117 pub fn from_log_data_tree(
118 log_data_tree: &LogDataTree,
119 evaluation_opts: &EvaluationOpts,
120 output_opts: OutputVariants<PathBuf>,
121 is_final_file: bool,
122 ) -> Result<Self> {
123 let output_variants = output_opts.try_map(|case, path| -> Result<_> {
124 Ok(AllFieldsTableWithOutputPathOrBase {
125 aft: AllFieldsTable::from_log_data_tree(
126 log_data_tree,
127 AllFieldsTableKindParams {
128 source_path: log_data_tree.log_data().path.as_ref().into(),
129 key_details: key_details_for(case, evaluation_opts),
130 },
131 )?,
132 output_path_or_base: path,
133 is_final_file,
134 })
135 })?;
136 Ok(Self(output_variants))
137 }
138}
139
140impl AllOutputsAllFieldsTable<SummaryStats> {
141 pub fn summary_stats(
142 aoafts: &[AllOutputsAllFieldsTable<SingleRunStats>],
143 field_selector: StatsField<TILE_COUNT>,
144 evaluation_opts: &EvaluationOpts,
145 output_opts: OutputVariants<PathBuf>,
146 is_final_file: bool,
147 ) -> AllOutputsAllFieldsTable<SummaryStats> {
148 let lists_by_field = output_opts.clone().map(|case, _path| {
150 aoafts
151 .into_iter()
152 .map(|aoaft| {
153 &aoaft
154 .get(case)
155 .as_ref()
156 .expect(
157 "same output_opts given in previous layer \
158 leading to same set of options",
159 )
160 .aft
161 })
162 .collect::<Vec<_>>()
163 });
164 let x = lists_by_field.map(|case, afts| AllFieldsTableWithOutputPathOrBase {
165 aft: AllFieldsTable::summary_stats(
166 afts.as_slice(),
167 match case {
168 CheckedOutputOptionsMapCase::Excel => field_selector,
169 CheckedOutputOptionsMapCase::Flame => StatsField::Sum,
172 },
173 &key_details_for(case, evaluation_opts),
174 ),
175 output_path_or_base: output_opts.get(case).as_ref().expect("ditto").clone(),
176 is_final_file,
177 });
178 Self(x)
179 }
180}
181
182fn node_children_sum<'key>(tree: &Tree<'key, u64>) -> u64 {
186 tree.children
187 .iter()
188 .map(|(_, child)| child.value.unwrap_or_else(|| node_children_sum(child)))
189 .sum()
190}
191
192fn fix_tree<'key>(tree: Tree<'key, u64>) -> Tree<'key, u64> {
197 let value = tree.value.map(|orig_value| {
198 let orig_children_total: u64 = node_children_sum(&tree);
199 orig_value
201 .checked_sub(orig_children_total)
202 .unwrap_or_else(|| {
203 eprintln!(
204 "somehow parent has lower value, {orig_value}, \
205 than sum of children, {orig_children_total}"
206 );
207 0
208 })
209 });
210 Tree {
211 value,
212 children: tree
213 .children
214 .into_iter()
215 .map(|(key, child)| (key, fix_tree(child)))
216 .collect(),
217 }
218}
219
220#[test]
221fn t_fix_tree() {
222 let vals = &[
223 ("a", 2),
224 ("a:b", 1),
225 ("a:b:c", 1),
226 ("c:d", 3),
227 ("d:e:f", 4),
228 ("d", 5),
229 ];
230 let tree = Tree::from_key_val(vals.into_iter().map(|(k, v)| (k.split(':'), *v)));
231 dbg!(&tree);
232 assert_eq!(tree.get("a".split(':')), Some(&2));
233 assert_eq!(tree.get("a:b".split(':')), Some(&1));
234 assert_eq!(tree.get("a:b:c".split(':')), Some(&1));
235 let tree = fix_tree(tree);
236 dbg!(&tree);
237 assert_eq!(tree.get("a".split(':')), Some(&1));
238 assert_eq!(tree.get("a:b".split(':')), Some(&0));
239 assert_eq!(tree.get("a:b:c".split(':')), Some(&1));
240 }
242
243impl<Kind: AllFieldsTableKind> AllOutputsAllFieldsTable<Kind> {
244 pub fn write_to_files(self, flame_field: StatsField<TILE_COUNT>) -> Result<()> {
248 self.0.try_map(|case, aft| -> Result<()> {
249 let AllFieldsTableWithOutputPathOrBase {
250 aft,
251 output_path_or_base,
252 is_final_file,
253 } = aft;
254 if !is_final_file {
255 bail!(
256 "trying to save a table that wasn't marked as \
257 the last stage in a processing chain"
258 )
259 }
260 let tables = aft.tables();
261 match case {
262 CheckedOutputOptionsMapCase::Excel => {
263 excel_file_write(
264 tables.iter().map(|v| {
265 let v: &dyn TableView = *v;
266 v
267 }),
268 &output_path_or_base,
269 )?;
270 }
271 CheckedOutputOptionsMapCase::Flame => {
272 let curdir = PathBuf::from(".");
273 let flame_base_dir = output_path_or_base.parent().unwrap_or(&*curdir);
274 let flame_base_name = output_path_or_base
275 .file_name()
276 .ok_or_else(|| anyhow!("--flame option argument is missing a file name"))?
277 .to_string_lossy();
278
279 for table in tables {
280 if table.table_key_vals(flame_field).next().is_none() {
281 continue;
286 }
287
288 let lines: Vec<String> = {
289 let tree = Tree::from_key_val(
290 table
291 .table_key_vals(flame_field)
292 .map(|KeyVal { key, val }| (key.split(';'), val)),
293 );
294
295 let fixed_tree = fix_tree(tree);
296
297 fixed_tree
298 .into_joined_key_val(";")
299 .into_iter()
300 .map(|(path, val)| format!("{path} {val}"))
301 .collect()
302 };
303
304 if !lines.iter().any(|s| s.contains(';')) {
309 eprintln!(
310 "note: there are no lines with ';' to be fed to inferno, \
311 thus do not attempt to generate flame graph"
312 );
313 } else {
314 let target_path = flame_base_dir
315 .append(format!("{flame_base_name}-{}.svg", table.table_name()));
316 if let Err(e) = (|| -> Result<()> {
317 let tempfile = TempfileOptions {
318 target_path: target_path.clone(),
319 retain_tempfile: true,
320 migrate_access: false,
321 }
322 .tempfile()?;
323
324 let mut options = inferno::flamegraph::Options::default();
325 options.count_name = table.resolution_unit();
326 options.title = table.table_name().into();
327 let mut out = BufWriter::new(File::create(&tempfile.temp_path)?);
330 inferno::flamegraph::from_lines(
331 &mut options,
333 lines.iter().map(|s| -> &str { s }),
334 &mut out,
335 )?;
336 out.flush()?;
337 tempfile.finish()?;
338 Ok(())
339 })() {
340 warn!(
341 "ignoring error creating flamegraph file \
342 {target_path:?}: {e:#}"
343 );
344 let dump_path = add_extension(&target_path, "data")
345 .expect("guaranteed to have file name");
346 ron_to_file_pretty(&lines, &dump_path, false, None)?;
347 info!(
348 "wrote data to be used in {target_path:?} here: {dump_path:?}"
349 );
350 }
351 }
352 }
353 }
354 }
355 Ok(())
356 })?;
357 Ok(())
358 }
359}