Skip to content

Commit 40e3944

Browse files
Expose per-query structured evaluator logs
1 parent b510b85 commit 40e3944

File tree

8 files changed

+194
-3
lines changed

8 files changed

+194
-3
lines changed

extensions/ql-vscode/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,14 @@
512512
"command": "codeQLQueryHistory.openQueryDirectory",
513513
"title": "Open query directory"
514514
},
515+
{
516+
"command": "codeQLQueryHistory.showEvalLog",
517+
"title": "Show Evaluator Log (Raw)"
518+
},
519+
{
520+
"command": "codeQLQueryHistory.showEvalLogSummary",
521+
"title": "Show Evaluator Log (Summary)"
522+
},
515523
{
516524
"command": "codeQLQueryHistory.cancel",
517525
"title": "Cancel"
@@ -706,6 +714,16 @@
706714
"group": "9_qlCommands",
707715
"when": "view == codeQLQueryHistory && !hasRemoteServer"
708716
},
717+
{
718+
"command": "codeQLQueryHistory.showEvalLog",
719+
"group": "9_qlCommands",
720+
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem || viewItem == cancelledResultsItem"
721+
},
722+
{
723+
"command": "codeQLQueryHistory.showEvalLogSummary",
724+
"group": "9_qlCommands",
725+
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem || viewItem == cancelledResultsItem"
726+
},
709727
{
710728
"command": "codeQLQueryHistory.showQueryText",
711729
"group": "9_qlCommands",
@@ -896,6 +914,14 @@
896914
"command": "codeQLQueryHistory.showQueryLog",
897915
"when": "false"
898916
},
917+
{
918+
"command": "codeQLQueryHistory.showEvalLog",
919+
"when": "false"
920+
},
921+
{
922+
"command": "codeQLQueryHistory.showEvalLogSummary",
923+
"when": "false"
924+
},
899925
{
900926
"command": "codeQLQueryHistory.openQueryDirectory",
901927
"when": "false"

extensions/ql-vscode/src/cli.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,23 @@ export class CodeQLCliServer implements Disposable {
665665
return await this.runCodeQlCliCommand(['generate', 'query-help'], subcommandArgs, `Generating qhelp in markdown format at ${outputDirectory}`);
666666
}
667667

668+
/**
669+
* Generate a summary of an evaluation log.
670+
* @param inputPath The path of an evaluation event log.
671+
* @param outputPath The path to write a human-readable summary of it to.
672+
*/
673+
async generateLogSummary(
674+
inputPath: string,
675+
outputPath: string,
676+
): Promise<string> {
677+
const subcommandArgs = [
678+
'--format=text',
679+
inputPath,
680+
outputPath
681+
];
682+
return await this.runCodeQlCliCommand(['generate', 'log-summary'], subcommandArgs, 'Generating log summary');
683+
}
684+
668685
/**
669686
* Gets the results from a bqrs.
670687
* @param bqrsPath The path to the bqrs.
@@ -1256,6 +1273,11 @@ export class CliVersionConstraint {
12561273
*/
12571274
public static CLI_VERSION_WITH_STRUCTURED_EVAL_LOG = new SemVer('2.8.2');
12581275

1276+
/**
1277+
* CLI version that supports rotating structured logs to produce one per query.
1278+
*/
1279+
public static CLI_VERSION_WITH_PER_QUERY_EVAL_LOG = new SemVer('2.8.4');
1280+
12591281
constructor(private readonly cli: CodeQLCliServer) {
12601282
/**/
12611283
}
@@ -1315,4 +1337,8 @@ export class CliVersionConstraint {
13151337
async supportsStructuredEvalLog() {
13161338
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_STRUCTURED_EVAL_LOG);
13171339
}
1340+
1341+
async supportsPerQueryEvalLog() {
1342+
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_PER_QUERY_EVAL_LOG);
1343+
}
13181344
}

extensions/ql-vscode/src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,8 @@ async function activateWithInstalledDistribution(
537537
queryStorageDir,
538538
progress,
539539
source.token,
540+
undefined,
541+
item,
540542
);
541543
item.completeThisQuery(completedQueryInfo);
542544
await showResultsForCompletedQuery(item as CompletedLocalQueryInfo, WebviewReveal.NotForced);

extensions/ql-vscode/src/pure/messages.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,35 @@ export interface ClearCacheParams {
646646
*/
647647
dryRun: boolean;
648648
}
649+
650+
/**
651+
* Parameters to start a new structured log
652+
*/
653+
export interface StartLogParams {
654+
/**
655+
* The dataset for which we want to start a new structured log
656+
*/
657+
db: Dataset;
658+
/**
659+
* The path where we want to place the new structured log
660+
*/
661+
logPath: string;
662+
}
663+
664+
/**
665+
* Parameters to terminate a structured log
666+
*/
667+
export interface EndLogParams {
668+
/**
669+
* The dataset for which we want to terminated the log
670+
*/
671+
db: Dataset;
672+
/**
673+
* The path of the log to terminate, will be a no-op if we aren't logging here
674+
*/
675+
logPath: string;
676+
}
677+
649678
/**
650679
* Parameters for trimming the cache of a dataset
651680
*/
@@ -682,6 +711,26 @@ export interface ClearCacheResult {
682711
deletionMessage: string;
683712
}
684713

