Skip to content

experiment(ui): diff UI POC#183

Closed
johallar wants to merge 33 commits into
rapidsai:mainfrom
johallar:workload-diff-ui
Closed

experiment(ui): diff UI POC#183
johallar wants to merge 33 commits into
rapidsai:mainfrom
johallar:workload-diff-ui

Conversation

@johallar

@johallar johallar commented May 19, 2026

Copy link
Copy Markdown
Contributor

Building a mock API + UI to iterate on diff API

V0 API types:
https://github.com/johallar/quent/blob/8f986c3536068a47fde938fa841282edefdbc14f/ui/packages/%40quent/client/src/queryProfileDiffTypes.ts

Screenshot 2026-05-19 at 1 43 52 PM

Feel pretty good about the modularization so far, this uses the pivot table, radix, and timeline components to implement the diff view and the biggest component so far is the diff page's layout:

510  +510 -0  ui/src/pages/DiffSelectionPage.tsx
414  +414 -0  ui/src/components/query-diff/QueryDiffTimeline.tsx
227  +227 -0  ui/src/components/query-diff/QueryDiffTable.tsx
211  +211 -0  ui/packages/@quent/components/src/stat-card/StatisticCard.tsx
169  +169 -0  ui/src/components/query-diff/queryProfileDiffFromBundles.ts
152  +152 -0  ui/src/components/query-diff/QueryDiffStats.tsx
135  +135 -0  ui/src/components/query-diff/QueryDiffTimeline.utils.ts
 87   +87 -0  ui/src/components/query-diff/QueryDiffTable.utils.ts
 78   +78 -0  ui/src/components/query-diff/QueryDiffStats.utils.ts
 75   +75 -0  ui/packages/@quent/client/src/queryProfileDiffTypes.ts
 69   +69 -0  ui/packages/@quent/client/src/queryProfileDiff.ts
 45   +45 -0  ui/src/components/query-diff/QueryDiffColors.ts
 40   +33 -7  ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx
 30   +30 -0  ui/packages/@quent/client/src/api.ts
 28   +28 -0  ui/src/index.css
 21   +21 -0  ui/packages/@quent/client/src/index.ts
 20   +20 -0  ui/src/routes/diff.engine.$engineId.query.$queryAId.compare.$queryBId.tsx
 19   +19 -0  ui/packages/@quent/components/src/pivot-table/types.ts
 14   +14 -0  ui/src/routes/__root.tsx
 13   +13 -0  ui/src/routes/diff.index.tsx
 12   +12 -0  ui/src/routes/diff.tsx
 11   +11 -0  ui/packages/@quent/components/src/index.ts

query_b_id: string;
}

export type QueryProfileDiffScenario = 'plans_equal' | 'plans_different' | 'plans_incomparable';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to define what makes two plans "incomparable" vs "different" (e.g. different SQL text? different engines?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this will definitely need some figuring, It's also likely we'll need this flag on various things (eg. query plan, resource tree), so should probably make this a more generic "can these things be compared" flag or something


export type QueryProfileDiffScenario = 'plans_equal' | 'plans_different' | 'plans_incomparable';

export interface QueryProfileDiffQuerySummary {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add:

  duration_s: number; // rather than deriving it from the query bundle

plan_id: string | null;
}

export interface QueryProfileDiffStatDelta {

@cmatzenbach cmatzenbach May 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider adding a FE-only derived type that wraps this with a direction field ('better' | 'worse' | 'neutral'). The direction is already being re-derived from the delta in multiple places (QueryDiffTable.utils, QueryDiffStats.utils)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could add some semantic value... i'm thinking if the analyzer can infer something that the FE can't (maybe a higher value is better for specific stats?) then this could be very useful.


export interface QueryProfileDiffPlanComparison {
/* Big question here, how do we represent query plan graph diffs */
match_kind: 'structural' | 'different' | 'incomparable';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

match_kind duplicates information already in QueryProfileDiffScenario - if scenario is 'plans_equal', match_kind is always 'structural', etc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, plan to remove this for now

stats: Record<string, QueryProfileDiffStatDelta>;
}

export interface QueryProfileDiffPlanComparison {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend could do graph-based matching instead - actually traversing the plan graph and pairing operators by their role and connections, not just their order. This would solve plans not being structurally identical. Then we could add match_strategy: 'positional' | 'graph' to record how operators were paired. As we will have the backend doing matching, the UI needs to know which was used

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely, this type will be the result of the graph isomorphism problem. The whole type will change once we have more of that strategy figured out I assume.

unmatched_operator_b_count: number;
}

export interface QueryProfileDiffResponse {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add:

shared_resource_types: string[]; // covers the timeline resource type dropdown, server already knows which resource types are shared


export type QueryProfileDiffTimelineEntries<T> = [T, T, ...T[]];

export interface QueryProfileDiffTimelineRequest {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we are having the FE build two SingleTImelineRequest objects and pass them up; however, the server already has all this info. Instead we should treat this like we do QueryProfileDiffRequest, here's my suggestion for the revised type once we have the API compute everything:

export interface QueryProfileDiffTimelineRequest {
  query_a_id: string;
  query_b_id: string;
  resource_type: string;
  config: TimelineConfig;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to handle n queries in the comparison requests, so should use the same strategy (whatever is landed on) as QueryProfileDiffTimelineRequest, otherwise agree

delta_config: TimelineConfig;
}

export interface QueryProfileDiffTimelineResponse {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here would be the response proposed for the request above:

export interface QueryProfileDiffTimelineResponse {
  timeline_a: SingleTimelineResponse;
  timeline_b: SingleTimelineResponse;
  delta: SingleTimelineResponse;
  warnings?: string[];
}

} from '@quent/utils';

export interface QueryProfileDiffRequest {
query_a_id: string;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be either an array (with the assumption that the first item is the "baseline"), or have explicit baseline and competitors (or similarly named) properties

}

export interface DiffRequest {
// Alternatively make this one array, assume first item is baseline

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thought: We talked about comparing query groups (eg. compare tpch runs from 2 commits), we should think that through and update this to accommodate. Not sure about comparing engines, but probably should handle that as well.

@johallar johallar added the DO NOT MERGE Hold off on merging; see PR for details label Jun 5, 2026
@johallar johallar changed the title experiment(ui): diff API experiment(ui): diff UI POC Jun 12, 2026
@johallar

Copy link
Copy Markdown
Contributor Author

POC did its job, replaced by johallar:workload-diff-ui-stage-1 and #193

@johallar johallar closed this Jun 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DO NOT MERGE Hold off on merging; see PR for details

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants