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

            Line data    Source code
       1              : //! The execution state of a plan, driven by events.
       2              : //!
       3              : //! Engines report what they observe as input events (`ProfileInputEvent`): statistics of the query, of fragments,
       4              : //! pipelines, slots (an operator in a pipeline) or operators, on whatever level the engine has. The model rolls
       5              : //! them up (slot -> operator / pipeline -> fragment -> query, see `rollup`) and emits output events
       6              : //! (`ProfileUpdate`, packed as `QueryProfileEvents`) for every entity whose effective state changed. Reported values
       7              : //! win over rolled up values, and values that nobody reported stay unknown.
       8              : //!
       9              : //! Counters are cumulative, so applying an event twice changes nothing and a dropped event is repaired by the
      10              : //! next one. The input events have a JSON form (serde), a recorded event log replays an execution.
      11              : 
      12              : mod rollup;
      13              : mod trino;
      14              : 
      15              : use super::query_profile::QueryProfile;
      16              : use dashql_buffers::profile as fb;
      17              : use flatbuffers::FlatBufferBuilder;
      18              : use serde::{Deserialize, Serialize};
      19              : 
      20              : /// The execution status of an entity
      21              : #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
      22              : #[serde(rename_all = "lowercase")]
      23              : pub enum Status {
      24              :     /// Not reported
      25              :     #[default]
      26              :     Unknown,
      27              :     /// Waiting to run
      28              :     Pending,
      29              :     /// Running
      30              :     Running,
      31              :     /// Finished successfully
      32              :     Succeeded,
      33              :     /// Failed
      34              :     Failed,
      35              :     /// Skipped
      36              :     Skipped,
      37              : }
      38              : 
      39              : impl Status {
      40              :     /// The FlatBuffers status
      41          415 :     pub fn to_fb(self) -> fb::ProfileExecutionStatus {
      42          415 :         match self {
      43            7 :             Status::Unknown => fb::ProfileExecutionStatus::UNKNOWN,
      44           13 :             Status::Pending => fb::ProfileExecutionStatus::PENDING,
      45          135 :             Status::Running => fb::ProfileExecutionStatus::RUNNING,
      46          249 :             Status::Succeeded => fb::ProfileExecutionStatus::SUCCEEDED,
      47           11 :             Status::Failed => fb::ProfileExecutionStatus::FAILED,
      48            0 :             Status::Skipped => fb::ProfileExecutionStatus::SKIPPED,
      49              :         }
      50          415 :     }
      51              : }
      52              : 
      53              : /// The execution statistics of an entity, `None` is unknown (not 0).
      54              : /// The counters are cumulative.
      55              : #[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
      56              : #[serde(rename_all = "camelCase", default, deny_unknown_fields)]
      57              : pub struct Stats {
      58              :     /// The time when the entity started in milliseconds since epoch
      59              :     #[serde(skip_serializing_if = "Option::is_none")]
      60              :     pub started_at: Option<u64>,
      61              :     /// The time when the entity finished in milliseconds since epoch
      62              :     #[serde(skip_serializing_if = "Option::is_none")]
      63              :     pub finished_at: Option<u64>,
      64              :     /// The tuples consumed
      65              :     #[serde(skip_serializing_if = "Option::is_none")]
      66              :     pub tuples_in: Option<u64>,
      67              :     /// The tuples produced
      68              :     #[serde(skip_serializing_if = "Option::is_none")]
      69              :     pub tuples_out: Option<u64>,
      70              :     /// The bytes consumed
      71              :     #[serde(skip_serializing_if = "Option::is_none")]
      72              :     pub bytes_in: Option<u64>,
      73              :     /// The bytes produced
      74              :     #[serde(skip_serializing_if = "Option::is_none")]
      75              :     pub bytes_out: Option<u64>,
      76              :     /// The CPU time in microseconds
      77              :     #[serde(skip_serializing_if = "Option::is_none")]
      78              :     pub cpu_time_us: Option<u64>,
      79              :     /// The wall time in microseconds
      80              :     #[serde(skip_serializing_if = "Option::is_none")]
      81              :     pub wall_time_us: Option<u64>,
      82              :     /// The blocked time in microseconds
      83              :     #[serde(skip_serializing_if = "Option::is_none")]
      84              :     pub blocked_time_us: Option<u64>,
      85              :     /// The memory currently attributed to the entity in bytes
      86              :     #[serde(skip_serializing_if = "Option::is_none")]
      87              :     pub memory_bytes: Option<u64>,
      88              :     /// The bytes spilled to disk
      89              :     #[serde(skip_serializing_if = "Option::is_none")]
      90              :     pub spilled_bytes: Option<u64>,
      91              :     /// The completed work units (splits, drivers, morsels)
      92              :     #[serde(skip_serializing_if = "Option::is_none")]
      93              :     pub work_done: Option<u64>,
      94              :     /// The known work units
      95              :     #[serde(skip_serializing_if = "Option::is_none")]
      96              :     pub work_total: Option<u64>,
      97              : }
      98              : 
      99              : /// The `ProfileStatsField` bit of every field, in the order of `Stats::fields`
     100              : const FIELD_BITS: [fb::ProfileStatsField; 13] = [
     101              :     fb::ProfileStatsField::STARTED_AT,
     102              :     fb::ProfileStatsField::FINISHED_AT,
     103              :     fb::ProfileStatsField::TUPLES_IN,
     104              :     fb::ProfileStatsField::TUPLES_OUT,
     105              :     fb::ProfileStatsField::BYTES_IN,
     106              :     fb::ProfileStatsField::BYTES_OUT,
     107              :     fb::ProfileStatsField::CPU_TIME,
     108              :     fb::ProfileStatsField::WALL_TIME,
     109              :     fb::ProfileStatsField::BLOCKED_TIME,
     110              :     fb::ProfileStatsField::MEMORY_BYTES,
     111              :     fb::ProfileStatsField::SPILLED_BYTES,
     112              :     fb::ProfileStatsField::WORK_DONE,
     113              :     fb::ProfileStatsField::WORK_TOTAL,
     114              : ];
     115              : 
     116              : impl Stats {
     117              :     /// The fields
     118         4108 :     fn fields(&self) -> [Option<u64>; 13] {
     119         4108 :         [
     120         4108 :             self.started_at,
     121         4108 :             self.finished_at,
     122         4108 :             self.tuples_in,
     123         4108 :             self.tuples_out,
     124         4108 :             self.bytes_in,
     125         4108 :             self.bytes_out,
     126         4108 :             self.cpu_time_us,
     127         4108 :             self.wall_time_us,
     128         4108 :             self.blocked_time_us,
     129         4108 :             self.memory_bytes,
     130         4108 :             self.spilled_bytes,
     131         4108 :             self.work_done,
     132         4108 :             self.work_total,
     133         4108 :         ]
     134         4108 :     }
     135              : 
     136              :     /// The fields, mutable
     137         2702 :     fn fields_mut(&mut self) -> [&mut Option<u64>; 13] {
     138         2702 :         [
     139         2702 :             &mut self.started_at,
     140         2702 :             &mut self.finished_at,
     141         2702 :             &mut self.tuples_in,
     142         2702 :             &mut self.tuples_out,
     143         2702 :             &mut self.bytes_in,
     144         2702 :             &mut self.bytes_out,
     145         2702 :             &mut self.cpu_time_us,
     146         2702 :             &mut self.wall_time_us,
     147         2702 :             &mut self.blocked_time_us,
     148         2702 :             &mut self.memory_bytes,
     149         2702 :             &mut self.spilled_bytes,
     150         2702 :             &mut self.work_done,
     151         2702 :             &mut self.work_total,
     152         2702 :         ]
     153         2702 :     }
     154              : 
     155              :     /// The reported fields (`ProfileStatsField` bits)
     156         1406 :     pub fn reported(&self) -> u16 {
     157        18278 :         self.fields().iter().zip(FIELD_BITS).filter(|(v, _)| v.is_some()).fold(0, |bits, (_, bit)| bits | bit.0)
     158         1406 :     }
     159              : 
     160              :     /// Does the entity report nothing?
     161          989 :     pub fn is_empty(&self) -> bool {
     162          989 :         self.reported() == 0
     163          989 :     }
     164              : 
     165              :     /// Overwrite the fields that another report knows
     166          388 :     pub fn overwrite(&mut self, report: &Stats) {
     167         5044 :         for (field, value) in self.fields_mut().into_iter().zip(report.fields()) {
     168         5044 :             if value.is_some() {
     169         2445 :                 *field = value;
     170         2924 :             }
     171              :         }
     172          388 :     }
     173              : 
     174              :     /// Fill the unknown fields from a fallback
     175         2314 :     pub fn or(mut self, fallback: &Stats) -> Stats {
     176        30082 :         for (field, value) in self.fields_mut().into_iter().zip(fallback.fields()) {
     177        30082 :             if field.is_none() {
     178        28078 :                 *field = value;
     179        28078 :             }
     180              :         }
     181         2314 :         self
     182         2314 :     }
     183              : 
     184              :     /// Pack as FlatBuffers struct
     185          415 :     pub fn to_fb(&self) -> fb::ProfileStats {
     186         5395 :         let v = |field: Option<u64>| field.unwrap_or(0);
     187          415 :         fb::ProfileStats::new(
     188          415 :             self.reported(),
     189          415 :             v(self.started_at),
     190          415 :             v(self.finished_at),
     191          415 :             v(self.tuples_in),
     192          415 :             v(self.tuples_out),
     193          415 :             v(self.bytes_in),
     194          415 :             v(self.bytes_out),
     195          415 :             v(self.cpu_time_us),
     196          415 :             v(self.wall_time_us),
     197          415 :             v(self.blocked_time_us),
     198          415 :             v(self.memory_bytes),
     199          415 :             v(self.spilled_bytes),
     200          415 :             v(self.work_done),
     201          415 :             v(self.work_total),
     202              :         )
     203          415 :     }
     204              : }
     205              : 
     206              : /// An operator of a pipeline that is registered at runtime
     207              : #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
     208              : #[serde(rename_all = "camelCase", deny_unknown_fields)]
     209              : pub struct SlotSpec {
     210              :     /// The operator
     211              :     pub operator: u32,
     212              :     /// The role of the operator in the pipeline
     213              :     pub role: SlotRole,
     214              : }
     215              : 
     216              : /// The role of an operator in a pipeline
     217              : #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
     218              : #[serde(rename_all = "lowercase")]
     219              : pub enum SlotRole {
     220              :     /// Produces the tuples of the pipeline
     221              :     Source,
     222              :     /// Passes the tuples on
     223              :     Through,
     224              :     /// Consumes the tuples of the pipeline
     225              :     Sink,
     226              : }
     227              : 
     228              : impl SlotRole {
     229              :     /// The FlatBuffers role
     230           93 :     pub fn to_fb(self) -> fb::ProfileSlotRole {
     231           93 :         match self {
     232           41 :             SlotRole::Source => fb::ProfileSlotRole::SOURCE,
     233           17 :             SlotRole::Through => fb::ProfileSlotRole::THROUGH,
     234           35 :             SlotRole::Sink => fb::ProfileSlotRole::SINK,
     235              :         }
     236           93 :     }
     237              : }
     238              : 
     239              : /// An input event: what the engine reported. Entities are addressed by their index in the profile, entities
     240              : /// registered at runtime get the next free index.
     241              : #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
     242              : #[serde(tag = "type", rename_all = "camelCase", rename_all_fields = "camelCase", deny_unknown_fields)]
     243              : pub enum ProfileInputEvent {
     244              :     /// A fragment appeared at runtime (a Trino stage without a plan yet)
     245              :     RegisterFragment {
     246              :         /// The event time in milliseconds since epoch
     247              :         time: u64,
     248              :         /// The fragment id of the engine
     249              :         #[serde(default, skip_serializing_if = "Option::is_none")]
     250              :         engine_id: Option<String>,
     251              :         /// The enclosing fragment, must exist
     252              :         #[serde(default, skip_serializing_if = "Option::is_none")]
     253              :         parent_fragment: Option<u32>,
     254              :     },
     255              :     /// A pipeline appeared at runtime (Trino pipelines are known from the operator summaries only)
     256              :     RegisterPipeline {
     257              :         /// The event time in milliseconds since epoch
     258              :         time: u64,
     259              :         /// The pipeline id of the engine
     260              :         #[serde(default, skip_serializing_if = "Option::is_none")]
     261              :         engine_id: Option<String>,
     262              :         /// The fragment that runs the pipeline
     263              :         #[serde(default, skip_serializing_if = "Option::is_none")]
     264              :         fragment: Option<u32>,
     265              :         /// The operators of the pipeline, they get the next slot indexes in this order
     266              :         slots: Vec<SlotSpec>,
     267              :     },
     268              :     /// The query state
     269              :     UpdateQuery {
     270              :         /// The event time in milliseconds since epoch
     271              :         time: u64,
     272              :         /// The status, unchanged if missing
     273              :         #[serde(default, skip_serializing_if = "Option::is_none")]
     274              :         status: Option<Status>,
     275              :         /// The reported statistics, missing fields are unchanged
     276              :         #[serde(default, skip_serializing_if = "Stats::is_empty")]
     277              :         stats: Stats,
     278              :     },
     279              :     /// The state of a fragment
     280              :     UpdateFragment {
     281              :         /// The event time in milliseconds since epoch
     282              :         time: u64,
     283              :         /// The fragment
     284              :         fragment: u32,
     285              :         /// The status, unchanged if missing
     286              :         #[serde(default, skip_serializing_if = "Option::is_none")]
     287              :         status: Option<Status>,
     288              :         /// The reported statistics, missing fields are unchanged
     289              :         #[serde(default, skip_serializing_if = "Stats::is_empty")]
     290              :         stats: Stats,
     291              :     },
     292              :     /// The state of a pipeline
     293              :     UpdatePipeline {
     294              :         /// The event time in milliseconds since epoch
     295              :         time: u64,
     296              :         /// The pipeline
     297              :         pipeline: u32,
     298              :         /// The status, unchanged if missing
     299              :         #[serde(default, skip_serializing_if = "Option::is_none")]
     300              :         status: Option<Status>,
     301              :         /// The reported statistics, missing fields are unchanged
     302              :         #[serde(default, skip_serializing_if = "Stats::is_empty")]
     303              :         stats: Stats,
     304              :     },
     305              :     /// The state of a slot (an operator in a pipeline)
     306              :     UpdateSlot {
     307              :         /// The event time in milliseconds since epoch
     308              :         time: u64,
     309              :         /// The slot
     310              :         slot: u32,
     311              :         /// The status, unchanged if missing
     312              :         #[serde(default, skip_serializing_if = "Option::is_none")]
     313              :         status: Option<Status>,
     314              :         /// The reported statistics, missing fields are unchanged
     315              :         #[serde(default, skip_serializing_if = "Stats::is_empty")]
     316              :         stats: Stats,
     317              :     },
     318              :     /// The state of an operator across its pipelines
     319              :     UpdateOperator {
     320              :         /// The event time in milliseconds since epoch
     321              :         time: u64,
     322              :         /// The operator
     323              :         operator: u32,
     324              :         /// The status, unchanged if missing
     325              :         #[serde(default, skip_serializing_if = "Option::is_none")]
     326              :         status: Option<Status>,
     327              :         /// The reported statistics, missing fields are unchanged
     328              :         #[serde(default, skip_serializing_if = "Stats::is_empty")]
     329              :         stats: Stats,
     330              :     },
     331              : }
     332              : 
     333              : impl ProfileInputEvent {
     334              :     /// The event time
     335          509 :     pub fn time(&self) -> u64 {
     336          509 :         match self {
     337           23 :             ProfileInputEvent::RegisterFragment { time, .. }
     338           46 :             | ProfileInputEvent::RegisterPipeline { time, .. }
     339          101 :             | ProfileInputEvent::UpdateQuery { time, .. }
     340          146 :             | ProfileInputEvent::UpdateFragment { time, .. }
     341            0 :             | ProfileInputEvent::UpdatePipeline { time, .. }
     342          157 :             | ProfileInputEvent::UpdateSlot { time, .. }
     343          509 :             | ProfileInputEvent::UpdateOperator { time, .. } => *time,
     344              :         }
     345          509 :     }
     346              : }
     347              : 
     348              : /// The state of an entity
     349              : #[derive(Clone, Copy, Debug, Default, PartialEq)]
     350              : pub struct EntityState {
     351              :     /// The status
     352              :     pub status: Status,
     353              :     /// The statistics
     354              :     pub stats: Stats,
     355              : }
     356              : 
     357              : impl EntityState {
     358              :     /// Apply a report
     359          388 :     fn apply(&mut self, status: Option<Status>, stats: &Stats) {
     360          388 :         if let Some(status) = status {
     361          372 :             self.status = status;
     362          372 :         }
     363          388 :         self.stats.overwrite(stats);
     364          388 :     }
     365              : 
     366              :     /// Is nothing known?
     367          560 :     fn is_unknown(&self) -> bool {
     368          560 :         self.status == Status::Unknown && self.stats.is_empty()
     369          560 :     }
     370              : }
     371              : 
     372              : /// An output event: the effective state of an entity changed
     373              : #[derive(Clone, Copy, Debug, PartialEq)]
     374              : pub struct ProfileUpdate {
     375              :     /// The event time in milliseconds since epoch
     376              :     pub time: u64,
     377              :     /// The entity kind
     378              :     pub entity: fb::ProfileEntity,
     379              :     /// The entity index, 0 for the query
     380              :     pub index: u32,
     381              :     /// The effective state
     382              :     pub state: EntityState,
     383              : }
     384              : 
     385              : /// The states of all entities
     386              : #[derive(Clone, Debug, Default)]
     387              : pub(crate) struct Entities {
     388              :     /// The query
     389              :     pub(crate) query: EntityState,
     390              :     /// The fragments
     391              :     pub(crate) fragments: Vec<EntityState>,
     392              :     /// The pipelines
     393              :     pub(crate) pipelines: Vec<EntityState>,
     394              :     /// The slots
     395              :     pub(crate) slots: Vec<EntityState>,
     396              :     /// The operators
     397              :     pub(crate) operators: Vec<EntityState>,
     398              : }
     399              : 
     400              : impl Entities {
     401              :     /// Resize to the entity counts
     402          322 :     fn resize(&mut self, fragments: usize, pipelines: usize, slots: usize, operators: usize) {
     403          322 :         self.fragments.resize(fragments, EntityState::default());
     404          322 :         self.pipelines.resize(pipelines, EntityState::default());
     405          322 :         self.slots.resize(slots, EntityState::default());
     406          322 :         self.operators.resize(operators, EntityState::default());
     407          322 :     }
     408              : 
     409              :     /// The state of an entity
     410         2250 :     fn get(&self, entity: fb::ProfileEntity, index: u32) -> Option<&EntityState> {
     411         2250 :         let index = index as usize;
     412         2250 :         match entity {
     413          218 :             fb::ProfileEntity::QUERY => Some(&self.query),
     414          338 :             fb::ProfileEntity::FRAGMENT => self.fragments.get(index),
     415          122 :             fb::ProfileEntity::PIPELINE => self.pipelines.get(index),
     416          274 :             fb::ProfileEntity::SLOT => self.slots.get(index),
     417         1298 :             _ => self.operators.get(index),
     418              :         }
     419         2250 :     }
     420              : 
     421              :     /// Visit every entity with its kind and index
     422          273 :     fn each(&self) -> impl Iterator<Item = (fb::ProfileEntity, u32, &EntityState)> {
     423         1092 :         fn all(
     424         1092 :             entity: fb::ProfileEntity,
     425         1092 :             states: &[EntityState],
     426         1092 :         ) -> impl Iterator<Item = (fb::ProfileEntity, u32, &EntityState)> {
     427         2537 :             states.iter().enumerate().map(move |(i, s)| (entity, i as u32, s))
     428         1092 :         }
     429          273 :         std::iter::once((fb::ProfileEntity::QUERY, 0, &self.query))
     430          273 :             .chain(all(fb::ProfileEntity::FRAGMENT, &self.fragments))
     431          273 :             .chain(all(fb::ProfileEntity::PIPELINE, &self.pipelines))
     432          273 :             .chain(all(fb::ProfileEntity::SLOT, &self.slots))
     433          273 :             .chain(all(fb::ProfileEntity::OPERATOR, &self.operators))
     434          273 :     }
     435              : }
     436              : 
     437              : /// The execution state of a plan
     438              : #[derive(Clone, Debug, Default)]
     439              : pub(crate) struct ExecutionState {
     440              :     /// What the engine reported
     441              :     pub(crate) reported: Entities,
     442              :     /// The effective states after rollups
     443              :     pub(crate) effective: Entities,
     444              :     /// The time when the effective state of an entity last changed, in the order of `Entities::each`
     445              :     pub(crate) changed_at: Vec<u64>,
     446              : }
     447              : 
     448              : impl ExecutionState {
     449              :     /// Resize to the entity counts
     450          161 :     pub(crate) fn resize(&mut self, fragments: usize, pipelines: usize, slots: usize, operators: usize) {
     451          161 :         self.reported.resize(fragments, pipelines, slots, operators);
     452          161 :         self.effective.resize(fragments, pipelines, slots, operators);
     453          161 :         self.changed_at.resize(1 + fragments + pipelines + slots + operators, 0);
     454          161 :     }
     455              : }
     456              : 
     457              : impl QueryProfile {
     458              :     /// Apply input events and return the updates of the effective state
     459          218 :     pub fn apply_events(&mut self, events: &[ProfileInputEvent]) -> Vec<ProfileUpdate> {
     460          218 :         let mut time = 0;
     461          452 :         for event in events {
     462          449 :             time = time.max(event.time());
     463          449 :             self.apply_event(event);
     464          449 :         }
     465          218 :         self.recompute(time)
     466          218 :     }
     467              : 
     468              :     /// Replay an event log: consecutive events with the same time form one batch (one poll of the engine).
     469              :     /// Returns the updates of all batches in order.
     470            6 :     pub fn replay(&mut self, events: &[ProfileInputEvent]) -> Vec<ProfileUpdate> {
     471           30 :         events.chunk_by(|a, b| a.time() == b.time()).flat_map(|batch| self.apply_events(batch)).collect()
     472            6 :     }
     473              : 
     474              :     /// Apply one input event, events that reference unknown entities are ignored
     475          449 :     fn apply_event(&mut self, event: &ProfileInputEvent) {
     476          449 :         let reported = &mut self.execution.reported;
     477          449 :         let update = |states: &mut Vec<EntityState>, index: u32, status: &Option<Status>, stats: &Stats| {
     478          293 :             if let Some(state) = states.get_mut(index as usize) {
     479          292 :                 state.apply(*status, stats);
     480          292 :             }
     481          293 :         };
     482          449 :         match event {
     483           18 :             ProfileInputEvent::RegisterFragment { engine_id, parent_fragment, .. } => {
     484           18 :                 if parent_fragment.is_some_and(|p| p as usize >= self.fragments.len()) {
     485            1 :                     return;
     486           17 :                 }
     487           17 :                 self.fragments.push(super::query_profile::Fragment {
     488           17 :                     engine_id: engine_id.clone(),
     489           17 :                     parent_fragment: *parent_fragment,
     490           17 :                     ..Default::default()
     491           17 :                 });
     492           17 :                 self.structure_changed();
     493              :             }
     494           42 :             ProfileInputEvent::RegisterPipeline { engine_id, fragment, slots, .. } => {
     495           42 :                 if fragment.is_some_and(|f| f as usize >= self.fragments.len())
     496           94 :                     || slots.iter().any(|s| s.operator as usize >= self.operators.len())
     497              :                 {
     498            1 :                     return;
     499           41 :                 }
     500           93 :                 let slots: Vec<_> = slots.iter().map(|s| (s.operator, s.role.to_fb())).collect();
     501           41 :                 self.add_pipeline(engine_id.clone(), *fragment, &slots, Vec::new());
     502           41 :                 self.structure_changed();
     503              :             }
     504           96 :             ProfileInputEvent::UpdateQuery { status, stats, .. } => reported.query.apply(*status, stats),
     505          138 :             ProfileInputEvent::UpdateFragment { fragment, status, stats, .. } => {
     506          138 :                 update(&mut reported.fragments, *fragment, status, stats)
     507              :             }
     508            0 :             ProfileInputEvent::UpdatePipeline { pipeline, status, stats, .. } => {
     509            0 :                 update(&mut reported.pipelines, *pipeline, status, stats)
     510              :             }
     511          133 :             ProfileInputEvent::UpdateSlot { slot, status, stats, .. } => {
     512          133 :                 update(&mut reported.slots, *slot, status, stats)
     513              :             }
     514           22 :             ProfileInputEvent::UpdateOperator { operator, status, stats, .. } => {
     515           22 :                 update(&mut reported.operators, *operator, status, stats)
     516              :             }
     517              :         }
     518          449 :     }
     519              : 
     520              :     /// The structure changed at runtime
     521           58 :     fn structure_changed(&mut self) {
     522           58 :         self.structure_version = self.structure_version.wrapping_add(1);
     523           58 :         self.execution.resize(self.fragments.len(), self.pipelines.len(), self.slots.len(), self.operators.len());
     524           58 :     }
     525              : 
     526              :     /// Recompute the effective state and return the entities that changed
     527          218 :     fn recompute(&mut self, time: u64) -> Vec<ProfileUpdate> {
     528          218 :         let effective = rollup::compute_effective(self, &self.execution.reported);
     529          218 :         let previous = std::mem::replace(&mut self.execution.effective, effective);
     530          218 :         let mut updates = Vec::new();
     531          218 :         let changed_at = &mut self.execution.changed_at;
     532         2250 :         for (i, (entity, index, state)) in self.execution.effective.each().enumerate() {
     533              :             // An entity registered in this batch starts unknown
     534         2250 :             let before = previous.get(entity, index).copied().unwrap_or_default();
     535         2250 :             if before == *state {
     536         1733 :                 continue;
     537          517 :             }
     538          517 :             changed_at[i] = time;
     539          517 :             updates.push(ProfileUpdate { time, entity, index, state: *state });
     540              :         }
     541          218 :         updates
     542          218 :     }
     543              : 
     544              :     /// The current effective state as updates: one update for every entity that has a state.
     545              :     /// A renderer that mounts late applies them like any other updates.
     546           55 :     pub fn snapshot_events(&self) -> Vec<ProfileUpdate> {
     547           55 :         self.execution
     548           55 :             .effective
     549           55 :             .each()
     550           55 :             .zip(&self.execution.changed_at)
     551          560 :             .filter(|((_, _, state), _)| !state.is_unknown())
     552          122 :             .map(|((entity, index, state), time)| ProfileUpdate { time: *time, entity, index, state: *state })
     553           55 :             .collect()
     554           55 :     }
     555              : 
     556              :     /// Reset the execution state: drop all reported state and the structure that was registered at runtime.
     557              :     /// Replaying the events of an execution up to a point in time after a reset seeks backwards.
     558            1 :     pub fn reset_execution(&mut self) {
     559            1 :         let (fragments, pipelines, slots) = self.loaded_counts;
     560            1 :         let registered = self.fragments.len() > fragments || self.pipelines.len() > pipelines;
     561            1 :         self.fragments.truncate(fragments);
     562            1 :         self.pipelines.truncate(pipelines);
     563            1 :         self.slots.truncate(slots);
     564            4 :         for op in &mut self.operators {
     565            6 :             op.slots.retain(|s| (*s as usize) < slots);
     566              :         }
     567            3 :         for edge in &mut self.operator_edges {
     568            3 :             edge.slot = edge.slot.filter(|s| (*s as usize) < slots);
     569              :         }
     570            1 :         if registered {
     571            1 :             self.structure_version = self.structure_version.wrapping_add(1);
     572            1 :         }
     573            1 :         self.execution = ExecutionState::default();
     574            1 :         self.execution.resize(self.fragments.len(), self.pipelines.len(), self.slots.len(), self.operators.len());
     575            1 :     }
     576              : 
     577              :     /// Apply input events in their JSON form (an array of events)
     578            1 :     pub fn apply_events_json(&mut self, events: &[u8]) -> Result<Vec<ProfileUpdate>, serde_json::Error> {
     579            1 :         let events: Vec<ProfileInputEvent> = serde_json::from_slice(events)?;
     580            1 :         Ok(self.apply_events(&events))
     581            1 :     }
     582              : 
     583              :     /// Pack updates as `QueryProfileEvents`
     584           19 :     pub fn pack_events(&self, updates: &[ProfileUpdate]) -> Vec<u8> {
     585           19 :         let mut builder = FlatBufferBuilder::new();
     586           19 :         let events: Vec<fb::ProfileUpdateEvent> = updates
     587           19 :             .iter()
     588          415 :             .map(|u| {
     589          415 :                 let stats = u.state.stats.to_fb();
     590          415 :                 fb::ProfileUpdateEvent::new(u.time, u.entity, u.index, u.state.status.to_fb(), &stats)
     591          415 :             })
     592           19 :             .collect();
     593           19 :         let events = builder.create_vector(&events);
     594           19 :         let root = fb::QueryProfileEvents::create(
     595           19 :             &mut builder,
     596           19 :             &fb::QueryProfileEventsArgs { structure_version: self.structure_version, events: Some(events) },
     597              :         );
     598           19 :         builder.finish(root, None);
     599           19 :         builder.finished_data().to_vec()
     600           19 :     }
     601              : }
        

Generated by: LCOV version 2.0-1