714+
/**
715+
* The result of starting a new structured log.
716+
*/
717+
export interface StartLogResult {
718+
/**
719+
* A user friendly message saying what happened.
720+
*/
721+
outcomeMessage: string;
722+
}
723+
724+
/**
725+
* The result of terminating a structured.
726+
*/
727+
export interface EndLogResult {
728+
/**
729+
* A user friendly message saying what happened.
730+
*/
731+
outcomeMessage: string;
732+
}
733+
685734
/**
686735
* Parameters for running a set of queries
687736
*/
@@ -1018,6 +1067,16 @@ export const compileUpgrade = new rpc.RequestType<WithProgressId<CompileUpgradeP
10181067
*/
10191068
export const compileUpgradeSequence = new rpc.RequestType<WithProgressId<CompileUpgradeSequenceParams>, CompileUpgradeSequenceResult, void, void>('compilation/compileUpgradeSequence');
10201069

1070+
/**
1071+
* Start a new structured log in the evaluator, terminating the previous one if it exists
1072+
*/
1073+
export const startLog = new rpc.RequestType<WithProgressId<StartLogParams>, StartLogResult, void, void>('evaluation/startLog');
1074+
1075+
/**
1076+
* Terminate a structured log in the evaluator. Is a no-op if we aren't logging to the given location
1077+
*/
1078+
export const endLog = new rpc.RequestType<WithProgressId<EndLogParams>, EndLogResult, void, void>('evaluation/endLog');
1079+
10211080
/**
10221081
* Clear the cache of a dataset
10231082
*/

extensions/ql-vscode/src/query-history.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import { DatabaseManager } from './databases';
3434
import { registerQueryHistoryScubber } from './query-history-scrubber';
3535
import { QueryStatus } from './query-status';
3636
import { slurpQueryHistory, splatQueryHistory } from './query-serialization';
37+
import * as fs from 'fs-extra';
38+
import { CliVersionConstraint } from './cli';
3739

