Parse summary of evaluator logs into data model (#1405)

Co-authored-by: Aditya Sharad <6874315+adityasharad@users.noreply.github.com>
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com>
This commit is contained in:
Angela P Wen 2022-07-12 14:04:55 +02:00 коммит произвёл GitHub
Родитель e57bbcb711
Коммит c9d895ea42
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 301 добавлений и 16 удалений

Просмотреть файл

@ -689,6 +689,23 @@ export class CodeQLCliServer implements Disposable {
return await this.runCodeQlCliCommand(['generate', 'log-summary'], subcommandArgs, 'Generating log summary');
}
/**
* Generate a JSON summary of an evaluation log.
* @param inputPath The path of an evaluation event log.
* @param outputPath The path to write a JSON summary of it to.
*/
async generateJsonLogSummary(
inputPath: string,
outputPath: string,
): Promise<string> {
const subcommandArgs = [
'--format=predicates',
inputPath,
outputPath
];
return await this.runCodeQlCliCommand(['generate', 'log-summary'], subcommandArgs, 'Generating JSON log summary');
}
/**
* Gets the results from a bqrs.
* @param bqrsPath The path to the bqrs.

Просмотреть файл

@ -0,0 +1,44 @@
import * as os from 'os';
// TODO(angelapwen): Only load in necessary information and
// location in bytes for this log to save memory.
export interface EvaluatorLogData {
queryCausingWork: string;
predicateName: string;
millis: number;
resultSize: number;
ra: Pipelines;
}
interface Pipelines {
// Key: pipeline identifier; Value: array of pipeline steps
pipelineNamesToSteps: Map<string, string[]>;
}
/**
* A pure method that parses a string of evaluator log summaries into
* an array of EvaluatorLogData objects.
*
*/
export function parseVisualizerData(logSummary: string): EvaluatorLogData[] {
// Remove newline delimiters because summary is in .jsonl format.
const jsonSummaryObjects: string[] = logSummary.split(os.EOL + os.EOL);
const visualizerData: EvaluatorLogData[] = [];
for (const obj of jsonSummaryObjects) {
const jsonObj = JSON.parse(obj);
// Only convert log items that have an RA and millis field
if (jsonObj.ra !== undefined && jsonObj.millis !== undefined) {
const newLogData: EvaluatorLogData = {
queryCausingWork: jsonObj.queryCausingWork,
predicateName: jsonObj.predicateName,
millis: jsonObj.millis,
resultSize: jsonObj.resultSize,
ra: jsonObj.ra
};
visualizerData.push(newLogData);
}
}
return visualizerData;
}

Просмотреть файл

@ -267,6 +267,10 @@ export function findQueryEvalLogSummaryFile(resultPath: string): string {
return path.join(resultPath, 'evaluator-log.summary');
}
export function findJsonQueryEvalLogSummaryFile(resultPath: string): string {
return path.join(resultPath, 'evaluator-log.summary.jsonl');
}
export function findQueryEvalLogEndSummaryFile(resultPath: string): string {
return path.join(resultPath, 'evaluator-log-end.summary');
}

Просмотреть файл

@ -37,6 +37,7 @@ import { ensureMetadataIsComplete } from './query-results';
import { SELECT_QUERY_NAME } from './contextual/locationFinder';
import { DecodedBqrsChunk } from './pure/bqrs-cli-types';
import { getErrorMessage } from './pure/helpers-pure';
import { parseVisualizerData } from './pure/log-summary-parser';
/**
* run-queries.ts
@ -103,6 +104,10 @@ export class QueryEvaluationInfo {
return qsClient.findQueryEvalLogSummaryFile(this.querySaveDir);
}
get jsonEvalLogSummaryPath() {
return qsClient.findJsonQueryEvalLogSummaryFile(this.querySaveDir);
}
get evalLogEndSummaryPath() {
return qsClient.findQueryEvalLogEndSummaryFile(this.querySaveDir);
}
@ -198,22 +203,10 @@ export class QueryEvaluationInfo {
logPath: this.evalLogPath,
});
if (await this.hasEvalLog()) {
queryInfo.evalLogLocation = this.evalLogPath;
void qs.cliServer.generateLogSummary(this.evalLogPath, this.evalLogSummaryPath, this.evalLogEndSummaryPath)
.then(() => {
queryInfo.evalLogSummaryLocation = this.evalLogSummaryPath;
fs.readFile(this.evalLogEndSummaryPath, (err, buffer) => {
if (err) {
throw new Error(`Could not read structured evaluator log end of summary file at ${this.evalLogEndSummaryPath}.`);
}
void qs.logger.log(' --- Evaluator Log Summary --- ', { additionalLogLocation: this.logPath });
void qs.logger.log(buffer.toString(), { additionalLogLocation: this.logPath });
});
})
.catch(err => {
void showAndLogWarningMessage(`Failed to generate structured evaluator log summary. Reason: ${err.message}`);
});
this.displayHumanReadableLogSummary(queryInfo, qs);
if (config.isCanary()) {
this.parseJsonLogSummary(qs.cliServer);
}
} else {
void showAndLogWarningMessage(`Failed to write structured evaluator log to ${this.evalLogPath}.`);
}
@ -338,6 +331,48 @@ export class QueryEvaluationInfo {
return fs.pathExists(this.evalLogPath);
}
/**
* Calls the appropriate CLI command to generate a human-readable log summary
* and logs to the Query Server console and query log file.
*/
displayHumanReadableLogSummary(queryInfo: LocalQueryInfo, qs: qsClient.QueryServerClient): void {
queryInfo.evalLogLocation = this.evalLogPath;
void qs.cliServer.generateLogSummary(this.evalLogPath, this.evalLogSummaryPath, this.evalLogEndSummaryPath)
.then(() => {
queryInfo.evalLogSummaryLocation = this.evalLogSummaryPath;
fs.readFile(this.evalLogEndSummaryPath, (err, buffer) => {
if (err) {
throw new Error(`Could not read structured evaluator log end of summary file at ${this.evalLogEndSummaryPath}.`);
}
void qs.logger.log(' --- Evaluator Log Summary --- ', { additionalLogLocation: this.logPath });
void qs.logger.log(buffer.toString(), { additionalLogLocation: this.logPath });
});
})
.catch(err => {
void showAndLogWarningMessage(`Failed to generate human-readable structured evaluator log summary. Reason: ${err.message}`);
});
}
/**
* Calls the appropriate CLI command to generate a JSON log summary and parse it
* into the appropriate data model for the log visualizer.
*/
parseJsonLogSummary(cliServer: cli.CodeQLCliServer): void {
void cliServer.generateJsonLogSummary(this.evalLogPath, this.jsonEvalLogSummaryPath)
.then(() => {
// TODO(angelapwen): Stream the file in.
fs.readFile(this.jsonEvalLogSummaryPath, (err, buffer) => {
if (err) {
throw new Error(`Could not read structured evaluator log summary JSON file at ${this.jsonEvalLogSummaryPath}.`);
}
parseVisualizerData(buffer.toString()); // Eventually this return value will feed into the tree visualizer.
});
})
.catch(err => {
void showAndLogWarningMessage(`Failed to generate JSON structured evaluator log summary. Reason: ${err.message}`);
});
}
/**
* Creates the CSV file containing the results of this query. This will only be called if the query
* does not have interpreted results and the CSV file does not already exist.

Просмотреть файл

@ -0,0 +1,5 @@
{
"summaryLogVersion" : "0.3.0",
"codeqlVersion" : "2.9.0+202204201304plus",
"startTime" : "2022-06-23T14:02:41.607Z"
}

Просмотреть файл

@ -0,0 +1,14 @@
{
"completionTime" : "2022-06-23T14:02:42.019Z",
"raHash" : "8caddafufc3it9svjph9m8d43me",
"predicateName" : "function_instantiation",
"appearsAs" : {
"function_instantiation" : {
"uboot-taint.ql" : [ 3, 9, 10, 11, 13, 14, 15, 17, 18, 20, 21, 22, 23, 24 ]
}
},
"queryCausingWork" : "uboot-taint.ql",
"evaluationStrategy" : "EXTENSIONAL",
"millis" : 0,
"resultSize" : 0
}

Просмотреть файл

@ -0,0 +1,124 @@
{
"completionTime" : "2022-06-23T14:02:42.007Z",
"raHash" : "e77dfaa9ciimqv7gb3imoesdb11",
"predicateName" : "query::ClassPointerType::getBaseType#dispred#f0820431#ff",
"appearsAs" : {
"query::ClassPointerType::getBaseType#dispred#f0820431#ff" : {
"uboot-taint.ql" : [ 3 ]
}
},
"queryCausingWork" : "uboot-taint.ql",
"evaluationStrategy" : "COMPUTE_SIMPLE",
"millis" : 11,
"resultSize" : 1413,
"dependencies" : {
"derivedtypes_2013#join_rhs" : "0da8f1fqbiin9i0mcitjedvlbc7",
"query::Class#class#f0820431#f" : "2e5d24rnudi1l99mvtse9u567b7",
"derivedtypes" : "070e120iu2i3dhj6pt13oqnbi66"
},
"ra" : {
"pipeline" : [
" {1} r1 = CONSTANT(unique int)[1]",
" {3} r2 = JOIN r1 WITH derivedtypes_2013#join_rhs ON FIRST 1 OUTPUT Rhs.3, Rhs.1, Rhs.2",
" {4} r3 = JOIN r2 WITH query::Class#class#f0820431#f ON FIRST 1 OUTPUT Lhs.1, Lhs.2, 1, Lhs.0",
" {2} r4 = JOIN r3 WITH derivedtypes ON FIRST 4 OUTPUT Lhs.0, Lhs.3",
" return r4"
]
},
"pipelineRuns" : [ {
"raReference" : "pipeline"
} ]
}
{
"completionTime" : "2022-06-23T14:02:42.072Z",
"raHash" : "0a0e3cicgtsru1m0cun896qhi61",
"predicateName" : "query::DefinedMemberFunction#class#f0820431#f",
"appearsAs" : {
"query::DefinedMemberFunction#class#f0820431#f" : {
"uboot-taint.ql" : [ 3 ]
}
},
"queryCausingWork" : "uboot-taint.ql",
"evaluationStrategy" : "COMPUTE_SIMPLE",
"millis" : 20,
"resultSize" : 8740,
"dependencies" : {
"fun_decls" : "e6e2e6vn02fcrrtp1ks1f75cd01",
"fun_def" : "6bec314lcq8sm3skg9mpecscp02",
"function_instantiation" : "8caddafufc3it9svjph9m8d43me",
"fun_decls_10#join_rhs" : "16474d6lehg7ssk83gnv2q7r8t8"
},
"ra" : {
"pipeline" : [
" {2} r1 = SCAN fun_decls OUTPUT In.0, In.1",
" {2} r2 = STREAM DEDUP r1",
" {1} r3 = JOIN r2 WITH fun_def ON FIRST 1 OUTPUT Lhs.1",
"",
" {2} r4 = SCAN function_instantiation OUTPUT In.1, In.0",
" {2} r5 = JOIN r4 WITH fun_decls_10#join_rhs ON FIRST 1 OUTPUT Rhs.1, Lhs.1",
" {1} r6 = JOIN r5 WITH fun_def ON FIRST 1 OUTPUT Lhs.1",
"",
" {1} r7 = r3 UNION r6",
" return r7"
]
},
"pipelineRuns" : [ {
"raReference" : "pipeline"
} ]
}
{
"completionTime" : "2022-06-23T14:02:43.799Z",
"raHash" : "1d0c6wplpr6bnlnii51r7f6lh85",
"predicateName" : "QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff",
"appearsAs" : {
"QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff" : {
"uboot-taint.ql" : [ 10 ]
}
},
"queryCausingWork" : "uboot-taint.ql",
"evaluationStrategy" : "COMPUTE_RECURSIVE",
"millis" : 2,
"predicateIterationMillis" : [ 1, 0 ],
"deltaSizes" : [ 1, 0 ],
"resultSize" : 1,
"dependencies" : {
"namespaces" : "bf72dcmerq68kur5k2uttmjoco4",
"namespacembrs_1#antijoin_rhs" : "3c47bbkgae024k8hgf148mgeqi2",
"QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff#join_rhs#1" : "d55d698lva8n15u4v7055ma1vl9",
"namespacembrs" : "08a148i70tonoa8fp0mb5eb44r1",
"QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff#join_rhs" : "77cd9dha843p3v12qso4qpe4qoe"
},
"layerSize" : 1,
"ra" : {
"base" : [
" {2} r1 = namespaces AND NOT namespacembrs_1#antijoin_rhs(Lhs.0)",
" return r1"
],
"standard" : [
" {2} r1 = JOIN QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff#prev_delta WITH QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff#join_rhs#1 ON FIRST 1 OUTPUT Rhs.1, Lhs.1",
"",
" {2} r2 = JOIN QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff#prev_delta WITH QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff#join_rhs ON FIRST 1 OUTPUT Rhs.1, (Lhs.1 ++ \"::\" ++ Rhs.2)",
"",
" {2} r3 = r1 UNION r2",
" {2} r4 = r3 AND NOT QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff#prev(Lhs.0, Lhs.1)",
" return r4"
],
"order_500000" : [
" {2} r1 = JOIN QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff#prev_delta WITH QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff#join_rhs#1 ON FIRST 1 OUTPUT Rhs.1, Lhs.1",
"",
" {2} r2 = JOIN QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff#prev_delta WITH namespacembrs ON FIRST 1 OUTPUT Rhs.1, Lhs.1",
" {2} r3 = JOIN r2 WITH namespaces ON FIRST 1 OUTPUT Lhs.0, (Lhs.1 ++ \"::\" ++ Rhs.1)",
"",
" {2} r4 = r1 UNION r3",
" {2} r5 = r4 AND NOT QualifiedName::Namespace::getAQualifierForMembers#f0820431#ff#prev(Lhs.0, Lhs.1)",
" return r5"
]
},
"pipelineRuns" : [ {
"raReference" : "base"
}, {
"raReference" : "order_500000"
} ]
}

Просмотреть файл

@ -0,0 +1,42 @@
import { expect } from 'chai';
import * as fs from 'fs-extra';
import * as path from 'path';
import 'mocha';
import { parseVisualizerData } from '../../src/pure/log-summary-parser';
describe('Evaluator log summary tests', async function () {
describe('for a valid summary text', async function () {
it('should return only valid EvaluatorLogData objects', async function () {
const validSummaryText = await fs.readFile(path.join(__dirname, 'evaluator-log-summaries/valid-summary.jsonl'), 'utf8');
const logDataItems = parseVisualizerData(validSummaryText.toString());
expect(logDataItems).to.not.be.undefined;
expect (logDataItems.length).to.eq(3);
for (const item of logDataItems) {
expect(item.queryCausingWork).to.not.be.empty;
expect(item.predicateName).to.not.be.empty;
expect(item.millis).to.be.a('number');
expect(item.resultSize).to.be.a('number');
expect(item.ra).to.not.be.undefined;
expect(item.ra).to.not.be.empty;
for (const [pipeline, steps] of Object.entries(item.ra)) {
expect (pipeline).to.not.be.empty;
expect (steps).to.not.be.undefined;
expect (steps.length).to.be.greaterThan(0);
}
}
});
it('should not parse a summary header object', async function () {
const invalidHeaderText = await fs.readFile(path.join(__dirname, 'evaluator-log-summaries/invalid-header.jsonl'), 'utf8');
const logDataItems = parseVisualizerData(invalidHeaderText);
expect (logDataItems.length).to.eq(0);
});
it('should not parse a log event missing RA or millis fields', async function () {
const invalidSummaryText = await fs.readFile(path.join(__dirname, 'evaluator-log-summaries/invalid-summary.jsonl'), 'utf8');
const logDataItems = parseVisualizerData(invalidSummaryText);
expect (logDataItems.length).to.eq(0);
});
});
});