Skip to content

Commit 838a2b7

Browse files
author
Dave Bartolomeo
committed
Scan logs on change in current query
1 parent 3c57597 commit 838a2b7

4 files changed

Lines changed: 166 additions & 115 deletions

File tree

extensions/ql-vscode/src/extension.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ import { RemoteQuery } from './remote-queries/remote-query';
101101
import { EvalLogViewer } from './eval-log-viewer';
102102
import { SummaryLanguageSupport } from './log-insights/summary-language-support';
103103
import { JoinOrderScannerProvider } from './log-insights/join-order';
104+
import { LogScannerService } from './log-insights/log-scanner-service';
104105

105106
/**
106107
* extension.ts
@@ -485,7 +486,9 @@ async function activateWithInstalledDistribution(
485486
ctx.subscriptions.push(qhm);
486487

487488
void logger.log('Initializing evaluation log scanners.');
488-
ctx.subscriptions.push(qhm.registerLogScannerProvider(new JoinOrderScannerProvider()));
489+
const logScannerService = new LogScannerService(qhm);
490+
ctx.subscriptions.push(logScannerService);
491+
ctx.subscriptions.push(logScannerService.registerLogScannerProvider(new JoinOrderScannerProvider()));
489492

490493
void logger.log('Reading query history');
491494
await qhm.readQueryHistory();
@@ -520,8 +523,6 @@ async function activateWithInstalledDistribution(
520523
forceReveal: WebviewReveal
521524
): Promise<void> {
522525
await intm.showResults(query, forceReveal, false);
523-
// Always update the log warnings so they stay in sync with the results.
524-
await qhm.scanEvalLog(query);
525526
}
526527

527528
async function compileAndRunQuery(
@@ -562,7 +563,7 @@ async function activateWithInstalledDistribution(
562563
undefined,
563564
item,
564565
);
565-
item.completeThisQuery(completedQueryInfo);
566+
qhm.completeQuery(item, completedQueryInfo);
566567
await showResultsForCompletedQuery(item as CompletedLocalQueryInfo, WebviewReveal.NotForced);
567568
// Note we must update the query history view after showing results as the
568569
// display and sorting might depend on the number of results
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { Diagnostic, DiagnosticSeverity, Disposable, languages, Range, Uri } from 'vscode';
2+
import { DisposableObject } from '../pure/disposable-object';
3+
import { QueryHistoryManager } from '../query-history';
4+
import { QueryHistoryInfo } from '../query-results';
5+
import { EvaluationLogProblemReporter, EvaluationLogScannerProvider } from './log-scanner';
6+
import { PipelineInfo, SummarySymbols } from './summary-parser';
7+
import * as fs from 'fs-extra';
8+
import { readJsonlFile } from './jsonl-reader';
9+
10+
/**
11+
* Compute the key used to find a predicate in the summary symbols.
12+
* @param name The name of the predicate.
13+
* @param raHash The RA hash of the predicate.
14+
* @returns The key of the predicate, consisting of `name@shortHash`, where `shortHash` is the first
15+
* eight characters of `raHash`.
16+
*/
17+
function predicateSymbolKey(name: string, raHash: string): string {
18+
return `${name}@${raHash.substring(0, 8)}`;
19+
}
20+
21+
/**
22+
* Implementation of `EvaluationLogProblemReporter` that generates `Diagnostic` objects to display
23+
* in the VS Code "Problems" view.
24+
*/
25+
class ProblemReporter implements EvaluationLogProblemReporter {
26+
public readonly diagnostics: Diagnostic[] = [];
27+
28+
constructor(private readonly symbols: SummarySymbols | undefined) {
29+
}
30+
31+
public reportProblem(predicateName: string, raHash: string, iteration: number, message: string): void {
32+
const nameWithHash = predicateSymbolKey(predicateName, raHash);
33+
const predicateSymbol = this.symbols?.predicates[nameWithHash];
34+
let predicateInfo: PipelineInfo | undefined = undefined;
35+
if (predicateSymbol !== undefined) {
36+
predicateInfo = predicateSymbol.iterations[iteration];
37+
}
38+
if (predicateInfo !== undefined) {
39+
const range = new Range(predicateInfo.raStartLine, 0, predicateInfo.raEndLine + 1, 0);
40+
this.diagnostics.push(new Diagnostic(range, message, DiagnosticSeverity.Error));
41+
}
42+
}
43+
}
44+
45+
export class LogScannerService extends DisposableObject {
46+
private readonly scannerProviders = new Map<number, EvaluationLogScannerProvider>();
47+
private nextScannerProviderId = 0;
48+
private readonly diagnosticCollection = this.push(languages.createDiagnosticCollection('ql-eval-log'));
49+
private currentItem: QueryHistoryInfo | undefined = undefined;
50+
51+
constructor(qhm: QueryHistoryManager) {
52+
super();
53+
54+
this.push(qhm.onDidChangeCurrentQueryItem(async (item) => {
55+
if (item !== this.currentItem) {
56+
this.currentItem = item;
57+
await this.scanEvalLog(item);
58+
}
59+
}));
60+
61+
this.push(qhm.onDidCompleteQuery(async (item) => {
62+
if (item === this.currentItem) {
63+
await this.scanEvalLog(item);
64+
}
65+
}));
66+
}
67+
68+
/**
69+
* Scan the evaluation log for a query, and report any diagnostics.
70+
*
71+
* @param query The query whose log is to be scanned.
72+
*/
73+
public async scanEvalLog(
74+
query: QueryHistoryInfo | undefined
75+
): Promise<void> {
76+
this.diagnosticCollection.clear();
77+
78+
if ((query === undefined) || (query.t !== 'local')) {
79+
return;
80+
}
81+
82+
if ((query !== undefined) && (query.t === 'local') && query.jsonEvalLogSummaryLocation) {
83+
const diagnostics = await this.scanLog(query.jsonEvalLogSummaryLocation, query.evalLogSummarySymbolsLocation);
84+
const uri = Uri.file(query.evalLogSummaryLocation!);
85+
this.diagnosticCollection.set(uri, diagnostics);
86+
}
87+
}
88+
89+
/**
90+
* Register a provider that can create instances of `EvaluationLogScanner` to scan evaluation logs
91+
* for problems.
92+
* @param provider The provider.
93+
* @returns A `Disposable` that, when disposed, will unregister the provider.
94+
*/
95+
public registerLogScannerProvider(provider: EvaluationLogScannerProvider): Disposable {
96+
const id = this.nextScannerProviderId;
97+
this.nextScannerProviderId++;
98+
99+
this.scannerProviders.set(id, provider);
100+
return {
101+
dispose: () => {
102+
this.scannerProviders.delete(id);
103+
}
104+
};
105+
}
106+
107+
/**
108+
* Scan the evaluator summary log for problems, using the scanners for all registered providers.
109+
* @param jsonSummaryLocation The file path of the JSON summary log.
110+
* @param symbolsLocation The file path of the symbols file for the human-readable log summary.
111+
* @returns An array of `Diagnostic`s representing the problems found by scanners.
112+
*/
113+
private async scanLog(jsonSummaryLocation: string, symbolsLocation: string | undefined): Promise<Diagnostic[]> {
114+
let symbols: SummarySymbols | undefined = undefined;
115+
if (symbolsLocation !== undefined) {
116+
symbols = JSON.parse(await fs.readFile(symbolsLocation, { encoding: 'utf-8' }));
117+
}
118+
119+
const problemReporter = new ProblemReporter(symbols);
120+
121+
const scanners = [...this.scannerProviders.values()].map(p => p.createScanner(problemReporter));
122+
123+
await readJsonlFile(jsonSummaryLocation, async obj => {
124+
scanners.forEach(scanner => {
125+
scanner.onEvent(obj);
126+
});
127+
});
128+
129+
scanners.forEach(scanner => scanner.onDone());
130+
131+
return problemReporter.diagnostics;
132+
}
133+
}

0 commit comments

Comments
 (0)