3840
/**
3941
* query-history.ts
@@ -406,6 +408,18 @@ export class QueryHistoryManager extends DisposableObject {
406408
this.handleOpenQueryDirectory.bind(this)
407409
)
408410
);
411+
this.push(
412+
commandRunner(
413+
'codeQLQueryHistory.showEvalLog',
414+
this.handleShowEvalLog.bind(this)
415+
)
416+
);
417+
this.push(
418+
commandRunner(
419+
'codeQLQueryHistory.showEvalLogSummary',
420+
this.handleShowEvalLogSummary.bind(this)
421+
)
422+
);
409423
this.push(
410424
commandRunner(
411425
'codeQLQueryHistory.cancel',
@@ -736,6 +750,46 @@ export class QueryHistoryManager extends DisposableObject {
736750
}
737751
}
738752
}
753+
754+
private warnNoEvalLog() {
755+
void showAndLogWarningMessage('No evaluator log is available for this run. Perhaps it failed before evaluation, or you are running with a version of CodeQL before ' + CliVersionConstraint.CLI_VERSION_WITH_PER_QUERY_EVAL_LOG + '?');
756+
}
757+
758+
async handleShowEvalLog(
759+
singleItem: QueryHistoryInfo,
760+
multiSelect: QueryHistoryInfo[]
761+
) {
762+
// Local queries only
763+
if (!this.assertSingleQuery(multiSelect) || singleItem?.t !== 'local') {
764+
return;
765+
}
766+
767+
if (singleItem.evalLogLocation) {
768+
await this.tryOpenExternalFile(singleItem.evalLogLocation);
769+
} else {
770+
this.warnNoEvalLog();
771+
}
772+
}
773+
774+
async handleShowEvalLogSummary(
775+
singleItem: QueryHistoryInfo,
776+
multiSelect: QueryHistoryInfo[]
777+
) {
778+
// Local queries only
779+
if (!this.assertSingleQuery(multiSelect) || singleItem?.t !== 'local') {
780+
return;
781+
}
782+
783+
if (singleItem.evalLogLocation) {
784+
const summaryLocation = singleItem.evalLogLocation + '.summary';
785+
if (!fs.existsSync(summaryLocation)) {
786+
await this.qs.cliServer.generateLogSummary(singleItem.evalLogLocation, summaryLocation);
787+
}
788+
await this.tryOpenExternalFile(summaryLocation);
789+
} else {
790+
this.warnNoEvalLog();
791+
}
792+
}
739793

740794
async handleCancel(
741795
singleItem: QueryHistoryInfo,

extensions/ql-vscode/src/query-results.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ export class LocalQueryInfo {
216216

217217
public failureReason: string | undefined;
218218
public completedQuery: CompletedQueryInfo | undefined;
219+
public evalLogLocation: string | undefined;
219220
private config: QueryHistoryConfig | undefined;
220221

221222
/**

extensions/ql-vscode/src/queryserver-client.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export class QueryServerClient extends DisposableObject {
146146
args.push('--require-db-registration');
147147
}
148148

149-
if (await this.cliServer.cliConstraints.supportsOldEvalStats()) {
149+
if (await this.cliServer.cliConstraints.supportsOldEvalStats() && !(await this.cliServer.cliConstraints.supportsPerQueryEvalLog())) {
150150
args.push('--old-eval-stats');
151151
}
152152

@@ -258,3 +258,7 @@ export class QueryServerClient extends DisposableObject {
258258
export function findQueryLogFile(resultPath: string): string {
259259
return path.join(resultPath, 'query.log');
260260
}
261+
262+
export function findQueryStructLogFile(resultPath: string): string {
263+
return path.join(resultPath, 'evaluator-log.jsonl');
264+
}

extensions/ql-vscode/src/run-queries.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { ProgressCallback, UserCancellationException } from './commandRunner';
2929
import { DatabaseInfo, QueryMetadata } from './pure/interface-types';
3030
import { logger } from './logging';
3131
import * as messages from './pure/messages';
32-
import { InitialQueryInfo } from './query-results';
32+
import { InitialQueryInfo, LocalQueryInfo } from './query-results';
3333
import * as qsClient from './queryserver-client';
3434
import { isQuickQueryPath } from './quick-query';
3535
import { compileDatabaseUpgradeSequence, hasNondestructiveUpgradeCapabilities, upgradeDatabaseExplicit } from './upgrades';
@@ -94,6 +94,10 @@ export class QueryEvaluationInfo {
9494
return qsClient.findQueryLogFile(this.querySaveDir);
9595
}
9696

97+
get structLogPath() {
98+
return qsClient.findQueryStructLogFile(this.querySaveDir);
99+
}
100+
97101
get resultsPaths() {
98102
return {
99103
resultsPath: path.join(this.querySaveDir, 'results.bqrs'),
@@ -124,6 +128,7 @@ export class QueryEvaluationInfo {
124128
dbItem: DatabaseItem,
125129
progress: ProgressCallback,
126130
token: CancellationToken,
131+
queryInfo?: LocalQueryInfo,
127132
): Promise<messages.EvaluationResult> {
128133
if (!dbItem.contents || dbItem.error) {
129134
throw new Error('Can\'t run query on invalid database.');
@@ -155,6 +160,12 @@ export class QueryEvaluationInfo {
155160
dbDir: dbItem.contents.datasetUri.fsPath,
156161
workingSet: 'default'
157162
};
163+
if (queryInfo && await qs.cliServer.cliConstraints.supportsPerQueryEvalLog()) {
164+
await qs.sendRequest(messages.startLog, {
165+
db: dataset,
166+
logPath: this.structLogPath,
167+
});
168+
}
158169
const params: messages.EvaluateQueriesParams = {
159170
db: dataset,
160171
evaluateId: callbackId,
@@ -171,6 +182,13 @@ export class QueryEvaluationInfo {
171182
}
172183
} finally {
173184
qs.unRegisterCallback(callbackId);
185+
if (queryInfo && await qs.cliServer.cliConstraints.supportsPerQueryEvalLog()) {
186+
await qs.sendRequest(messages.endLog, {
187+
db: dataset,
188+
logPath: this.structLogPath,
189+
});
190+
queryInfo.evalLogLocation = this.structLogPath;
191+
}
174192
}
175193
return result || {
176194
evaluationTime: 0,
@@ -657,6 +675,7 @@ export async function compileAndRunQueryAgainstDatabase(
657675
progress: ProgressCallback,
658676
token: CancellationToken,
659677
templates?: messages.TemplateDefinitions,
678+
queryInfo?: LocalQueryInfo,
660679
): Promise<QueryWithResults> {
661680
if (!dbItem.contents || !dbItem.contents.dbSchemeUri) {
662681
throw new Error(`Database ${dbItem.databaseUri} does not have a CodeQL database scheme.`);
@@ -742,7 +761,7 @@ export async function compileAndRunQueryAgainstDatabase(
742761
}
743762

744763
if (errors.length === 0) {
745-
const result = await query.run(qs, upgradeQlo, availableMlModels, dbItem, progress, token);
764+
const result = await query.run(qs, upgradeQlo, availableMlModels, dbItem, progress, token, queryInfo);
746765
if (result.resultType !== messages.QueryResultType.SUCCESS) {
747766
const message = result.message || 'Failed to run query';
748767
void logger.log(message);

0 commit comments

Comments
 (0)