Send new per-query alert count event reports for QA telemetry (#1741)
This commit is contained in:
parent
cff3d9e3c9
commit
46a6823b81
19 changed files with 213 additions and 51 deletions
File diff suppressed because one or more lines are too long
6
lib/analyze-action.js
generated
6
lib/analyze-action.js
generated
|
|
@ -44,7 +44,7 @@ const logging_1 = require("./logging");
|
||||||
const repository_1 = require("./repository");
|
const repository_1 = require("./repository");
|
||||||
const shared_environment_1 = require("./shared-environment");
|
const shared_environment_1 = require("./shared-environment");
|
||||||
const trap_caching_1 = require("./trap-caching");
|
const trap_caching_1 = require("./trap-caching");
|
||||||
const upload_lib = __importStar(require("./upload-lib"));
|
const uploadLib = __importStar(require("./upload-lib"));
|
||||||
const util = __importStar(require("./util"));
|
const util = __importStar(require("./util"));
|
||||||
const util_1 = require("./util");
|
const util_1 = require("./util");
|
||||||
async function sendStatusReport(startedAt, config, stats, error, trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, logger) {
|
async function sendStatusReport(startedAt, config, stats, error, trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, logger) {
|
||||||
|
|
@ -177,7 +177,7 @@ async function run() {
|
||||||
core.setOutput("db-locations", dbLocations);
|
core.setOutput("db-locations", dbLocations);
|
||||||
const uploadInput = actionsUtil.getOptionalInput("upload");
|
const uploadInput = actionsUtil.getOptionalInput("upload");
|
||||||
if (runStats && actionsUtil.getUploadValue(uploadInput) === "always") {
|
if (runStats && actionsUtil.getUploadValue(uploadInput) === "always") {
|
||||||
uploadResult = await upload_lib.uploadFromActions(outputDir, actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), logger);
|
uploadResult = await uploadLib.uploadFromActions(outputDir, actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), logger);
|
||||||
core.setOutput("sarif-id", uploadResult.sarifID);
|
core.setOutput("sarif-id", uploadResult.sarifID);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -196,7 +196,7 @@ async function run() {
|
||||||
}
|
}
|
||||||
else if (uploadResult !== undefined &&
|
else if (uploadResult !== undefined &&
|
||||||
actionsUtil.getRequiredInput("wait-for-processing") === "true") {
|
actionsUtil.getRequiredInput("wait-for-processing") === "true") {
|
||||||
await upload_lib.waitForProcessing((0, repository_1.parseRepositoryNwo)(util.getRequiredEnvParam("GITHUB_REPOSITORY")), uploadResult.sarifID, (0, logging_1.getActionsLogger)());
|
await uploadLib.waitForProcessing((0, repository_1.parseRepositoryNwo)(util.getRequiredEnvParam("GITHUB_REPOSITORY")), uploadResult.sarifID, (0, logging_1.getActionsLogger)());
|
||||||
}
|
}
|
||||||
// If we did not throw an error yet here, but we expect one, throw it.
|
// If we did not throw an error yet here, but we expect one, throw it.
|
||||||
if (actionsUtil.getOptionalInput("expect-error") === "true") {
|
if (actionsUtil.getOptionalInput("expect-error") === "true") {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
51
lib/analyze.js
generated
51
lib/analyze.js
generated
|
|
@ -39,6 +39,7 @@ const configUtils = __importStar(require("./config-utils"));
|
||||||
const feature_flags_1 = require("./feature-flags");
|
const feature_flags_1 = require("./feature-flags");
|
||||||
const languages_1 = require("./languages");
|
const languages_1 = require("./languages");
|
||||||
const tracer_config_1 = require("./tracer-config");
|
const tracer_config_1 = require("./tracer-config");
|
||||||
|
const upload_lib_1 = require("./upload-lib");
|
||||||
const util = __importStar(require("./util"));
|
const util = __importStar(require("./util"));
|
||||||
class CodeQLAnalysisError extends Error {
|
class CodeQLAnalysisError extends Error {
|
||||||
constructor(queriesStatusReport, message) {
|
constructor(queriesStatusReport, message) {
|
||||||
|
|
@ -138,6 +139,9 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
|
||||||
const queryFilters = validateQueryFilters(config.originalUserInput["query-filters"]);
|
const queryFilters = validateQueryFilters(config.originalUserInput["query-filters"]);
|
||||||
const packsWithVersion = config.packs[language] || [];
|
const packsWithVersion = config.packs[language] || [];
|
||||||
try {
|
try {
|
||||||
|
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
|
||||||
|
let startTimeInterpretResults;
|
||||||
|
let endTimeInterpretResults;
|
||||||
if (await util.useCodeScanningConfigInCli(codeql, features)) {
|
if (await util.useCodeScanningConfigInCli(codeql, features)) {
|
||||||
// If we are using the code scanning config in the CLI,
|
// If we are using the code scanning config in the CLI,
|
||||||
// much of the work needed to generate the query suites
|
// much of the work needed to generate the query suites
|
||||||
|
|
@ -152,11 +156,11 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
|
||||||
statusReport[`analyze_builtin_queries_${language}_duration_ms`] =
|
statusReport[`analyze_builtin_queries_${language}_duration_ms`] =
|
||||||
new Date().getTime() - startTimeBuiltIn;
|
new Date().getTime() - startTimeBuiltIn;
|
||||||
logger.startGroup(`Interpreting results for ${language}`);
|
logger.startGroup(`Interpreting results for ${language}`);
|
||||||
const startTimeInterpretResults = new Date().getTime();
|
startTimeInterpretResults = new Date().getTime();
|
||||||
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
|
|
||||||
const analysisSummary = await runInterpretResults(language, undefined, sarifFile, config.debugMode);
|
const analysisSummary = await runInterpretResults(language, undefined, sarifFile, config.debugMode);
|
||||||
|
endTimeInterpretResults = new Date().getTime();
|
||||||
statusReport[`interpret_results_${language}_duration_ms`] =
|
statusReport[`interpret_results_${language}_duration_ms`] =
|
||||||
new Date().getTime() - startTimeInterpretResults;
|
endTimeInterpretResults - startTimeInterpretResults;
|
||||||
logger.endGroup();
|
logger.endGroup();
|
||||||
logger.info(analysisSummary);
|
logger.info(analysisSummary);
|
||||||
}
|
}
|
||||||
|
|
@ -201,14 +205,29 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
|
||||||
}
|
}
|
||||||
logger.endGroup();
|
logger.endGroup();
|
||||||
logger.startGroup(`Interpreting results for ${language}`);
|
logger.startGroup(`Interpreting results for ${language}`);
|
||||||
const startTimeInterpretResults = new Date().getTime();
|
startTimeInterpretResults = new Date().getTime();
|
||||||
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
|
|
||||||
const analysisSummary = await runInterpretResults(language, querySuitePaths, sarifFile, config.debugMode);
|
const analysisSummary = await runInterpretResults(language, querySuitePaths, sarifFile, config.debugMode);
|
||||||
|
endTimeInterpretResults = new Date().getTime();
|
||||||
statusReport[`interpret_results_${language}_duration_ms`] =
|
statusReport[`interpret_results_${language}_duration_ms`] =
|
||||||
new Date().getTime() - startTimeInterpretResults;
|
endTimeInterpretResults - startTimeInterpretResults;
|
||||||
logger.endGroup();
|
logger.endGroup();
|
||||||
logger.info(analysisSummary);
|
logger.info(analysisSummary);
|
||||||
}
|
}
|
||||||
|
if (await features.getValue(feature_flags_1.Feature.QaTelemetryEnabled)) {
|
||||||
|
const perQueryAlertCounts = getPerQueryAlertCounts(sarifFile, logger);
|
||||||
|
const perQueryAlertCountEventReport = {
|
||||||
|
event: "codeql database interpret-results",
|
||||||
|
started_at: startTimeInterpretResults.toString(),
|
||||||
|
completed_at: endTimeInterpretResults.toString(),
|
||||||
|
exit_status: "success",
|
||||||
|
language,
|
||||||
|
properties: perQueryAlertCounts,
|
||||||
|
};
|
||||||
|
if (statusReport["event_reports"] === undefined) {
|
||||||
|
statusReport["event_reports"] = [];
|
||||||
|
}
|
||||||
|
statusReport["event_reports"].push(perQueryAlertCountEventReport);
|
||||||
|
}
|
||||||
await runPrintLinesOfCode(language);
|
await runPrintLinesOfCode(language);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
|
|
@ -225,6 +244,26 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
|
||||||
const databasePath = util.getCodeQLDatabasePath(config, language);
|
const databasePath = util.getCodeQLDatabasePath(config, language);
|
||||||
return await codeql.databaseInterpretResults(databasePath, queries, sarifFile, addSnippetsFlag, threadsFlag, enableDebugLogging ? "-vv" : "-v", automationDetailsId, config, features, logger);
|
return await codeql.databaseInterpretResults(databasePath, queries, sarifFile, addSnippetsFlag, threadsFlag, enableDebugLogging ? "-vv" : "-v", automationDetailsId, config, features, logger);
|
||||||
}
|
}
|
||||||
|
/** Get an object with all queries and their counts parsed from a SARIF file path. */
|
||||||
|
function getPerQueryAlertCounts(sarifPath, log) {
|
||||||
|
(0, upload_lib_1.validateSarifFileSchema)(sarifPath, log);
|
||||||
|
const sarifObject = JSON.parse(fs.readFileSync(sarifPath, "utf8"));
|
||||||
|
// We do not need to compute fingerprints because we are not sending data based off of locations.
|
||||||
|
// Generate the query: alert count object
|
||||||
|
const perQueryAlertCounts = {};
|
||||||
|
// All rules (queries), from all results, from all runs
|
||||||
|
for (const sarifRun of sarifObject.runs) {
|
||||||
|
if (sarifRun.results) {
|
||||||
|
for (const result of sarifRun.results) {
|
||||||
|
const query = result.rule?.id || result.ruleId;
|
||||||
|
if (query) {
|
||||||
|
perQueryAlertCounts[query] = (perQueryAlertCounts[query] || 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return perQueryAlertCounts;
|
||||||
|
}
|
||||||
async function runPrintLinesOfCode(language) {
|
async function runPrintLinesOfCode(language) {
|
||||||
const databasePath = util.getCodeQLDatabasePath(config, language);
|
const databasePath = util.getCodeQLDatabasePath(config, language);
|
||||||
return await codeql.databasePrintBaseline(databasePath);
|
return await codeql.databasePrintBaseline(databasePath);
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
37
lib/analyze.test.js
generated
37
lib/analyze.test.js
generated
|
|
@ -37,11 +37,16 @@ const feature_flags_1 = require("./feature-flags");
|
||||||
const languages_1 = require("./languages");
|
const languages_1 = require("./languages");
|
||||||
const logging_1 = require("./logging");
|
const logging_1 = require("./logging");
|
||||||
const testing_utils_1 = require("./testing-utils");
|
const testing_utils_1 = require("./testing-utils");
|
||||||
|
const uploadLib = __importStar(require("./upload-lib"));
|
||||||
const util = __importStar(require("./util"));
|
const util = __importStar(require("./util"));
|
||||||
(0, testing_utils_1.setupTests)(ava_1.default);
|
(0, testing_utils_1.setupTests)(ava_1.default);
|
||||||
// Checks that the duration fields are populated for the correct language
|
/** Checks that the duration fields are populated for the correct language
|
||||||
// and correct case of builtin or custom. Also checks the correct search
|
* and correct case of builtin or custom. Also checks the correct search
|
||||||
// paths are set in the database analyze invocation.
|
* paths are set in the database analyze invocation.
|
||||||
|
*
|
||||||
|
* Mocks the QA telemetry feature flag and checks the appropriate status report
|
||||||
|
* fields.
|
||||||
|
*/
|
||||||
(0, ava_1.default)("status report fields and search path setting", async (t) => {
|
(0, ava_1.default)("status report fields and search path setting", async (t) => {
|
||||||
let searchPathsUsed = [];
|
let searchPathsUsed = [];
|
||||||
return await util.withTmpDir(async (tmpDir) => {
|
return await util.withTmpDir(async (tmpDir) => {
|
||||||
|
|
@ -53,6 +58,7 @@ const util = __importStar(require("./util"));
|
||||||
[languages_1.Language.cpp]: ["a/b@1.0.0"],
|
[languages_1.Language.cpp]: ["a/b@1.0.0"],
|
||||||
[languages_1.Language.java]: ["c/d@2.0.0"],
|
[languages_1.Language.java]: ["c/d@2.0.0"],
|
||||||
};
|
};
|
||||||
|
sinon.stub(uploadLib, "validateSarifFileSchema");
|
||||||
for (const language of Object.values(languages_1.Language)) {
|
for (const language of Object.values(languages_1.Language)) {
|
||||||
(0, codeql_1.setCodeQL)({
|
(0, codeql_1.setCodeQL)({
|
||||||
packDownload: async () => ({ packs: [] }),
|
packDownload: async () => ({ packs: [] }),
|
||||||
|
|
@ -130,18 +136,25 @@ const util = __importStar(require("./util"));
|
||||||
builtin: ["foo.ql"],
|
builtin: ["foo.ql"],
|
||||||
custom: [],
|
custom: [],
|
||||||
};
|
};
|
||||||
const builtinStatusReport = await (0, analyze_1.runQueries)(tmpDir, memoryFlag, addSnippetsFlag, threadsFlag, undefined, config, (0, logging_1.getRunnerLogger)(true), (0, testing_utils_1.createFeatures)([]));
|
const builtinStatusReport = await (0, analyze_1.runQueries)(tmpDir, memoryFlag, addSnippetsFlag, threadsFlag, undefined, config, (0, logging_1.getRunnerLogger)(true), (0, testing_utils_1.createFeatures)([feature_flags_1.Feature.QaTelemetryEnabled]));
|
||||||
const hasPacks = language in packs;
|
const hasPacks = language in packs;
|
||||||
const statusReportKeys = Object.keys(builtinStatusReport).sort();
|
const statusReportKeys = Object.keys(builtinStatusReport).sort();
|
||||||
if (hasPacks) {
|
if (hasPacks) {
|
||||||
t.deepEqual(statusReportKeys.length, 3, statusReportKeys.toString());
|
t.deepEqual(statusReportKeys.length, 4, statusReportKeys.toString());
|
||||||
t.deepEqual(statusReportKeys[0], `analyze_builtin_queries_${language}_duration_ms`);
|
t.deepEqual(statusReportKeys[0], `analyze_builtin_queries_${language}_duration_ms`);
|
||||||
t.deepEqual(statusReportKeys[1], `analyze_custom_queries_${language}_duration_ms`);
|
t.deepEqual(statusReportKeys[1], `analyze_custom_queries_${language}_duration_ms`);
|
||||||
t.deepEqual(statusReportKeys[2], `interpret_results_${language}_duration_ms`);
|
t.deepEqual(statusReportKeys[2], "event_reports");
|
||||||
|
t.deepEqual(statusReportKeys[3], `interpret_results_${language}_duration_ms`);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
t.deepEqual(statusReportKeys[0], `analyze_builtin_queries_${language}_duration_ms`);
|
t.deepEqual(statusReportKeys[0], `analyze_builtin_queries_${language}_duration_ms`);
|
||||||
t.deepEqual(statusReportKeys[1], `interpret_results_${language}_duration_ms`);
|
t.deepEqual(statusReportKeys[1], "event_reports");
|
||||||
|
t.deepEqual(statusReportKeys[2], `interpret_results_${language}_duration_ms`);
|
||||||
|
}
|
||||||
|
if (builtinStatusReport.event_reports) {
|
||||||
|
for (const eventReport of builtinStatusReport.event_reports) {
|
||||||
|
t.deepEqual(eventReport.event, "codeql database interpret-results");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
config.queries[language] = {
|
config.queries[language] = {
|
||||||
builtin: [],
|
builtin: [],
|
||||||
|
|
@ -156,14 +169,20 @@ const util = __importStar(require("./util"));
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const customStatusReport = await (0, analyze_1.runQueries)(tmpDir, memoryFlag, addSnippetsFlag, threadsFlag, undefined, config, (0, logging_1.getRunnerLogger)(true), (0, testing_utils_1.createFeatures)([]));
|
const customStatusReport = await (0, analyze_1.runQueries)(tmpDir, memoryFlag, addSnippetsFlag, threadsFlag, undefined, config, (0, logging_1.getRunnerLogger)(true), (0, testing_utils_1.createFeatures)([feature_flags_1.Feature.QaTelemetryEnabled]));
|
||||||
t.deepEqual(Object.keys(customStatusReport).length, 2);
|
t.deepEqual(Object.keys(customStatusReport).length, 3);
|
||||||
t.true(`analyze_custom_queries_${language}_duration_ms` in customStatusReport);
|
t.true(`analyze_custom_queries_${language}_duration_ms` in customStatusReport);
|
||||||
const expectedSearchPathsUsed = hasPacks
|
const expectedSearchPathsUsed = hasPacks
|
||||||
? [undefined, undefined, "/1", "/2", undefined]
|
? [undefined, undefined, "/1", "/2", undefined]
|
||||||
: [undefined, "/1", "/2"];
|
: [undefined, "/1", "/2"];
|
||||||
t.deepEqual(searchPathsUsed, expectedSearchPathsUsed);
|
t.deepEqual(searchPathsUsed, expectedSearchPathsUsed);
|
||||||
t.true(`interpret_results_${language}_duration_ms` in customStatusReport);
|
t.true(`interpret_results_${language}_duration_ms` in customStatusReport);
|
||||||
|
t.true("event_reports" in customStatusReport);
|
||||||
|
if (customStatusReport.event_reports) {
|
||||||
|
for (const eventReport of customStatusReport.event_reports) {
|
||||||
|
t.deepEqual(eventReport.event, "codeql database interpret-results");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
verifyQuerySuites(tmpDir);
|
verifyQuerySuites(tmpDir);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
6
lib/feature-flags.js
generated
6
lib/feature-flags.js
generated
|
|
@ -40,6 +40,7 @@ var Feature;
|
||||||
Feature["ExportCodeScanningConfigEnabled"] = "export_code_scanning_config_enabled";
|
Feature["ExportCodeScanningConfigEnabled"] = "export_code_scanning_config_enabled";
|
||||||
Feature["ExportDiagnosticsEnabled"] = "export_diagnostics_enabled";
|
Feature["ExportDiagnosticsEnabled"] = "export_diagnostics_enabled";
|
||||||
Feature["MlPoweredQueriesEnabled"] = "ml_powered_queries_enabled";
|
Feature["MlPoweredQueriesEnabled"] = "ml_powered_queries_enabled";
|
||||||
|
Feature["QaTelemetryEnabled"] = "qa_telemetry_enabled";
|
||||||
Feature["UploadFailedSarifEnabled"] = "upload_failed_sarif_enabled";
|
Feature["UploadFailedSarifEnabled"] = "upload_failed_sarif_enabled";
|
||||||
})(Feature = exports.Feature || (exports.Feature = {}));
|
})(Feature = exports.Feature || (exports.Feature = {}));
|
||||||
exports.featureConfig = {
|
exports.featureConfig = {
|
||||||
|
|
@ -68,6 +69,11 @@ exports.featureConfig = {
|
||||||
minimumVersion: "2.7.5",
|
minimumVersion: "2.7.5",
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
|
[Feature.QaTelemetryEnabled]: {
|
||||||
|
envVar: "CODEQL_ACTION_QA_TELEMETRY",
|
||||||
|
minimumVersion: undefined,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
[Feature.UploadFailedSarifEnabled]: {
|
[Feature.UploadFailedSarifEnabled]: {
|
||||||
envVar: "CODEQL_ACTION_UPLOAD_FAILED_SARIF",
|
envVar: "CODEQL_ACTION_UPLOAD_FAILED_SARIF",
|
||||||
minimumVersion: "2.11.3",
|
minimumVersion: "2.11.3",
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
4
lib/upload-lib.js
generated
4
lib/upload-lib.js
generated
|
|
@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.pruneInvalidResults = exports.validateUniqueCategory = exports.waitForProcessing = exports.buildPayload = exports.validateSarifFileSchema = exports.countResultsInSarif = exports.uploadFromActions = exports.findSarifFilesInDir = exports.populateRunAutomationDetails = exports.combineSarifFiles = void 0;
|
exports.pruneInvalidResults = exports.validateUniqueCategory = exports.waitForProcessing = exports.buildPayload = exports.validateSarifFileSchema = exports.uploadFromActions = exports.findSarifFilesInDir = exports.populateRunAutomationDetails = void 0;
|
||||||
const fs = __importStar(require("fs"));
|
const fs = __importStar(require("fs"));
|
||||||
const path = __importStar(require("path"));
|
const path = __importStar(require("path"));
|
||||||
const process_1 = require("process");
|
const process_1 = require("process");
|
||||||
|
|
@ -62,7 +62,6 @@ function combineSarifFiles(sarifFiles) {
|
||||||
}
|
}
|
||||||
return combinedSarif;
|
return combinedSarif;
|
||||||
}
|
}
|
||||||
exports.combineSarifFiles = combineSarifFiles;
|
|
||||||
// Populates the run.automationDetails.id field using the analysis_key and environment
|
// Populates the run.automationDetails.id field using the analysis_key and environment
|
||||||
// and return an updated sarif file contents.
|
// and return an updated sarif file contents.
|
||||||
function populateRunAutomationDetails(sarif, category, analysis_key, environment) {
|
function populateRunAutomationDetails(sarif, category, analysis_key, environment) {
|
||||||
|
|
@ -174,7 +173,6 @@ function countResultsInSarif(sarif) {
|
||||||
}
|
}
|
||||||
return numResults;
|
return numResults;
|
||||||
}
|
}
|
||||||
exports.countResultsInSarif = countResultsInSarif;
|
|
||||||
// Validates that the given file path refers to a valid SARIF file.
|
// Validates that the given file path refers to a valid SARIF file.
|
||||||
// Throws an error if the file is invalid.
|
// Throws an error if the file is invalid.
|
||||||
function validateSarifFileSchema(sarifFilePath, logger) {
|
function validateSarifFileSchema(sarifFilePath, logger) {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -315,6 +315,25 @@ export type ActionStatus =
|
||||||
| "failure"
|
| "failure"
|
||||||
| "user-error";
|
| "user-error";
|
||||||
|
|
||||||
|
// Any status report may include an array of EventReports associated with it.
|
||||||
|
export interface EventReport {
|
||||||
|
/** An enumerable description of the event. */
|
||||||
|
event: string;
|
||||||
|
/** Time this event started. */
|
||||||
|
started_at: string;
|
||||||
|
/** Time this event ended. */
|
||||||
|
completed_at: string;
|
||||||
|
/** eg: `success`, `failure`, `timeout`, etc. */
|
||||||
|
exit_status?: string;
|
||||||
|
/** If the event is language-specific. */
|
||||||
|
language?: string;
|
||||||
|
/**
|
||||||
|
* A generic JSON blob of data related to this event.
|
||||||
|
* Use Object.assign() to append additional fields to the object.
|
||||||
|
*/
|
||||||
|
properties?: object;
|
||||||
|
}
|
||||||
|
|
||||||
export interface StatusReportBase {
|
export interface StatusReportBase {
|
||||||
/**
|
/**
|
||||||
* UUID representing the job run that this status report belongs to. We
|
* UUID representing the job run that this status report belongs to. We
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,13 @@ import {
|
||||||
CODEQL_ACTION_DID_AUTOBUILD_GOLANG,
|
CODEQL_ACTION_DID_AUTOBUILD_GOLANG,
|
||||||
} from "./shared-environment";
|
} from "./shared-environment";
|
||||||
import { getTotalCacheSize, uploadTrapCaches } from "./trap-caching";
|
import { getTotalCacheSize, uploadTrapCaches } from "./trap-caching";
|
||||||
import * as upload_lib from "./upload-lib";
|
import * as uploadLib from "./upload-lib";
|
||||||
import { UploadResult } from "./upload-lib";
|
import { UploadResult } from "./upload-lib";
|
||||||
import * as util from "./util";
|
import * as util from "./util";
|
||||||
import { checkForTimeout, wrapError } from "./util";
|
import { checkForTimeout, wrapError } from "./util";
|
||||||
|
|
||||||
interface AnalysisStatusReport
|
interface AnalysisStatusReport
|
||||||
extends upload_lib.UploadStatusReport,
|
extends uploadLib.UploadStatusReport,
|
||||||
QueriesStatusReport {}
|
QueriesStatusReport {}
|
||||||
|
|
||||||
interface FinishStatusReport
|
interface FinishStatusReport
|
||||||
|
|
@ -269,7 +269,7 @@ async function run() {
|
||||||
core.setOutput("db-locations", dbLocations);
|
core.setOutput("db-locations", dbLocations);
|
||||||
const uploadInput = actionsUtil.getOptionalInput("upload");
|
const uploadInput = actionsUtil.getOptionalInput("upload");
|
||||||
if (runStats && actionsUtil.getUploadValue(uploadInput) === "always") {
|
if (runStats && actionsUtil.getUploadValue(uploadInput) === "always") {
|
||||||
uploadResult = await upload_lib.uploadFromActions(
|
uploadResult = await uploadLib.uploadFromActions(
|
||||||
outputDir,
|
outputDir,
|
||||||
actionsUtil.getRequiredInput("checkout_path"),
|
actionsUtil.getRequiredInput("checkout_path"),
|
||||||
actionsUtil.getOptionalInput("category"),
|
actionsUtil.getOptionalInput("category"),
|
||||||
|
|
@ -296,7 +296,7 @@ async function run() {
|
||||||
uploadResult !== undefined &&
|
uploadResult !== undefined &&
|
||||||
actionsUtil.getRequiredInput("wait-for-processing") === "true"
|
actionsUtil.getRequiredInput("wait-for-processing") === "true"
|
||||||
) {
|
) {
|
||||||
await upload_lib.waitForProcessing(
|
await uploadLib.waitForProcessing(
|
||||||
parseRepositoryNwo(util.getRequiredEnvParam("GITHUB_REPOSITORY")),
|
parseRepositoryNwo(util.getRequiredEnvParam("GITHUB_REPOSITORY")),
|
||||||
uploadResult.sarifID,
|
uploadResult.sarifID,
|
||||||
getActionsLogger()
|
getActionsLogger()
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,18 @@ import { Feature } from "./feature-flags";
|
||||||
import { Language } from "./languages";
|
import { Language } from "./languages";
|
||||||
import { getRunnerLogger } from "./logging";
|
import { getRunnerLogger } from "./logging";
|
||||||
import { setupTests, setupActionsVars, createFeatures } from "./testing-utils";
|
import { setupTests, setupActionsVars, createFeatures } from "./testing-utils";
|
||||||
|
import * as uploadLib from "./upload-lib";
|
||||||
import * as util from "./util";
|
import * as util from "./util";
|
||||||
|
|
||||||
setupTests(test);
|
setupTests(test);
|
||||||
|
|
||||||
// Checks that the duration fields are populated for the correct language
|
/** Checks that the duration fields are populated for the correct language
|
||||||
// and correct case of builtin or custom. Also checks the correct search
|
* and correct case of builtin or custom. Also checks the correct search
|
||||||
// paths are set in the database analyze invocation.
|
* paths are set in the database analyze invocation.
|
||||||
|
*
|
||||||
|
* Mocks the QA telemetry feature flag and checks the appropriate status report
|
||||||
|
* fields.
|
||||||
|
*/
|
||||||
test("status report fields and search path setting", async (t) => {
|
test("status report fields and search path setting", async (t) => {
|
||||||
let searchPathsUsed: Array<string | undefined> = [];
|
let searchPathsUsed: Array<string | undefined> = [];
|
||||||
return await util.withTmpDir(async (tmpDir) => {
|
return await util.withTmpDir(async (tmpDir) => {
|
||||||
|
|
@ -38,6 +43,8 @@ test("status report fields and search path setting", async (t) => {
|
||||||
[Language.java]: ["c/d@2.0.0"],
|
[Language.java]: ["c/d@2.0.0"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sinon.stub(uploadLib, "validateSarifFileSchema");
|
||||||
|
|
||||||
for (const language of Object.values(Language)) {
|
for (const language of Object.values(Language)) {
|
||||||
setCodeQL({
|
setCodeQL({
|
||||||
packDownload: async () => ({ packs: [] }),
|
packDownload: async () => ({ packs: [] }),
|
||||||
|
|
@ -135,12 +142,12 @@ test("status report fields and search path setting", async (t) => {
|
||||||
undefined,
|
undefined,
|
||||||
config,
|
config,
|
||||||
getRunnerLogger(true),
|
getRunnerLogger(true),
|
||||||
createFeatures([])
|
createFeatures([Feature.QaTelemetryEnabled])
|
||||||
);
|
);
|
||||||
const hasPacks = language in packs;
|
const hasPacks = language in packs;
|
||||||
const statusReportKeys = Object.keys(builtinStatusReport).sort();
|
const statusReportKeys = Object.keys(builtinStatusReport).sort();
|
||||||
if (hasPacks) {
|
if (hasPacks) {
|
||||||
t.deepEqual(statusReportKeys.length, 3, statusReportKeys.toString());
|
t.deepEqual(statusReportKeys.length, 4, statusReportKeys.toString());
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
statusReportKeys[0],
|
statusReportKeys[0],
|
||||||
`analyze_builtin_queries_${language}_duration_ms`
|
`analyze_builtin_queries_${language}_duration_ms`
|
||||||
|
|
@ -149,8 +156,9 @@ test("status report fields and search path setting", async (t) => {
|
||||||
statusReportKeys[1],
|
statusReportKeys[1],
|
||||||
`analyze_custom_queries_${language}_duration_ms`
|
`analyze_custom_queries_${language}_duration_ms`
|
||||||
);
|
);
|
||||||
|
t.deepEqual(statusReportKeys[2], "event_reports");
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
statusReportKeys[2],
|
statusReportKeys[3],
|
||||||
`interpret_results_${language}_duration_ms`
|
`interpret_results_${language}_duration_ms`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -158,11 +166,17 @@ test("status report fields and search path setting", async (t) => {
|
||||||
statusReportKeys[0],
|
statusReportKeys[0],
|
||||||
`analyze_builtin_queries_${language}_duration_ms`
|
`analyze_builtin_queries_${language}_duration_ms`
|
||||||
);
|
);
|
||||||
|
t.deepEqual(statusReportKeys[1], "event_reports");
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
statusReportKeys[1],
|
statusReportKeys[2],
|
||||||
`interpret_results_${language}_duration_ms`
|
`interpret_results_${language}_duration_ms`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (builtinStatusReport.event_reports) {
|
||||||
|
for (const eventReport of builtinStatusReport.event_reports) {
|
||||||
|
t.deepEqual(eventReport.event, "codeql database interpret-results");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config.queries[language] = {
|
config.queries[language] = {
|
||||||
builtin: [],
|
builtin: [],
|
||||||
|
|
@ -185,9 +199,9 @@ test("status report fields and search path setting", async (t) => {
|
||||||
undefined,
|
undefined,
|
||||||
config,
|
config,
|
||||||
getRunnerLogger(true),
|
getRunnerLogger(true),
|
||||||
createFeatures([])
|
createFeatures([Feature.QaTelemetryEnabled])
|
||||||
);
|
);
|
||||||
t.deepEqual(Object.keys(customStatusReport).length, 2);
|
t.deepEqual(Object.keys(customStatusReport).length, 3);
|
||||||
t.true(
|
t.true(
|
||||||
`analyze_custom_queries_${language}_duration_ms` in customStatusReport
|
`analyze_custom_queries_${language}_duration_ms` in customStatusReport
|
||||||
);
|
);
|
||||||
|
|
@ -196,6 +210,12 @@ test("status report fields and search path setting", async (t) => {
|
||||||
: [undefined, "/1", "/2"];
|
: [undefined, "/1", "/2"];
|
||||||
t.deepEqual(searchPathsUsed, expectedSearchPathsUsed);
|
t.deepEqual(searchPathsUsed, expectedSearchPathsUsed);
|
||||||
t.true(`interpret_results_${language}_duration_ms` in customStatusReport);
|
t.true(`interpret_results_${language}_duration_ms` in customStatusReport);
|
||||||
|
t.true("event_reports" in customStatusReport);
|
||||||
|
if (customStatusReport.event_reports) {
|
||||||
|
for (const eventReport of customStatusReport.event_reports) {
|
||||||
|
t.deepEqual(eventReport.event, "codeql database interpret-results");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyQuerySuites(tmpDir);
|
verifyQuerySuites(tmpDir);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import * as toolrunner from "@actions/exec/lib/toolrunner";
|
||||||
import del from "del";
|
import del from "del";
|
||||||
import * as yaml from "js-yaml";
|
import * as yaml from "js-yaml";
|
||||||
|
|
||||||
import { DatabaseCreationTimings } from "./actions-util";
|
import { DatabaseCreationTimings, EventReport } from "./actions-util";
|
||||||
import * as analysisPaths from "./analysis-paths";
|
import * as analysisPaths from "./analysis-paths";
|
||||||
import { CodeQL, getCodeQL } from "./codeql";
|
import { CodeQL, getCodeQL } from "./codeql";
|
||||||
import * as configUtils from "./config-utils";
|
import * as configUtils from "./config-utils";
|
||||||
|
|
@ -14,6 +14,7 @@ import { FeatureEnablement, Feature } from "./feature-flags";
|
||||||
import { isScannedLanguage, Language } from "./languages";
|
import { isScannedLanguage, Language } from "./languages";
|
||||||
import { Logger } from "./logging";
|
import { Logger } from "./logging";
|
||||||
import { endTracingForCluster } from "./tracer-config";
|
import { endTracingForCluster } from "./tracer-config";
|
||||||
|
import { validateSarifFileSchema } from "./upload-lib";
|
||||||
import * as util from "./util";
|
import * as util from "./util";
|
||||||
|
|
||||||
export class CodeQLAnalysisError extends Error {
|
export class CodeQLAnalysisError extends Error {
|
||||||
|
|
@ -78,6 +79,8 @@ export interface QueriesStatusReport {
|
||||||
interpret_results_swift_duration_ms?: number;
|
interpret_results_swift_duration_ms?: number;
|
||||||
/** Name of language that errored during analysis (or undefined if no language failed). */
|
/** Name of language that errored during analysis (or undefined if no language failed). */
|
||||||
analyze_failure_language?: string;
|
analyze_failure_language?: string;
|
||||||
|
/** Reports on discrete events associated with this status report. */
|
||||||
|
event_reports?: EventReport[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupPythonExtractor(
|
async function setupPythonExtractor(
|
||||||
|
|
@ -242,6 +245,9 @@ export async function runQueries(
|
||||||
const packsWithVersion = config.packs[language] || [];
|
const packsWithVersion = config.packs[language] || [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
|
||||||
|
let startTimeInterpretResults: number;
|
||||||
|
let endTimeInterpretResults: number;
|
||||||
if (await util.useCodeScanningConfigInCli(codeql, features)) {
|
if (await util.useCodeScanningConfigInCli(codeql, features)) {
|
||||||
// If we are using the code scanning config in the CLI,
|
// If we are using the code scanning config in the CLI,
|
||||||
// much of the work needed to generate the query suites
|
// much of the work needed to generate the query suites
|
||||||
|
|
@ -257,16 +263,16 @@ export async function runQueries(
|
||||||
new Date().getTime() - startTimeBuiltIn;
|
new Date().getTime() - startTimeBuiltIn;
|
||||||
|
|
||||||
logger.startGroup(`Interpreting results for ${language}`);
|
logger.startGroup(`Interpreting results for ${language}`);
|
||||||
const startTimeInterpretResults = new Date().getTime();
|
startTimeInterpretResults = new Date().getTime();
|
||||||
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
|
|
||||||
const analysisSummary = await runInterpretResults(
|
const analysisSummary = await runInterpretResults(
|
||||||
language,
|
language,
|
||||||
undefined,
|
undefined,
|
||||||
sarifFile,
|
sarifFile,
|
||||||
config.debugMode
|
config.debugMode
|
||||||
);
|
);
|
||||||
|
endTimeInterpretResults = new Date().getTime();
|
||||||
statusReport[`interpret_results_${language}_duration_ms`] =
|
statusReport[`interpret_results_${language}_duration_ms`] =
|
||||||
new Date().getTime() - startTimeInterpretResults;
|
endTimeInterpretResults - startTimeInterpretResults;
|
||||||
logger.endGroup();
|
logger.endGroup();
|
||||||
logger.info(analysisSummary);
|
logger.info(analysisSummary);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -342,19 +348,37 @@ export async function runQueries(
|
||||||
}
|
}
|
||||||
logger.endGroup();
|
logger.endGroup();
|
||||||
logger.startGroup(`Interpreting results for ${language}`);
|
logger.startGroup(`Interpreting results for ${language}`);
|
||||||
const startTimeInterpretResults = new Date().getTime();
|
startTimeInterpretResults = new Date().getTime();
|
||||||
const sarifFile = path.join(sarifFolder, `${language}.sarif`);
|
|
||||||
const analysisSummary = await runInterpretResults(
|
const analysisSummary = await runInterpretResults(
|
||||||
language,
|
language,
|
||||||
querySuitePaths,
|
querySuitePaths,
|
||||||
sarifFile,
|
sarifFile,
|
||||||
config.debugMode
|
config.debugMode
|
||||||
);
|
);
|
||||||
|
endTimeInterpretResults = new Date().getTime();
|
||||||
statusReport[`interpret_results_${language}_duration_ms`] =
|
statusReport[`interpret_results_${language}_duration_ms`] =
|
||||||
new Date().getTime() - startTimeInterpretResults;
|
endTimeInterpretResults - startTimeInterpretResults;
|
||||||
logger.endGroup();
|
logger.endGroup();
|
||||||
logger.info(analysisSummary);
|
logger.info(analysisSummary);
|
||||||
}
|
}
|
||||||
|
if (await features.getValue(Feature.QaTelemetryEnabled)) {
|
||||||
|
const perQueryAlertCounts = getPerQueryAlertCounts(sarifFile, logger);
|
||||||
|
|
||||||
|
const perQueryAlertCountEventReport: EventReport = {
|
||||||
|
event: "codeql database interpret-results",
|
||||||
|
started_at: startTimeInterpretResults.toString(),
|
||||||
|
completed_at: endTimeInterpretResults.toString(),
|
||||||
|
exit_status: "success",
|
||||||
|
language,
|
||||||
|
properties: perQueryAlertCounts,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (statusReport["event_reports"] === undefined) {
|
||||||
|
statusReport["event_reports"] = [];
|
||||||
|
}
|
||||||
|
statusReport["event_reports"].push(perQueryAlertCountEventReport);
|
||||||
|
}
|
||||||
|
|
||||||
await runPrintLinesOfCode(language);
|
await runPrintLinesOfCode(language);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.info(String(e));
|
logger.info(String(e));
|
||||||
|
|
@ -392,6 +416,34 @@ export async function runQueries(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Get an object with all queries and their counts parsed from a SARIF file path. */
|
||||||
|
function getPerQueryAlertCounts(
|
||||||
|
sarifPath: string,
|
||||||
|
log: Logger
|
||||||
|
): Record<string, number> {
|
||||||
|
validateSarifFileSchema(sarifPath, log);
|
||||||
|
const sarifObject = JSON.parse(
|
||||||
|
fs.readFileSync(sarifPath, "utf8")
|
||||||
|
) as util.SarifFile;
|
||||||
|
// We do not need to compute fingerprints because we are not sending data based off of locations.
|
||||||
|
|
||||||
|
// Generate the query: alert count object
|
||||||
|
const perQueryAlertCounts: Record<string, number> = {};
|
||||||
|
|
||||||
|
// All rules (queries), from all results, from all runs
|
||||||
|
for (const sarifRun of sarifObject.runs) {
|
||||||
|
if (sarifRun.results) {
|
||||||
|
for (const result of sarifRun.results) {
|
||||||
|
const query = result.rule?.id || result.ruleId;
|
||||||
|
if (query) {
|
||||||
|
perQueryAlertCounts[query] = (perQueryAlertCounts[query] || 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return perQueryAlertCounts;
|
||||||
|
}
|
||||||
|
|
||||||
async function runPrintLinesOfCode(language: Language): Promise<string> {
|
async function runPrintLinesOfCode(language: Language): Promise<string> {
|
||||||
const databasePath = util.getCodeQLDatabasePath(config, language);
|
const databasePath = util.getCodeQLDatabasePath(config, language);
|
||||||
return await codeql.databasePrintBaseline(databasePath);
|
return await codeql.databasePrintBaseline(databasePath);
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ export enum Feature {
|
||||||
ExportCodeScanningConfigEnabled = "export_code_scanning_config_enabled",
|
ExportCodeScanningConfigEnabled = "export_code_scanning_config_enabled",
|
||||||
ExportDiagnosticsEnabled = "export_diagnostics_enabled",
|
ExportDiagnosticsEnabled = "export_diagnostics_enabled",
|
||||||
MlPoweredQueriesEnabled = "ml_powered_queries_enabled",
|
MlPoweredQueriesEnabled = "ml_powered_queries_enabled",
|
||||||
|
QaTelemetryEnabled = "qa_telemetry_enabled",
|
||||||
UploadFailedSarifEnabled = "upload_failed_sarif_enabled",
|
UploadFailedSarifEnabled = "upload_failed_sarif_enabled",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,6 +77,11 @@ export const featureConfig: Record<
|
||||||
minimumVersion: "2.7.5",
|
minimumVersion: "2.7.5",
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
|
[Feature.QaTelemetryEnabled]: {
|
||||||
|
envVar: "CODEQL_ACTION_QA_TELEMETRY",
|
||||||
|
minimumVersion: undefined,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
[Feature.UploadFailedSarifEnabled]: {
|
[Feature.UploadFailedSarifEnabled]: {
|
||||||
envVar: "CODEQL_ACTION_UPLOAD_FAILED_SARIF",
|
envVar: "CODEQL_ACTION_UPLOAD_FAILED_SARIF",
|
||||||
minimumVersion: "2.11.3",
|
minimumVersion: "2.11.3",
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import * as workflow from "./workflow";
|
||||||
|
|
||||||
// Takes a list of paths to sarif files and combines them together,
|
// Takes a list of paths to sarif files and combines them together,
|
||||||
// returning the contents of the combined sarif file.
|
// returning the contents of the combined sarif file.
|
||||||
export function combineSarifFiles(sarifFiles: string[]): SarifFile {
|
function combineSarifFiles(sarifFiles: string[]): SarifFile {
|
||||||
const combinedSarif: SarifFile = {
|
const combinedSarif: SarifFile = {
|
||||||
version: null,
|
version: null,
|
||||||
runs: [],
|
runs: [],
|
||||||
|
|
@ -198,7 +198,7 @@ function getSarifFilePaths(sarifPath: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Counts the number of results in the given SARIF file
|
// Counts the number of results in the given SARIF file
|
||||||
export function countResultsInSarif(sarif: string): number {
|
function countResultsInSarif(sarif: string): number {
|
||||||
let numResults = 0;
|
let numResults = 0;
|
||||||
let parsedSarif;
|
let parsedSarif;
|
||||||
try {
|
try {
|
||||||
|
|
@ -224,7 +224,7 @@ export function countResultsInSarif(sarif: string): number {
|
||||||
// Validates that the given file path refers to a valid SARIF file.
|
// Validates that the given file path refers to a valid SARIF file.
|
||||||
// Throws an error if the file is invalid.
|
// Throws an error if the file is invalid.
|
||||||
export function validateSarifFileSchema(sarifFilePath: string, logger: Logger) {
|
export function validateSarifFileSchema(sarifFilePath: string, logger: Logger) {
|
||||||
const sarif = JSON.parse(fs.readFileSync(sarifFilePath, "utf8"));
|
const sarif = JSON.parse(fs.readFileSync(sarifFilePath, "utf8")) as SarifFile;
|
||||||
const schema = require("../src/sarif-schema-2.1.0.json") as jsonschema.Schema;
|
const schema = require("../src/sarif-schema-2.1.0.json") as jsonschema.Schema;
|
||||||
|
|
||||||
const result = new jsonschema.Validator().validate(sarif, schema);
|
const result = new jsonschema.Validator().validate(sarif, schema);
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,9 @@ export interface SarifInvocation {
|
||||||
|
|
||||||
export interface SarifResult {
|
export interface SarifResult {
|
||||||
ruleId?: string;
|
ruleId?: string;
|
||||||
|
rule?: {
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
message?: {
|
message?: {
|
||||||
text?: string;
|
text?: string;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue