evobench_tools/stats_tables/tables/
table.rs

1//! `Table`: an implementation of `TableView` with keyed rows
2//!
3//! Rows pair a string key representing the
4//! first column with a value that implements `TableViewRow` and
5//! represents the remaining columns.
6//!
7//! `Table` is also parameterized with a `TableKind` type for type
8//! safety and to carry metadata (used to represent RealTime, CpuTime,
9//! SysTime and CtxSwitches tables, see
10//! [evaluator/all_field_tables.rs](../../evaluator/all_field_tables.rs)).
11
12use std::{borrow::Cow, fmt::Debug};
13
14use genawaiter::rc::Gen;
15use itertools::{EitherOrBoth, Itertools};
16
17use crate::{
18    evaluator::options::TILE_COUNT,
19    join::KeyVal,
20    resolution_unit::ResolutionUnit,
21    stats_tables::{
22        dynamic_typing::{StatsOrCount, StatsOrCountOrSubStats},
23        stats::{Stats, StatsField, SubStats, ToStatsString},
24        tables::{
25            change::{Change, IsBetter},
26            table_field_view::TableFieldView,
27            table_view::{ColumnFormatting, Highlight, TableView, TableViewRow, Unit},
28        },
29    },
30};
31
32pub trait TableKind: Clone {
33    fn table_name(&self) -> Cow<'_, str>;
34    /// The column title for the *key* field in the rows
35    fn table_key_label(&self) -> Cow<'_, str>;
36    /// Width of key column in number of characters (as per Excel),
37    /// None == automatic.
38    fn table_key_column_width(&self) -> Option<f64>;
39}
40
41pub struct Table<'key, K: TableKind, T> {
42    pub kind: K,
43    pub rows: Vec<KeyVal<Cow<'key, str>, T>>,
44}
45
46impl<'key, K: TableKind, T: TableViewRow<()>> TableView for Table<'key, K, T> {
47    fn table_view_header(&self) -> Box<dyn AsRef<[(Cow<'static, str>, Unit, ColumnFormatting)]>> {
48        let mut header = vec![(
49            self.kind.table_key_label().to_string().into(),
50            Unit::None,
51            ColumnFormatting::String {
52                width_chars: self.kind.table_key_column_width(),
53            },
54        )];
55        let row_header = T::table_view_header(());
56        for label in (*row_header).as_ref() {
57            header.push((*label).clone());
58        }
59        Box::new(header)
60    }
61
62    fn table_name(&self) -> Cow<'_, str> {
63        self.kind.table_name()
64    }
65
66    fn table_view_body<'s>(
67        &'s self,
68    ) -> Box<dyn Iterator<Item = Cow<'s, [(Cow<'s, str>, Highlight)]>> + 's> {
69        Box::new(
70            Gen::new(|co| async move {
71                for KeyVal { key, val } in &self.rows {
72                    // Can't re-use vals across yield calls for
73                    // lifetime reasons (or I don't know how), so
74                    // allocate a new one for every iteration.
75                    let mut vals = Vec::new();
76                    vals.push((key.clone(), Highlight::Neutral));
77                    val.table_view_row(&mut vals);
78                    co.yield_(vals.into()).await;
79                }
80            })
81            .into_iter(),
82        )
83    }
84}
85
86impl<'key, K: TableKind, ViewType: Debug + ToStatsString + From<u64> + ResolutionUnit>
87    TableFieldView<TILE_COUNT> for Table<'key, K, StatsOrCountOrSubStats<ViewType, TILE_COUNT>>
88{
89    fn table_key_vals<'s>(
90        &'s self,
91        stats_field: StatsField<TILE_COUNT>,
92    ) -> Box<dyn Iterator<Item = KeyVal<&'s str, u64>> + 's> {
93        Box::new(
94            Gen::new(|co| async move {
95                for KeyVal { key, val } in &self.rows {
96                    let val = match val {
97                        StatsOrCountOrSubStats::StatsOrCount(stats_or_count) => {
98                            match stats_or_count {
99                                StatsOrCount::Stats(stats) => stats.get(stats_field),
100                                StatsOrCount::Count(_) => {
101                                    // XX todo: I forgot: do I check
102                                    // if stats_field is a count and
103                                    // in that case give this value?
104                                    continue;
105                                }
106                            }
107                        }
108                        StatsOrCountOrSubStats::SubStats(sub_stats) => match sub_stats {
109                            SubStats::Count(stats) => stats.get(stats_field),
110                            SubStats::ViewType(stats) => stats.get(stats_field),
111                        },
112                    };
113                    co.yield_(KeyVal {
114                        key: key.as_ref(),
115                        val,
116                    })
117                    .await;
118                }
119            })
120            .into_iter(),
121        )
122    }
123
124    fn resolution_unit(&self) -> String {
125        ViewType::RESOLUTION_UNIT_SHORT.into()
126    }
127}
128
129impl<'key, K: TableKind, ViewType: Debug, const TILE_COUNT: usize>
130    Table<'key, K, StatsOrCount<ViewType, TILE_COUNT>>
131{
132    /// Silently ignores rows with keys that only appear on one side.
133    /// XX now take whole Groups.
134    pub fn change<Better: IsBetter>(
135        &self,
136        to: &Self,
137        extract: fn(&Stats<ViewType, TILE_COUNT>) -> u64,
138    ) -> Table<'key, K, Change<Better>> {
139        let mut rows: Vec<KeyVal<_, _>> = Vec::new();
140        for either_or_both in self
141            .rows
142            .iter()
143            .merge_join_by(&to.rows, |a, b| a.key.cmp(&b.key))
144        {
145            if let EitherOrBoth::Both(from, to) = either_or_both {
146                match (&from.val, &to.val) {
147                    (StatsOrCount::Stats(from_stats), StatsOrCount::Stats(to_stats)) => {
148                        rows.push(KeyVal {
149                            key: from.key.clone(), // OK, usually with a ref anyway?
150                            val: Change::new(extract(from_stats), extract(to_stats)),
151                        });
152                        // XX but also pass data for significance!
153                    }
154                    (StatsOrCount::Count(_from), StatsOrCount::Count(_to)) => {
155                        // Ignore bare counts for comparisons. -- XX
156                        // or should it output the relation? Usually
157                        // 1, but? But only when counts were asked! ->
158                        // take this boolean info about the extract
159                        // function as argument?
160                    }
161                    _ => panic!("not in sync, {from:?} vs. {to:?}"),
162                }
163            }
164            // Silently ignore rows with keys that only appear on one
165            // side.
166        }
167        Table {
168            kind: self.kind.clone(), // XX?
169            rows,
170        }
171    }
172}