|
| 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