Skip to content

Commit c4578a9

Browse files
committed
Add Compare Performance command (WIP)
1 parent 6b5955d commit c4578a9

File tree

16 files changed

+1027
-0
lines changed

16 files changed

+1027
-0
lines changed

extensions/ql-vscode/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,10 @@
955955
"command": "codeQLQueryHistory.compareWith",
956956
"title": "Compare Results"
957957
},
958+
{
959+
"command": "codeQLQueryHistory.comparePerformanceWith",
960+
"title": "Compare Performance"
961+
},
958962
{
959963
"command": "codeQLQueryHistory.openOnGithub",
960964
"title": "View Logs"
@@ -1226,6 +1230,11 @@
12261230
"group": "3_queryHistory@0",
12271231
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem"
12281232
},
1233+
{
1234+
"command": "codeQLQueryHistory.comparePerformanceWith",
1235+
"group": "3_queryHistory@1",
1236+
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem"
1237+
},
12291238
{
12301239
"command": "codeQLQueryHistory.showQueryLog",
12311240
"group": "4_queryHistory@4",

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

extensions/ql-vscode/src/extension.ts

Lines changed: 35 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,16 @@ 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+
cliServer,
962+
queryServerLogger,
963+
labelProvider,
964+
localQueryResultsView,
965+
);
966+
ctx.subscriptions.push(comparePerformanceView);
967+
952968
void extLogger.log("Initializing source archive filesystem provider.");
953969
archiveFilesystemProvider_activate(ctx, dbm);
954970

@@ -1190,6 +1206,25 @@ async function showResultsForComparison(
11901206
}
11911207
}
11921208

1209+
async function showPerformanceComparison(
1210+
view: ComparePerformanceView,
1211+
from: CompletedLocalQueryInfo,
1212+
to: CompletedLocalQueryInfo,
1213+
): Promise<void> {
1214+
const fromLog = from.evalutorLogPaths?.jsonSummary;
1215+
const toLog = to.evalutorLogPaths?.jsonSummary;
1216+
if (fromLog === undefined || toLog === undefined) {
1217+
return extLogger.showWarningMessage(
1218+
`Cannot compare performance as the structured logs are missing. Did they queries complete normally?`,
1219+
);
1220+
}
1221+
await extLogger.log(
1222+
`Comparing performance of ${from.getQueryName()} and ${to.getQueryName()}`,
1223+
);
1224+
1225+
await view.showResults(fromLog, toLog);
1226+
}
1227+
11931228
function addUnhandledRejectionListener() {
11941229
const handler = (error: unknown) => {
11951230
// 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+
}

0 commit comments

Comments
 (0)