LCOV - code coverage report
Current view: top level - dashql-core-profile/src/progress - rollup.rs Coverage Total Hit
Test: DashQL 915828b Lines: 98.3 % 121 119
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : //! The rollup of execution state: slot -> operator / pipeline -> fragment -> query.
       2              : //!
       3              : //! Every entity takes what the engine reported for it and fills the rest from its children:
       4              : //! - an operator from its slots: times, memory and work summed, tuples in summed over all slots, tuples out
       5              : //!   from the slots that pass tuples on (not the sinks), unless it only has sinks,
       6              : //! - a pipeline from its slots: times, memory and work summed, tuples in from the sources, tuples out from the
       7              : //!   sinks,
       8              : //! - a fragment from its pipelines, tuples out from its root operator. What no pipeline knows comes from the
       9              : //!   operators of the fragment (an analyzed Hyper plan reports operators only),
      10              : //! - the query from all fragments, tuples out from the fragments without parent.
      11              : //!
      12              : //! The state of a fragment is its own work, without its child fragments, like the stage statistics of Trino.
      13              : //! Every operator belongs to one fragment only and every pipeline to one fragment, so nothing is counted twice.
      14              : //! A status is rolled up over the children that know theirs.
      15              : 
      16              : use super::{Entities, EntityState, Stats, Status};
      17              : use crate::query_profile::QueryProfile;
      18              : use dashql_buffers::profile as fb;
      19              : 
      20              : /// Add an optional counter
      21        17336 : fn add(sum: &mut Option<u64>, value: Option<u64>) {
      22        17336 :     if let Some(value) = value {
      23         3459 :         *sum = Some(sum.unwrap_or(0).saturating_add(value));
      24        13877 :     }
      25        17336 : }
      26              : 
      27              : /// The rollup of the children of an entity
      28              : #[derive(Default)]
      29              : struct Rollup {
      30              :     /// The rolled up statistics
      31              :     stats: Stats,
      32              :     /// The children
      33              :     children: usize,
      34              :     /// The children that finished
      35              :     finished: usize,
      36              :     /// The latest finish time
      37              :     finished_at: Option<u64>,
      38              :     /// The statuses of the children, indexed by `Status as usize`
      39              :     statuses: [usize; 6],
      40              : }
      41              : 
      42              : impl Rollup {
      43              :     /// Add a child: times, memory and work are summed, the earliest start wins
      44         2218 :     fn add(&mut self, child: &EntityState) {
      45         2218 :         let (stats, child_stats) = (&mut self.stats, &child.stats);
      46         2218 :         self.children += 1;
      47         2218 :         self.statuses[child.status as usize] += 1;
      48         2218 :         if let Some(started_at) = child_stats.started_at {
      49           55 :             stats.started_at = Some(stats.started_at.map_or(started_at, |s| s.min(started_at)));
      50         2163 :         }
      51         2218 :         if let Some(finished_at) = child_stats.finished_at {
      52           30 :             self.finished += 1;
      53           30 :             self.finished_at = Some(self.finished_at.map_or(finished_at, |f| f.max(finished_at)));
      54         2188 :         }
      55         2218 :         add(&mut stats.cpu_time_us, child_stats.cpu_time_us);
      56         2218 :         add(&mut stats.wall_time_us, child_stats.wall_time_us);
      57         2218 :         add(&mut stats.blocked_time_us, child_stats.blocked_time_us);
      58         2218 :         add(&mut stats.memory_bytes, child_stats.memory_bytes);
      59         2218 :         add(&mut stats.spilled_bytes, child_stats.spilled_bytes);
      60         2218 :         add(&mut stats.work_done, child_stats.work_done);
      61         2218 :         add(&mut stats.work_total, child_stats.work_total);
      62         2218 :     }
      63              : 
      64              :     /// Add the input of a child
      65          396 :     fn add_input(&mut self, child: &Stats) {
      66          396 :         add(&mut self.stats.tuples_in, child.tuples_in);
      67          396 :         add(&mut self.stats.bytes_in, child.bytes_in);
      68          396 :     }
      69              : 
      70              :     /// Add the output of a child
      71          509 :     fn add_output(&mut self, child: &Stats) {
      72          509 :         add(&mut self.stats.tuples_out, child.tuples_out);
      73          509 :         add(&mut self.stats.bytes_out, child.bytes_out);
      74          509 :     }
      75              : 
      76              :     /// The rolled up status, over the children that know their status
      77         2314 :     fn status(&self) -> Status {
      78         4089 :         let count = |status: Status| self.statuses[status as usize];
      79         2314 :         let known = self.children - count(Status::Unknown);
      80         2314 :         if known == 0 {
      81         1899 :             Status::Unknown
      82          415 :         } else if count(Status::Failed) > 0 {
      83            8 :             Status::Failed
      84          407 :         } else if count(Status::Skipped) == known {
      85            0 :             Status::Skipped
      86          407 :         } else if count(Status::Succeeded) + count(Status::Skipped) == known {
      87          268 :             Status::Succeeded
      88          139 :         } else if count(Status::Running) > 0 || count(Status::Succeeded) > 0 {
      89          139 :             Status::Running
      90              :         } else {
      91            0 :             Status::Pending
      92              :         }
      93         2314 :     }
      94              : 
      95              :     /// The rolled up state. An entity finished when all children finished.
      96         2314 :     fn finish(mut self) -> EntityState {
      97         2314 :         if self.children > 0 && self.finished == self.children {
      98           12 :             self.stats.finished_at = self.finished_at;
      99         2302 :         }
     100         2314 :         EntityState { status: self.status(), stats: self.stats }
     101         2314 :     }
     102              : }
     103              : 
     104              : /// Merge the report of an entity with the rollup of its children, reported values win
     105         2314 : fn merge(reported: &EntityState, rollup: EntityState) -> EntityState {
     106              :     EntityState {
     107         2314 :         status: if reported.status != Status::Unknown { reported.status } else { rollup.status },
     108         2314 :         stats: reported.stats.or(&rollup.stats),
     109              :     }
     110         2314 : }
     111              : 
     112              : /// Compute the effective state of all entities
     113          218 : pub(super) fn compute_effective(model: &QueryProfile, reported: &Entities) -> Entities {
     114          218 :     let mut out = Entities { slots: reported.slots.clone(), ..Default::default() };
     115              : 
     116              :     // Operators from their slots
     117          218 :     out.operators = model
     118          218 :         .operators
     119          218 :         .iter()
     120          218 :         .enumerate()
     121         1298 :         .map(|(o, op)| {
     122         1298 :             let mut rollup = Rollup::default();
     123         1298 :             let passes_on = op.slots.iter().any(|s| model.slots[*s as usize].role != fb::ProfileSlotRole::SINK);
     124         1298 :             for &s in &op.slots {
     125          274 :                 let slot = &out.slots[s as usize];
     126          274 :                 rollup.add(slot);
     127          274 :                 rollup.add_input(&slot.stats);
     128          274 :                 if !passes_on || model.slots[s as usize].role != fb::ProfileSlotRole::SINK {
     129          222 :                     rollup.add_output(&slot.stats);
     130          222 :                 }
     131              :             }
     132         1298 :             merge(&reported.operators[o], rollup.finish())
     133         1298 :         })
     134          218 :         .collect();
     135              : 
     136              :     // Pipelines from their slots
     137          218 :     out.pipelines = model
     138          218 :         .pipelines
     139          218 :         .iter()
     140          218 :         .enumerate()
     141          218 :         .map(|(p, pipeline)| {
     142          122 :             let mut rollup = Rollup::default();
     143          274 :             for &s in &pipeline.slots {
     144          274 :                 let slot = &out.slots[s as usize];
     145          274 :                 rollup.add(slot);
     146          274 :                 match model.slots[s as usize].role {
     147          122 :                     fb::ProfileSlotRole::SOURCE => rollup.add_input(&slot.stats),
     148          106 :                     fb::ProfileSlotRole::SINK => rollup.add_output(&slot.stats),
     149           46 :                     _ => {}
     150              :                 }
     151              :             }
     152          122 :             merge(&reported.pipelines[p], rollup.finish())
     153          122 :         })
     154          218 :         .collect();
     155              : 
     156              :     // Fragments from their pipelines, the operators fill what no pipeline knows
     157          218 :     let mut pipelines_of: Vec<Vec<usize>> = vec![Vec::new(); model.fragments.len()];
     158          218 :     for (p, pipeline) in model.pipelines.iter().enumerate() {
     159          122 :         if let Some(f) = pipeline.fragment {
     160          122 :             pipelines_of[f as usize].push(p);
     161          122 :         }
     162              :     }
     163          218 :     out.fragments = Vec::with_capacity(model.fragments.len());
     164          341 :     for (f, fragment) in model.fragments.iter().enumerate() {
     165          338 :         let (mut by_pipelines, mut by_operators) = (Rollup::default(), Rollup::default());
     166          338 :         for &p in &pipelines_of[f] {
     167          122 :             by_pipelines.add(&out.pipelines[p]);
     168          122 :         }
     169         1210 :         for &o in &fragment.operators {
     170         1210 :             by_operators.add(&out.operators[o as usize]);
     171         1210 :         }
     172          338 :         let mut own = merge(&by_pipelines.finish(), by_operators.finish());
     173          338 :         if let Some(root) = fragment.root_operator {
     174          277 :             let root = &out.operators[root as usize].stats;
     175          277 :             (own.stats.tuples_out, own.stats.bytes_out) = (root.tuples_out, root.bytes_out);
     176          277 :         }
     177          338 :         out.fragments.push(merge(&reported.fragments[f], own));
     178              :     }
     179              : 
     180              :     // The query from all fragments, its output is the output of the top-level fragments
     181          218 :     let mut rollup = Rollup::default();
     182          341 :     for (f, fragment) in model.fragments.iter().enumerate() {
     183          338 :         rollup.add(&out.fragments[f]);
     184          338 :         if fragment.parent_fragment.is_none() {
     185          181 :             rollup.add_output(&out.fragments[f].stats);
     186          187 :         }
     187              :     }
     188          218 :     out.query = merge(&reported.query, rollup.finish());
     189          218 :     out
     190          218 : }
        

Generated by: LCOV version 2.0-1