Skip to content

Commit 81b8104

Browse files
Expose per-query structured evaluator logs
1 parent ef127c2 commit 81b8104

9 files changed

Lines changed: 195 additions & 3 deletions

File tree

extensions/ql-vscode/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ No user facing changes.
1515
- Fix a bug where queries took a long time to run if there are no folders in the workspace. [#1157](https://github.com/github/vscode-codeql/pull/1157)
1616
- [BREAKING CHANGE] The `codeQL.runningQueries.customLogDirectory` setting is deprecated and no longer has any function. Instead, all query log files will be stored in the query history directory, next to the query results. [#1178](https://github.com/github/vscode-codeql/pull/1178)
1717
- Add a _Open query directory_ command for query items. This command opens the directory containing all artifacts for a query. [#1179](https://github.com/github/vscode-codeql/pull/1179)
18+
- Add options to display evaluator logs for a given query run. Some information that was previously found in the query server output may now be found here. [#1186](https://github.com/github/vscode-codeql/pull/1186)
1819

1920
## 1.5.11 - 10 February 2022
2021

extensions/ql-vscode/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,14 @@
522522
"command": "codeQLQueryHistory.openQueryDirectory",
523523
"title": "Open query directory"
524524
},
525+
{
526+
"command": "codeQLQueryHistory.showEvalLog",
527+
"title": "Show Evaluator Log (Raw)"
528+
},
529+
{
530+
"command": "codeQLQueryHistory.showEvalLogSummary",
531+
"title": "Show Evaluator Log (Summary)"
532+
},
525533
{
526534
"command": "codeQLQueryHistory.cancel",
527535
"title": "Cancel"
@@ -725,6 +733,16 @@
725733
"group": "9_qlCommands",
726734
"when": "view == codeQLQueryHistory && !hasRemoteServer"
727735
},
736+
{
737+
"command": "codeQLQueryHistory.showEvalLog",
738+
"group": "9_qlCommands",
739+
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem || viewItem == cancelledResultsItem"
740+
},
741+
{
742+
"command": "codeQLQueryHistory.showEvalLogSummary",
743+
"group": "9_qlCommands",
744+
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem || viewItem == cancelledResultsItem"
745+
},
728746
{
729747
"command": "codeQLQueryHistory.showQueryText",
730748
"group": "9_qlCommands",
@@ -924,6 +942,14 @@
924942
"command": "codeQLQueryHistory.showQueryLog",
925943
"when": "false"
926944
},
945+
{
946+
"command": "codeQLQueryHistory.showEvalLog",
947+
"when": "false"
948+
},
949+
{
950+
"command": "codeQLQueryHistory.showEvalLogSummary",
951+
"when": "false"
952+
},
927953
{
928954
"command": "codeQLQueryHistory.openQueryDirectory",
929955
"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
@@ -535,6 +535,8 @@ async function activateWithInstalledDistribution(
535535
queryStorageDir,
536536
progress,
537537
source.token,
538+
undefined,
539+
item,
538540
);
539541
item.completeThisQuery(completedQueryInfo);
540542
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',
@@ -744,6 +758,46 @@ export class QueryHistoryManager extends DisposableObject {
744758
}
745759
}
746760
}
761+
762+
private warnNoEvalLog() {
763+
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 + '?');
764+
}
765+
766+
async handleShowEvalLog(
767+
singleItem: QueryHistoryInfo,
768+
multiSelect: QueryHistoryInfo[]
769+
) {
770+
// Local queries only
771+
if (!this.assertSingleQuery(multiSelect) || singleItem?.t !== 'local') {
772+
return;
773+
}
774+
775+
if (singleItem.evalLogLocation) {
776+
await this.tryOpenExternalFile(singleItem.evalLogLocation);
777+
} else {
778+
this.warnNoEvalLog();
779+
}
780+
}
781+
782+
async handleShowEvalLogSummary(
783+
singleItem: QueryHistoryInfo,
784+
multiSelect: QueryHistoryInfo[]
785+
) {
786+
// Local queries only
787+
if (!this.assertSingleQuery(multiSelect) || singleItem?.t !== 'local') {
788+
return;
789+
}
790+
791+
if (singleItem.evalLogLocation) {
792+
const summaryLocation = singleItem.evalLogLocation + '.summary';
793+
if (!fs.existsSync(summaryLocation)) {
794+
await this.qs.cliServer.generateLogSummary(singleItem.evalLogLocation, summaryLocation);
795+
}
796+
await this.tryOpenExternalFile(summaryLocation);
797+
} else {
798+
this.warnNoEvalLog();
799+
}
800+
}
747801

748802
async handleCancel(
749803
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';
@@ -95,6 +95,10 @@ export class QueryEvaluationInfo {
9595
return qsClient.findQueryLogFile(this.querySaveDir);
9696
}
9797

98+
get structLogPath() {
99+
return qsClient.findQueryStructLogFile(this.querySaveDir);
100+
}
101+
98102
get resultsPaths() {
99103
return {
100104
resultsPath: path.join(this.querySaveDir, 'results.bqrs'),
@@ -125,6 +129,7 @@ export class QueryEvaluationInfo {
125129
dbItem: DatabaseItem,
126130
progress: ProgressCallback,
127131
token: CancellationToken,
132+
queryInfo?: LocalQueryInfo,
128133
): Promise<messages.EvaluationResult> {
129134
if (!dbItem.contents || dbItem.error) {
130135
throw new Error('Can\'t run query on invalid database.');
@@ -156,6 +161,12 @@ export class QueryEvaluationInfo {
156161
dbDir: dbItem.contents.datasetUri.fsPath,
157162
workingSet: 'default'
158163
};
164+
if (queryInfo && await qs.cliServer.cliConstraints.supportsPerQueryEvalLog()) {
165+
await qs.sendRequest(messages.startLog, {
166+
db: dataset,
167+
logPath: this.structLogPath,
168+
});
169+
}
159170
const params: messages.EvaluateQueriesParams = {
160171
db: dataset,
161172
evaluateId: callbackId,
@@ -172,6 +183,13 @@ export class QueryEvaluationInfo {
172183
}
173184
} finally {
174185
qs.unRegisterCallback(callbackId);
186+
if (queryInfo && await qs.cliServer.cliConstraints.supportsPerQueryEvalLog()) {
187+
await qs.sendRequest(messages.endLog, {
188+
db: dataset,
189+
logPath: this.structLogPath,
190+
});
191+
queryInfo.evalLogLocation = this.structLogPath;
192+
}
175193
}
176194
return result || {
177195
evaluationTime: 0,
@@ -658,6 +676,7 @@ export async function compileAndRunQueryAgainstDatabase(
658676
progress: ProgressCallback,
659677
token: CancellationToken,
660678
templates?: messages.TemplateDefinitions,
679+
queryInfo?: LocalQueryInfo,
661680
): Promise<QueryWithResults> {
662681
if (!dbItem.contents || !dbItem.contents.dbSchemeUri) {
663682
throw new Error(`Database ${dbItem.databaseUri} does not have a CodeQL database scheme.`);
@@ -743,7 +762,7 @@ export async function compileAndRunQueryAgainstDatabase(
743762
}
744763

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

0 commit comments

Comments
 (0)