Skip to content

Commit 3b06977

Browse files
committed
Add Compare Performance command (WIP)
1 parent 60c15a0 commit 3b06977

20 files changed

Lines changed: 1038 additions & 0 deletions

extensions/ql-vscode/package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,10 @@
959959
"command": "codeQLQueryHistory.compareWith",
960960
"title": "Compare Results"
961961
},
962+
{
963+
"command": "codeQLQueryHistory.comparePerformanceWith",
964+
"title": "Compare Performance"
965+
},
962966
{
963967
"command": "codeQLQueryHistory.openOnGithub",
964968
"title": "View Logs"
@@ -1230,6 +1234,11 @@
12301234
"group": "3_queryHistory@0",
12311235
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem"
12321236
},
1237+
{
1238+
"command": "codeQLQueryHistory.comparePerformanceWith",
1239+
"group": "3_queryHistory@1",
1240+
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem"
1241+
},
12331242
{
12341243
"command": "codeQLQueryHistory.showQueryLog",
12351244
"group": "4_queryHistory@4",
@@ -1733,6 +1742,10 @@
17331742
"command": "codeQLQueryHistory.compareWith",
17341743
"when": "false"
17351744
},
1745+
{
1746+
"command": "codeQLQueryHistory.comparePerformanceWith",
1747+
"when": "false"
1748+
},
17361749
{
17371750
"command": "codeQLQueryHistory.sortByName",
17381751
"when": "false"

extensions/ql-vscode/src/common/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export type QueryHistoryCommands = {
180180
"codeQLQueryHistory.removeHistoryItemContextInline": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
181181
"codeQLQueryHistory.renameItem": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
182182
"codeQLQueryHistory.compareWith": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
183+
"codeQLQueryHistory.comparePerformanceWith": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
183184
"codeQLQueryHistory.showEvalLog": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
184185
"codeQLQueryHistory.showEvalLogSummary": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;
185186
"codeQLQueryHistory.showEvalLogViewer": TreeViewContextMultiSelectionCommandFunction<QueryHistoryInfo>;

extensions/ql-vscode/src/common/interface-types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import type {
2727
} from "./raw-result-types";
2828
import type { AccessPathSuggestionOptions } from "../model-editor/suggestions";
2929
import type { ModelEvaluationRunState } from "../model-editor/shared/model-evaluation-run-state";
30+
import type { PerformanceComparisonDataFromLog } from "../log-insights/performance-comparison";
3031

3132
/**
3233
* This module contains types and code that are shared between
@@ -396,6 +397,16 @@ export interface SetComparisonsMessage {
396397
readonly message: string | undefined;
397398
}
398399

400+
export type ToComparePerformanceViewMessage = SetPerformanceComparisonQueries;
401+
402+
export interface SetPerformanceComparisonQueries {
403+
readonly t: "setPerformanceComparison";
404+
readonly from: PerformanceComparisonDataFromLog;
405+
readonly to: PerformanceComparisonDataFromLog;
406+
}
407+
408+
export type FromComparePerformanceViewMessage = CommonFromViewMessages;
409+
399410
export type QueryCompareResult =
400411
| RawQueryCompareResult
401412
| InterpretedQueryCompareResult;

extensions/ql-vscode/src/common/vscode/abstract-webview.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ export abstract class AbstractWebview<
4141

4242
constructor(protected readonly app: App) {}
4343

44+
public hidePanel() {
45+
if (this.panel !== undefined) {
46+
this.panel.dispose();
47+
this.panel = undefined;
48+
}
49+
}
50+
4451
public async restoreView(panel: WebviewPanel): Promise<void> {
4552
this.panel = panel;
4653
const config = await this.getPanelConfig();

extensions/ql-vscode/src/common/vscode/webview-html.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { App } from "../app";
77
export type WebviewKind =
88
| "results"
99
| "compare"
10+
| "compare-performance"
1011
| "variant-analysis"
1112
| "data-flow-paths"
1213
| "model-editor"
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { ViewColumn } from "vscode";
2+
3+
import type { App } from "../common/app";
4+
import { redactableError } from "../common/errors";
5+
import type {
6+
FromComparePerformanceViewMessage,
7+
ToComparePerformanceViewMessage,
8+
} from "../common/interface-types";
9+
import type { Logger } from "../common/logging";
10+
import { showAndLogExceptionWithTelemetry } from "../common/logging";
11+
import { extLogger } from "../common/logging/vscode";
12+
import type { WebviewPanelConfig } from "../common/vscode/abstract-webview";
13+
import { AbstractWebview } from "../common/vscode/abstract-webview";
14+
import { telemetryListener } from "../common/vscode/telemetry";
15+
import type { HistoryItemLabelProvider } from "../query-history/history-item-label-provider";
16+
import { PerformanceOverviewScanner } from "../log-insights/performance-comparison";
17+
import { scanLog } from "../log-insights/log-scanner";
18+
import type { ResultsView } from "../local-queries";
19+
20+
export class ComparePerformanceView extends AbstractWebview<
21+
ToComparePerformanceViewMessage,
22+
FromComparePerformanceViewMessage
23+
> {
24+
constructor(
25+
app: App,
26+
public logger: Logger,
27+
public labelProvider: HistoryItemLabelProvider,
28+
private resultsView: ResultsView,
29+
) {
30+
super(app);
31+
}
32+
33+
async showResults(fromJsonLog: string, toJsonLog: string) {
34+
const panel = await this.getPanel();
35+
panel.reveal(undefined, false);
36+
37+
// Close the results viewer as it will have opened when the user clicked the query in the history view
38+
// (which they must do as part of the UI interaction for opening the performance view).
39+
// The performance view generally needs a lot of width so it's annoying to have the result viewer open.
40+
this.resultsView.hidePanel();
41+
42+
await this.waitForPanelLoaded();
43+
44+
// TODO: try processing in (async) parallel once readJsonl is streaming
45+
const fromPerf = await scanLog(
46+
fromJsonLog,
47+
new PerformanceOverviewScanner(),
48+
);
49+
const toPerf = await scanLog(toJsonLog, new PerformanceOverviewScanner());
50+
51+
await this.postMessage({
52+
t: "setPerformanceComparison",
53+
from: fromPerf.getData(),
54+
to: toPerf.getData(),
55+
});
56+
}
57+
58+
protected getPanelConfig(): WebviewPanelConfig {
59+
return {
60+
viewId: "comparePerformanceView",
61+
title: "Compare CodeQL Performance",
62+
viewColumn: ViewColumn.Active,
63+
preserveFocus: true,
64+
view: "compare-performance",
65+
};
66+
}
67+
68+
protected onPanelDispose(): void {}
69+
70+
protected async onMessage(
71+
msg: FromComparePerformanceViewMessage,
72+
): Promise<void> {
73+
switch (msg.t) {
74+
case "viewLoaded":
75+
this.onWebViewLoaded();
76+
break;
77+
78+
case "telemetry":
79+
telemetryListener?.sendUIInteraction(msg.action);
80+
break;
81+
82+
case "unhandledError":
83+
void showAndLogExceptionWithTelemetry(
84+
extLogger,
85+
telemetryListener,
86+
redactableError(
87+
msg.error,
88+
)`Unhandled error in performance comparison view: ${msg.error.message}`,
89+
);
90+
break;
91+
}
92+
}
93+
}

extensions/ql-vscode/src/extension.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ import { LanguageContextStore } from "./language-context-store";
135135
import { LanguageSelectionPanel } from "./language-selection-panel/language-selection-panel";
136136
import { GitHubDatabasesModule } from "./databases/github-databases";
137137
import { DatabaseFetcher } from "./databases/database-fetcher";
138+
import { ComparePerformanceView } from "./compare-performance/compare-performance-view";
138139

139140
/**
140141
* extension.ts
@@ -924,6 +925,11 @@ async function activateWithInstalledDistribution(
924925
from: CompletedLocalQueryInfo,
925926
to: CompletedLocalQueryInfo,
926927
): Promise<void> => showResultsForComparison(compareView, from, to),
928+
async (
929+
from: CompletedLocalQueryInfo,
930+
to: CompletedLocalQueryInfo,
931+
): Promise<void> =>
932+
showPerformanceComparison(comparePerformanceView, from, to),
927933
);
928934

929935
ctx.subscriptions.push(qhm);
@@ -949,6 +955,15 @@ async function activateWithInstalledDistribution(
949955
);
950956
ctx.subscriptions.push(compareView);
951957

958+
void extLogger.log("Initializing performance comparison view.");
959+
const comparePerformanceView = new ComparePerformanceView(
960+
app,
961+
queryServerLogger,
962+
labelProvider,
963+
localQueryResultsView,
964+
);
965+
ctx.subscriptions.push(comparePerformanceView);
966+
952967
void extLogger.log("Initializing source archive filesystem provider.");
953968
archiveFilesystemProvider_activate(ctx, dbm);
954969

@@ -1190,6 +1205,25 @@ async function showResultsForComparison(
11901205
}
11911206
}
11921207

1208+
async function showPerformanceComparison(
1209+
view: ComparePerformanceView,
1210+
from: CompletedLocalQueryInfo,
1211+
to: CompletedLocalQueryInfo,
1212+
): Promise<void> {
1213+
const fromLog = from.evaluatorLogPaths?.jsonSummary;
1214+
const toLog = to.evaluatorLogPaths?.jsonSummary;
1215+
if (fromLog === undefined || toLog === undefined) {
1216+
return extLogger.showWarningMessage(
1217+
`Cannot compare performance as the structured logs are missing. Did they queries complete normally?`,
1218+
);
1219+
}
1220+
await extLogger.log(
1221+
`Comparing performance of ${from.getQueryName()} and ${to.getQueryName()}`,
1222+
);
1223+
1224+
await view.showResults(fromLog, toLog);
1225+
}
1226+
11931227
function addUnhandledRejectionListener() {
11941228
const handler = (error: unknown) => {
11951229
// This listener will be triggered for errors from other extensions as

extensions/ql-vscode/src/log-insights/log-scanner.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,20 @@ export class EvaluationLogScannerSet {
112112
scanners.forEach((scanner) => scanner.onDone());
113113
}
114114
}
115+
116+
/**
117+
* Scan the evaluator summary log using the given scanner. For conveience, returns the scanner.
118+
*
119+
* @param jsonSummaryLocation The file path of the JSON summary log.
120+
* @param scanner The scanner to process events from the log
121+
*/
122+
export async function scanLog<T extends EvaluationLogScanner>(
123+
jsonSummaryLocation: string,
124+
scanner: T,
125+
): Promise<T> {
126+
await readJsonlFile<SummaryEvent>(jsonSummaryLocation, async (obj) => {
127+
scanner.onEvent(obj);
128+
});
129+
scanner.onDone();
130+
return scanner;
131+
}

extensions/ql-vscode/src/log-insights/log-summary.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ interface ResultEventBase extends SummaryEventBase {
3333
export interface ComputeSimple extends ResultEventBase {
3434
evaluationStrategy: "COMPUTE_SIMPLE";
3535
ra: Ra;
36+
millis: number;
3637
pipelineRuns?: [PipelineRun];
3738
queryCausingWork?: string;
3839
dependencies: { [key: string]: string };
@@ -42,6 +43,7 @@ export interface ComputeRecursive extends ResultEventBase {
4243
evaluationStrategy: "COMPUTE_RECURSIVE";
4344
deltaSizes: number[];
4445
ra: Ra;
46+
millis: number;
4547
pipelineRuns: PipelineRun[];
4648
queryCausingWork?: string;
4749
dependencies: { [key: string]: string };

0 commit comments

Comments
 (0)