Skip to content

Commit ffed4b6

Browse files
authored
Merge pull request #3843 from asgerf/asgerf/compare-perf-view
Add 'compare performance' view
2 parents 1338935 + 666c26e commit ffed4b6

File tree

21 files changed

+1776
-2
lines changed

21 files changed

+1776
-2
lines changed

extensions/ql-vscode/package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,10 @@
942942
"command": "codeQLQueryHistory.compareWith",
943943
"title": "Compare Results"
944944
},
945+
{
946+
"command": "codeQLQueryHistory.comparePerformanceWith",
947+
"title": "Compare Performance"
948+
},
945949
{
946950
"command": "codeQLQueryHistory.openOnGithub",
947951
"title": "View Logs"
@@ -1213,6 +1217,11 @@
12131217
"group": "3_queryHistory@0",
12141218
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem"
12151219
},
1220+
{
1221+
"command": "codeQLQueryHistory.comparePerformanceWith",
1222+
"group": "3_queryHistory@1",
1223+
"when": "viewItem == rawResultsItem && config.codeQL.canary || viewItem == interpretedResultsItem && config.codeQL.canary"
1224+
},
12161225
{
12171226
"command": "codeQLQueryHistory.showQueryLog",
12181227
"group": "4_queryHistory@4",
@@ -1716,6 +1725,10 @@
17161725
"command": "codeQLQueryHistory.compareWith",
17171726
"when": "false"
17181727
},
1728+
{
1729+
"command": "codeQLQueryHistory.comparePerformanceWith",
1730+
"when": "false"
1731+
},
17191732
{
17201733
"command": "codeQLQueryHistory.sortByName",
17211734
"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: 12 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,17 @@ 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+
readonly comparison: boolean;
407+
}
408+
409+
export type FromComparePerformanceViewMessage = CommonFromViewMessages;
410+
399411
export type QueryCompareResult =
400412
| RawQueryCompareResult
401413
| 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: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { statSync } from "fs";
2+
import { ViewColumn } from "vscode";
3+
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 { withProgress } from "../common/vscode/progress";
16+
import { telemetryListener } from "../common/vscode/telemetry";
17+
import type { HistoryItemLabelProvider } from "../query-history/history-item-label-provider";
18+
import { PerformanceOverviewScanner } from "../log-insights/performance-comparison";
19+
import { scanLog } from "../log-insights/log-scanner";
20+
import type { ResultsView } from "../local-queries";
21+
22+
export class ComparePerformanceView extends AbstractWebview<
23+
ToComparePerformanceViewMessage,
24+
FromComparePerformanceViewMessage
25+
> {
26+
constructor(
27+
app: App,
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+
function scanLogWithProgress(log: string, logDescription: string) {
47+
const bytes = statSync(log).size;
48+
return withProgress(
49+
async (progress) =>
50+
scanLog(log, new PerformanceOverviewScanner(), progress),
51+
52+
{
53+
title: `Scanning evaluator log ${logDescription} (${(bytes / 1024 / 1024).toFixed(1)} MB)`,
54+
},
55+
);
56+
}
57+
58+
const [fromPerf, toPerf] = await Promise.all([
59+
fromJsonLog === ""
60+
? new PerformanceOverviewScanner()
61+
: scanLogWithProgress(fromJsonLog, "1/2"),
62+
scanLogWithProgress(toJsonLog, fromJsonLog === "" ? "1/1" : "2/2"),
63+
]);
64+
65+
await this.postMessage({
66+
t: "setPerformanceComparison",
67+
from: fromPerf.getData(),
68+
to: toPerf.getData(),
69+
comparison: fromJsonLog !== "",
70+
});
71+
}
72+
73+
protected getPanelConfig(): WebviewPanelConfig {
74+
return {
75+
viewId: "comparePerformanceView",
76+
title: "Compare CodeQL Performance",
77+
viewColumn: ViewColumn.Active,
78+
preserveFocus: true,
79+
view: "compare-performance",
80+
};
81+
}
82+
83+
protected onPanelDispose(): void {}
84+
85+
protected async onMessage(
86+
msg: FromComparePerformanceViewMessage,
87+
): Promise<void> {
88+
switch (msg.t) {
89+
case "viewLoaded":
90+
this.onWebViewLoaded();
91+
break;
92+
93+
case "telemetry":
94+
telemetryListener?.sendUIInteraction(msg.action);
95+
break;
96+
97+
case "unhandledError":
98+
void showAndLogExceptionWithTelemetry(
99+
extLogger,
100+
telemetryListener,
101+
redactableError(
102+
msg.error,
103+
)`Unhandled error in performance comparison view: ${msg.error.message}`,
104+
);
105+
break;
106+
}
107+
}
108+
}

extensions/ql-vscode/src/extension.ts

Lines changed: 39 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
@@ -928,6 +929,11 @@ async function activateWithInstalledDistribution(
928929
from: CompletedLocalQueryInfo,
929930
to: CompletedLocalQueryInfo,
930931
): Promise<void> => showResultsForComparison(compareView, from, to),
932+
async (
933+
from: CompletedLocalQueryInfo,
934+
to: CompletedLocalQueryInfo | undefined,
935+
): Promise<void> =>
936+
showPerformanceComparison(comparePerformanceView, from, to),
931937
);
932938

933939
ctx.subscriptions.push(qhm);
@@ -953,6 +959,15 @@ async function activateWithInstalledDistribution(
953959
);
954960
ctx.subscriptions.push(compareView);
955961

962+
void extLogger.log("Initializing performance comparison view.");
963+
const comparePerformanceView = new ComparePerformanceView(
964+
app,
965+
queryServerLogger,
966+
labelProvider,
967+
localQueryResultsView,
968+
);
969+
ctx.subscriptions.push(comparePerformanceView);
970+
956971
void extLogger.log("Initializing source archive filesystem provider.");
957972
archiveFilesystemProvider_activate(ctx, dbm);
958973

@@ -1191,6 +1206,30 @@ async function showResultsForComparison(
11911206
}
11921207
}
11931208

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

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import type { SummaryEvent } from "./log-summary";
2-
import { readJsonlFile } from "../common/jsonl-reader";
31
import type { Disposable } from "../common/disposable-object";
2+
import { readJsonlFile } from "../common/jsonl-reader";
3+
import type { ProgressCallback } from "../common/vscode/progress";
4+
import type { SummaryEvent } from "./log-summary";
45

56
/**
67
* Callback interface used to report diagnostics from a log scanner.
@@ -112,3 +113,27 @@ export class EvaluationLogScannerSet {
112113
scanners.forEach((scanner) => scanner.onDone());
113114
}
114115
}
116+
117+
/**
118+
* Scan the evaluator summary log using the given scanner. For convenience, returns the scanner.
119+
*
120+
* @param jsonSummaryLocation The file path of the JSON summary log.
121+
* @param scanner The scanner to process events from the log
122+
*/
123+
export async function scanLog<T extends EvaluationLogScanner>(
124+
jsonSummaryLocation: string,
125+
scanner: T,
126+
progress?: ProgressCallback,
127+
): Promise<T> {
128+
progress?.({
129+
// all scans have step 1 - the backing progress tracker allows increments instead of steps - but for now we are happy with a tiny UI that says what is happening
130+
message: `Scanning ...`,
131+
step: 1,
132+
maxStep: 2,
133+
});
134+
await readJsonlFile<SummaryEvent>(jsonSummaryLocation, async (obj) => {
135+
scanner.onEvent(obj);
136+
});
137+
scanner.onDone();
138+
return scanner;
139+
}

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)