1use std::{
2 borrow::Cow,
3 fmt::{Debug, Display},
4 num::NonZeroU32,
5 path::PathBuf,
6};
7
8use anyhow::Result;
9use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
10
11use crate::{
12 evaluator::{
13 data::{
14 log_data_tree::{LogDataTree, PathStringOptions, SpanId},
15 log_message::Timing,
16 },
17 index_by_call_path::IndexByCallPath,
18 options::TILE_COUNT,
19 },
20 join::{self, KeyVal, keyval_inner_join},
21 stats_tables::{
22 dynamic_typing::{StatsOrCount, StatsOrCountOrSubStats},
23 stats::{
24 Stats, StatsError, StatsField, ToStatsString,
25 weighted::{WEIGHT_ONE, WeightedValue},
26 },
27 tables::{
28 table::{Table, TableKind},
29 table_field_view::TableFieldView,
30 },
31 },
32 times::{MicroTime, NanoTime},
33 utillib::{rayon_util::par_run::ParRun, tuple_transpose::TupleTranspose},
34};
35
36fn scopestats<'t, K: KeyDetails>(
37 log_data_tree: &LogDataTree<'t>,
38 spans: &[SpanId<'t>],
39) -> Result<Stats<K::ViewType, TILE_COUNT>, StatsError> {
40 let vals: Vec<WeightedValue> = spans
41 .into_iter()
42 .filter_map(|span_id| -> Option<_> {
43 let span = span_id.get_from_db(log_data_tree);
44 let (start, end) = span.start_and_end()?;
45 let value: u64 = K::timing_extract(end)?.into() - K::timing_extract(start)?.into();
46 let weight = NonZeroU32::try_from(start.n())
48 .expect("num_runs is always at least 1 in the start Timing");
49 Some(WeightedValue { value, weight })
50 })
51 .collect();
52 Stats::from_values(vals)
53}
54
55fn pn_stats<'t, K: KeyDetails>(
56 log_data_tree: &LogDataTree<'t>,
57 spans: &[SpanId<'t>],
58 pn: &str,
59) -> Result<KeyVal<Cow<'static, str>, StatsOrCountOrSubStats<K::ViewType, TILE_COUNT>>, StatsError>
60{
61 let r: Result<Stats<K::ViewType, TILE_COUNT>, StatsError> =
62 scopestats::<K>(log_data_tree, spans);
63 match r {
64 Ok(s) => Ok(KeyVal {
65 key: pn.to_string().into(),
66 val: StatsOrCount::Stats(s).into(),
67 }),
68 Err(StatsError::NoInputs) => {
69 let count = spans.len();
70 Ok(KeyVal {
71 key: pn.to_string().into(),
73 val: StatsOrCount::Count(count).into(),
74 })
75 }
76 Err(e) => Err(e),
77 }
78}
79
80fn table_for_field<'key, K: KeyDetails>(
83 kind: K,
84 log_data_tree: &LogDataTree<'key>,
85 index_by_call_path: &'key IndexByCallPath<'key>,
86) -> Result<Table<'static, K, StatsOrCountOrSubStats<K::ViewType, TILE_COUNT>>> {
87 let mut rows = Vec::new();
88
89 if kind.show_probe_names() {
91 rows = log_data_tree
92 .probe_names()
93 .into_par_iter()
94 .map(|pn| -> Result<join::KeyVal<_, _>, StatsError> {
95 pn_stats::<K>(log_data_tree, log_data_tree.spans_by_pn(&pn).unwrap(), pn)
96 })
97 .collect::<Result<Vec<_>, StatsError>>()?;
98 }
99
100 let mut rows2 = index_by_call_path
101 .call_paths()
102 .into_par_iter()
103 .map(|call_path| {
104 pn_stats::<K>(
105 log_data_tree,
106 index_by_call_path.spans_by_call_path(call_path).unwrap(),
107 call_path,
108 )
109 })
110 .collect::<Result<Vec<_>, StatsError>>()?;
111
112 rows.append(&mut rows2);
113
114 Ok(Table { kind, rows })
115}
116
117#[derive(Clone, PartialEq, Debug)]
125pub struct KeyRuntimeDetails {
126 pub normal_separator: &'static str,
128 pub reverse_separator: &'static str,
129 pub show_probe_names: bool,
131 pub show_paths_without_thread_number: bool,
132 pub show_paths_with_thread_number: bool,
133 pub show_paths_reversed_too: bool,
134 pub key_column_width: Option<f64>,
135 pub prefix: Option<&'static str>,
138 pub skip_process: bool,
140}
141
142impl KeyRuntimeDetails {
143 fn key_label(&self) -> String {
144 let mut cases = Vec::new();
145 cases.push("A: across all threads");
146 if self.show_paths_with_thread_number {
147 cases.push("N: by thread number");
148 }
149 if self.show_paths_reversed_too {
150 cases.push("..R: reversed");
151 }
152 format!("Probe name or path\n({})", cases.join(", ")).into()
153 }
154}
155
156trait KeyDetails: TableKind + Debug {
157 type ViewType: Into<u64> + From<u64> + ToStatsString + Debug + Display;
158 fn new(det: KeyRuntimeDetails) -> Self;
159 fn timing_extract(timing: &Timing) -> Option<Self::ViewType>;
161 fn all_fields_table_extract<'f>(
163 aft: &'f AllFieldsTable<SingleRunStats>,
164 ) -> &'f Table<'static, Self, StatsOrCountOrSubStats<Self::ViewType, TILE_COUNT>>;
165 fn show_probe_names(&self) -> bool;
167}
168
169macro_rules! def_key_details {
170 { $T:tt: $ViewType:tt, $table_name:tt, $timing_extract:expr, $aft_extract:expr, } => {
171 #[derive(Clone, Debug)]
172 pub struct $T(KeyRuntimeDetails);
173 impl TableKind for $T {
174 fn table_name(&self) -> Cow<'_, str> {
175 $table_name.into()
176 }
177 fn table_key_label(&self) -> Cow<'_, str> {
178 self.0.key_label().into()
179 }
180 fn table_key_column_width(&self) -> Option<f64> {
181 self.0.key_column_width
182 }
183 }
184 impl KeyDetails for $T {
185 type ViewType = $ViewType;
186 fn new(det: KeyRuntimeDetails) -> Self { Self(det) }
187 fn timing_extract(timing: &Timing) -> Option<Self::ViewType> {
188 ($timing_extract)(timing)
189 }
190 fn all_fields_table_extract<'f>(
191 aft: &'f AllFieldsTable<SingleRunStats>,
192 ) -> &'f Table<'static, Self, StatsOrCountOrSubStats<Self::ViewType, TILE_COUNT>>{
193 ($aft_extract)(aft)
194 }
195 fn show_probe_names(&self) -> bool {
196 self.0.show_probe_names
197 }
198 }
199 }
200}
201
202def_key_details! {
203 RealTime:
204 NanoTime, "real time",
205 |timing: &Timing| Some(timing.r),
206 |aft: &'f AllFieldsTable<SingleRunStats>| &aft.real_time,
207}
208def_key_details! {
209 CpuTime:
210 MicroTime, "cpu time",
211 |timing: &Timing| Some(timing.u),
212 |aft: &'f AllFieldsTable<SingleRunStats>| &aft.cpu_time,
213}
214def_key_details! {
215 SysTime:
216 MicroTime, "sys time",
217 |timing: &Timing| Some(timing.s),
218 |aft: &'f AllFieldsTable<SingleRunStats>| &aft.sys_time,
219}
220def_key_details! {
221 CtxSwitches:
222 u64, "ctx switches",
223 |timing: &Timing| Some(timing.nvcsw()? + timing.nivcsw()?),
224 |aft: &'f AllFieldsTable<SingleRunStats>| &aft.ctx_switches,
225}
226
227#[derive(Clone, Debug)]
228pub struct AllFieldsTableKindParams {
229 pub source_path: PathBuf,
230 pub key_details: KeyRuntimeDetails,
231}
232
233pub trait AllFieldsTableKind {}
235
236pub struct SingleRunStats;
238impl AllFieldsTableKind for SingleRunStats {}
239
240pub struct SummaryStats;
244impl AllFieldsTableKind for SummaryStats {}
245
246pub struct TrendStats;
249impl AllFieldsTableKind for TrendStats {}
250
251pub struct AllFieldsTable<Kind: AllFieldsTableKind> {
255 pub kind: Kind,
256 pub params: AllFieldsTableKindParams,
259 pub real_time: Table<'static, RealTime, StatsOrCountOrSubStats<NanoTime, TILE_COUNT>>,
260 pub cpu_time: Table<'static, CpuTime, StatsOrCountOrSubStats<MicroTime, TILE_COUNT>>,
261 pub sys_time: Table<'static, SysTime, StatsOrCountOrSubStats<MicroTime, TILE_COUNT>>,
262 pub ctx_switches: Table<'static, CtxSwitches, StatsOrCountOrSubStats<u64, TILE_COUNT>>,
263}
264
265impl<Kind: AllFieldsTableKind> AsRef<AllFieldsTable<Kind>> for AllFieldsTable<Kind> {
266 fn as_ref(&self) -> &AllFieldsTable<Kind> {
267 self
268 }
269}
270
271impl<Kind: AllFieldsTableKind> AllFieldsTable<Kind> {
272 pub fn tables(&self) -> Vec<&dyn TableFieldView<TILE_COUNT>> {
275 let mut tables: Vec<&dyn TableFieldView<TILE_COUNT>> = vec![];
276 let Self {
277 kind: _,
278 params: _,
279 real_time,
280 cpu_time,
281 sys_time,
282 ctx_switches,
283 } = self;
284 tables.push(real_time);
285 tables.push(cpu_time);
286 tables.push(sys_time);
287 tables.push(ctx_switches);
288 tables
289 }
290}
291
292impl AllFieldsTable<SingleRunStats> {
293 pub fn from_log_data_tree(
294 log_data_tree: &LogDataTree,
295 params: AllFieldsTableKindParams,
296 ) -> Result<Self> {
297 let AllFieldsTableKindParams {
298 key_details,
299 source_path: _,
301 } = ¶ms;
302
303 let KeyRuntimeDetails {
304 normal_separator,
305 reverse_separator,
306 show_paths_without_thread_number,
307 show_paths_with_thread_number,
308 show_paths_reversed_too,
309 skip_process,
310 prefix,
311 show_probe_names: _,
314 key_column_width: _,
315 } = key_details;
316 let skip_process = *skip_process;
317
318 let index_by_call_path = {
319 let mut opts = vec![];
327 if *show_paths_without_thread_number {
328 opts.push(PathStringOptions {
329 normal_separator,
330 reverse_separator,
331 ignore_process: true,
332 skip_process,
333 ignore_thread: true,
334 include_thread_number_in_path: false,
335 reversed: false,
336 prefix: prefix.unwrap_or("A:"),
338 });
339 }
340 if *show_paths_reversed_too {
348 opts.push(PathStringOptions {
349 normal_separator,
350 reverse_separator,
351 ignore_process: true,
352 skip_process,
353 ignore_thread: true,
354 include_thread_number_in_path: false,
355 reversed: true,
356 prefix: prefix.unwrap_or("AR:"),
357 });
358 }
359 if *show_paths_with_thread_number {
360 opts.push(PathStringOptions {
361 normal_separator,
362 reverse_separator,
363 ignore_process: true,
364 skip_process,
365 ignore_thread: true,
366 include_thread_number_in_path: true,
367 reversed: false,
368 prefix: prefix.unwrap_or("N:"),
370 });
371 if *show_paths_reversed_too {
372 opts.push(PathStringOptions {
373 normal_separator,
374 reverse_separator,
375 ignore_process: true,
376 skip_process,
377 ignore_thread: true,
378 include_thread_number_in_path: true,
379 reversed: true,
380 prefix: prefix.unwrap_or("NR:"),
381 });
382 }
383 }
384 IndexByCallPath::from_logdataindex(&log_data_tree, &opts)
385 };
386
387 let (real_time, cpu_time, sys_time, ctx_switches) = (
388 || {
389 table_for_field(
390 RealTime(key_details.clone()),
391 &log_data_tree,
392 &index_by_call_path,
393 )
394 },
395 || {
396 table_for_field(
397 CpuTime(key_details.clone()),
398 &log_data_tree,
399 &index_by_call_path,
400 )
401 },
402 || {
403 table_for_field(
404 SysTime(key_details.clone()),
405 &log_data_tree,
406 &index_by_call_path,
407 )
408 },
409 || {
410 table_for_field(
411 CtxSwitches(key_details.clone()),
412 &log_data_tree,
413 &index_by_call_path,
414 )
415 },
416 )
417 .par_run()
418 .transpose()?;
419
420 Ok(AllFieldsTable {
421 kind: SingleRunStats,
422 params,
423 real_time,
424 cpu_time,
425 sys_time,
426 ctx_switches,
427 })
428 }
429}
430
431fn summary_stats_for_field<'t, K: KeyDetails + 'static>(
436 key_details: &KeyRuntimeDetails,
437 afts: &[impl AsRef<AllFieldsTable<SingleRunStats>> + Sync],
438 extract_stats_field: StatsField<TILE_COUNT>, ) -> Table<'static, K, StatsOrCountOrSubStats<K::ViewType, TILE_COUNT>>
440where
441 K::ViewType: 'static,
442{
443 let mut rowss: Vec<_> = afts
444 .par_iter()
445 .map(|aft| {
446 Some(K::all_fields_table_extract(aft.as_ref()).rows.iter().map(
447 |KeyVal { key, val }| -> KeyVal<Cow<'static, str>, _> {
448 KeyVal {
449 key: key.clone(),
450 val,
451 }
452 },
453 ))
454 })
455 .collect();
456 let rows_merged: Vec<_> = keyval_inner_join(&mut rowss)
457 .expect("at least 1 table")
458 .collect();
459 let rows: Vec<_> = rows_merged
460 .into_par_iter()
461 .filter_map(|KeyVal { key, val }| {
462 let vals: Vec<WeightedValue> = val
465 .iter()
466 .filter_map(|s| match s {
467 StatsOrCountOrSubStats::StatsOrCount(stats_or_count) => match stats_or_count {
468 StatsOrCount::Stats(stats) => Some(stats.get(extract_stats_field)),
469 StatsOrCount::Count(c) => {
470 if extract_stats_field == StatsField::N {
471 Some(u64::try_from(*c).expect("hopefully in range, here, too"))
472 } else {
473 None
474 }
475 }
476 },
477 StatsOrCountOrSubStats::SubStats(_sub_stats) => {
478 unreachable!("SingleRunStats cannot contain SubStats")
479 }
480 })
481 .map(|value| WeightedValue {
482 value,
483 weight: WEIGHT_ONE,
484 })
485 .collect();
486 let maybe_val = match Stats::<K::ViewType, TILE_COUNT>::from_values_from_field(
487 extract_stats_field,
488 vals,
489 ) {
490 Ok(val) => Some(val.into()),
491 Err(e) => match e {
492 StatsError::NoInputs => {
493 None
497 }
498 StatsError::SaturatedU128 => {
499 unreachable!("expecting to never see values > u64")
500 }
501 StatsError::VirtualCountDoesNotFitUSize => unreachable!("on 64bit archs"),
502 StatsError::VirtualSumDoesNotFitU96 => panic!("stats error: {e:#}"),
503 },
504 };
505 let val = maybe_val?;
506 Some(KeyVal { key, val })
507 })
508 .collect();
509
510 Table {
511 kind: K::new(key_details.clone()), rows,
513 }
514}
515
516impl AllFieldsTable<SummaryStats> {
517 pub fn summary_stats(
518 afts: &[impl AsRef<AllFieldsTable<SingleRunStats>> + Sync],
519 field_selector: StatsField<TILE_COUNT>,
520 key_details: &KeyRuntimeDetails,
521 ) -> AllFieldsTable<SummaryStats> {
522 let params = afts[0].as_ref().params.clone();
524 for aft in afts {
525 if params.key_details != aft.as_ref().params.key_details {
526 panic!(
527 "unequal key_details in params: {:?} vs. {:?}",
528 params,
529 aft.as_ref().params
530 );
531 }
532 }
533
534 let (real_time, cpu_time, sys_time, ctx_switches) = (
535 || summary_stats_for_field::<RealTime>(key_details, afts, field_selector),
536 || summary_stats_for_field::<CpuTime>(key_details, afts, field_selector),
537 || summary_stats_for_field::<SysTime>(key_details, afts, field_selector),
538 || summary_stats_for_field::<CtxSwitches>(key_details, afts, field_selector),
539 )
540 .par_run();
541
542 AllFieldsTable {
543 kind: SummaryStats,
544 params,
545 real_time,
546 cpu_time,
547 sys_time,
548 ctx_switches,
549 }
550 }
551}