Line data Source code
1 : import * as dashql from '@ankoh/dashql-core';
2 : /// No index (u32::MAX)
3 1 : const NO_INDEX = 0xFFFFFFFF;
4 :
5 : type QueryProfileEvents = dashql.buffers.profile.QueryProfileEvents;
6 : type ProfileStatsT = dashql.buffers.profile.ProfileStatsT;
7 1 : const view = dashql.buffers.profile;
8 :
9 : /// The effective state of an entity of a plan (a `ProfileUpdateEvent` of the core)
10 : export interface ProfileEntityState {
11 : readonly status: dashql.buffers.profile.ProfileExecutionStatus;
12 : readonly stats: ProfileStatsT;
13 : }
14 :
15 : /// Is a statistics field known? A field that is not reported is unknown, not 0.
16 1 : export function isStatReported(stats: ProfileStatsT, field: dashql.buffers.profile.ProfileStatsField): boolean {
17 1 : return (stats.reported & field) !== 0;
18 1 : }
19 :
20 : type States = ReadonlyArray<ProfileEntityState | undefined>;
21 :
22 : /// The execution state of a plan: the effective state of every entity, folded from the output events of the core
23 : /// query profile (`applyEvents`, `snapshotEvents`). Folding returns a new state, so React can compare states.
24 1 : export class ProfileExecutionState {
25 1 : public static readonly EMPTY = new ProfileExecutionState(null, [], [], [], []);
26 :
27 1 : public constructor(
28 1 : public readonly query: ProfileEntityState | null,
29 1 : public readonly fragments: States,
30 1 : public readonly pipelines: States,
31 1 : public readonly slots: States,
32 1 : public readonly operators: States,
33 1 : ) {}
34 :
35 : /// Fold output events into a new state
36 1 : public withEvents(events: QueryProfileEvents): ProfileExecutionState {
37 1 : if (events.eventsLength() === 0) return this;
38 1 : let query = this.query;
39 1 : const fragments = [...this.fragments];
40 1 : const pipelines = [...this.pipelines];
41 1 : const slots = [...this.slots];
42 1 : const operators = [...this.operators];
43 1 : const event = new view.ProfileUpdateEvent();
44 1 : for (let i = 0; i < events.eventsLength(); ++i) {
45 1 : events.events(i, event);
46 1 : const state: ProfileEntityState = { status: event.status(), stats: event.stats()!.unpack() };
47 1 : switch (event.entity()) {
48 1 : case view.ProfileEntity.QUERY: query = state; break;
49 1 : case view.ProfileEntity.FRAGMENT: fragments[event.index()] = state; break;
50 1 : case view.ProfileEntity.PIPELINE: pipelines[event.index()] = state; break;
51 1 : case view.ProfileEntity.SLOT: slots[event.index()] = state; break;
52 1 : case view.ProfileEntity.OPERATOR: operators[event.index()] = state; break;
53 1 : }
54 1 : }
55 1 : return new ProfileExecutionState(query, fragments, pipelines, slots, operators);
56 1 : }
57 :
58 : /// The rows that flowed over an edge: the output of the slot of the child that carries the edge, or the output
59 : /// of the child operator if the slot reports none. Null if unknown.
60 1 : public edgeRows(slot: number, childOperator: number): bigint | null {
61 1 : const reported = (state: ProfileEntityState | undefined) =>
62 1 : state != null && isStatReported(state.stats, view.ProfileStatsField.TUPLES_OUT) ? state.stats.tuplesOut : null;
63 1 : return (slot !== NO_INDEX ? reported(this.slots[slot]) : null) ?? reported(this.operators[childOperator]);
64 1 : }
65 1 : }
|