@oaklean/cli
Version:
A command-line interface that provides utilities for parsing, inspecting, and converting the .oak file format, as well as interfaces used in the @oaklean suite.
295 lines • 29.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const profiler_1 = require("@oaklean/profiler");
const profiler_core_1 = require("@oaklean/profiler-core");
const commander_1 = require("commander");
class JestCommands {
constructor() {
const baseCommand = commander_1.program
.command('jest')
.description('Commands to inspect the jest profiler format. This is mostly used for debugging purposes');
baseCommand
.command('verify')
.description('Checks wether the accumulate report of the jest-test-environment would be generated the same way with this version')
.option('-o, --output <output>', 'output file path of the reproduced report (if not set no file will be generated)')
.option('-d, --deep', 'also check wether each report would be generated the same way')
.option('-m, --measure', 'also measure the reproduction of the reports and outputs a report (this will take longer, but is useful for performance comparisons)')
.action(this.verify.bind(this));
baseCommand
.command('inspect-profiles')
.description('Inspects all reports and cpu profiles in the jests output directory and verifies their consistency')
.action(this.inspectCPUProfiles.bind(this));
baseCommand
.command('verify-trees')
.description('Checks all sub reports in the output directory for SourceFileMetaDataTree consistency')
.action(this.verifyTrees.bind(this));
}
static init() {
return new JestCommands();
}
verify(options) {
return __awaiter(this, void 0, void 0, function* () {
const profilerConfig = profiler_core_1.ProfilerConfig.autoResolve();
const rootDir = profilerConfig.getRootDir();
const exportAssetHelper = new profiler_core_1.ExportAssetHelper(profilerConfig.getOutDir().join('jest'));
const verifyExportAssetHelper = new profiler_core_1.ExportAssetHelper(profilerConfig.getOutDir().join('jest-verify'));
const reportPaths = exportAssetHelper.allReportPathsInOutputDir();
const accumulatedProjectReportPath = exportAssetHelper.outputAccumulatedReportPath();
const expectedAccumulatedReport = profiler_core_1.ProjectReport.loadFromFile(accumulatedProjectReportPath, 'bin');
if (!expectedAccumulatedReport) {
profiler_core_1.LoggerHelper.warn(`Could not find a profiler report at ${accumulatedProjectReportPath.toPlatformString()}\n` +
'So no comparison can be made. Please make sure that the report is generated and stored in the original location.');
return;
}
let profiler;
if (options.measure !== undefined) {
profiler_core_1.LoggerHelper.success('Measuring the reproduction of the reports.');
profiler = new profiler_1.Profiler('verify');
yield profiler.start('latest');
}
const reports = [];
if (options.deep !== undefined) {
if (fs.existsSync(verifyExportAssetHelper.outputDir().toString())) {
fs.rmSync(verifyExportAssetHelper.outputDir().toString(), {
recursive: true
});
}
for (const reportPath of reportPaths) {
const expectedReport = profiler_core_1.ProjectReport.loadFromFile(reportPath, 'bin');
if (!expectedReport) {
profiler_core_1.LoggerHelper.error(`ProjectReport could not be found: ${reportPath}`);
continue;
}
if (reportPath.toString() === accumulatedProjectReportPath.toString()) {
continue; // Skip the accumulated report itself
}
const title = exportAssetHelper.titleFromReportFilePath(reportPath);
const report = new profiler_core_1.ProjectReport(expectedReport.executionDetails, profiler_core_1.ReportKind.measurement);
const metricsDataCollectionPath = exportAssetHelper.outputMetricsDataCollectionPath(title);
const externalResourceHelperPath = exportAssetHelper.outputExternalResourceHelperPath(title);
const v8CPUProfilePath = exportAssetHelper.outputCPUProfilePath(title);
const cpuProfile = yield profiler_core_1.CPUProfileHelper.loadFromFile(v8CPUProfilePath);
if (cpuProfile === undefined) {
profiler_core_1.LoggerHelper.error(`CPU profile could not be loaded from ${v8CPUProfilePath.toPlatformString()}. ` +
'Please make sure the file exists and is a valid CPU profile.');
continue;
}
const metricsDataCollection = profiler_core_1.MetricsDataCollection.loadFromFile(metricsDataCollectionPath);
const externalResourceHelper = profiler_core_1.ExternalResourceHelper.loadFromFile(rootDir, externalResourceHelperPath);
if (externalResourceHelper === undefined) {
profiler_core_1.LoggerHelper.error('External Resource Helper could not be loaded from' +
externalResourceHelperPath.toPlatformString() +
' Please make sure the file exists and is a valid external resource helper.');
continue;
}
profiler_core_1.LoggerHelper.log(`[REPRODUCE] ${reportPath.toPlatformString()}`);
yield report.insertCPUProfile(rootDir, cpuProfile, externalResourceHelper, metricsDataCollection);
report.trackUncommittedFiles(rootDir, externalResourceHelper);
report.relativeRootDir = expectedReport.relativeRootDir;
if (report.hash() !== expectedReport.hash()) {
profiler_core_1.LoggerHelper.warn(`[NOT_REPRODUCIBLE] ${reportPath.toPlatformString()}`, report.hash(), expectedReport.hash());
report.storeToFile(verifyExportAssetHelper.outputReportPath(title), 'pretty-json', profilerConfig);
expectedReport.storeToFile(verifyExportAssetHelper.outputReportPath(title + '-expected'), 'pretty-json', profilerConfig);
}
else {
profiler_core_1.LoggerHelper.success(`[REPRODUCIBLE] ${reportPath.toPlatformString()}`);
}
reports.push(report);
}
}
else {
for (const reportPath of reportPaths) {
if (reportPath.toString() === accumulatedProjectReportPath.toString()) {
continue; // Skip the accumulated report itself
}
const report = profiler_core_1.ProjectReport.loadFromFile(reportPath, 'bin');
if (!report) {
throw new Error(`ProjectReport could not be found: ${reportPath}`);
}
reports.push(report);
}
}
const engineModule = reports.length > 0
? reports[0].globalIndex.engineModule
: profiler_core_1.NodeModule.currentEngineModule();
const globalIndex = new profiler_core_1.GlobalIndex(engineModule);
const moduleIndex = globalIndex.getModuleIndex('upsert');
const accumulatedProjectReport = profiler_core_1.ProjectReport.merge(moduleIndex, ...reports);
if (profiler !== undefined) {
yield profiler.finish('latest');
profiler_core_1.LoggerHelper.success('Stored performance report at', profiler.exportAssetHelper.outputReportPath('latest').toPlatformString());
}
if (options.output) {
accumulatedProjectReport.storeToFile(new profiler_core_1.UnifiedPath(options.output), 'bin', profilerConfig);
profiler_core_1.LoggerHelper.log(`The report was stored at ${options.output}`);
}
else {
profiler_core_1.LoggerHelper.log('The report was not stored, because no output path was provided');
}
accumulatedProjectReport.relativeRootDir =
expectedAccumulatedReport.relativeRootDir;
const reportsAreEqual = expectedAccumulatedReport.hash() === accumulatedProjectReport.hash();
if (reportsAreEqual) {
profiler_core_1.LoggerHelper.success('The reports are equal');
}
else {
profiler_core_1.LoggerHelper.warn('The reports are not equal');
}
});
}
inspectCPUProfiles() {
return __awaiter(this, void 0, void 0, function* () {
const profilerConfig = profiler_core_1.ProfilerConfig.autoResolve();
const exportAssetHelper = new profiler_core_1.ExportAssetHelper(profilerConfig.getOutDir().join('jest'));
const accumulatedProjectReportPath = exportAssetHelper.outputAccumulatedReportPath();
const reportPaths = exportAssetHelper.allReportPathsInOutputDir();
let totalNodeCount = 0, totalSourceNodeLocationCount = 0, totalSampleCount = 0, totalHits = 0, totalCPUTime = 0;
for (const reportPath of reportPaths) {
if (reportPath.toString() === accumulatedProjectReportPath.toString()) {
continue; // Skip the accumulated report itself
}
const title = exportAssetHelper.titleFromReportFilePath(reportPath);
const cpuProfilePath = exportAssetHelper.outputCPUProfilePath(title);
const report = profiler_core_1.ProjectReport.loadFromFile(reportPath, 'bin');
if (!report) {
profiler_core_1.LoggerHelper.error(`ProjectReport could not be found: ${reportPath}`);
continue;
}
const cpuProfile = yield profiler_core_1.CPUProfileHelper.loadFromFile(cpuProfilePath);
if (cpuProfile === undefined) {
profiler_core_1.LoggerHelper.error(`CPU profile could not be loaded from ${cpuProfilePath.toPlatformString()}. ` +
'Please make sure the file exists and is a valid CPU profile.');
return;
}
const inspectResult = yield profiler_core_1.CPUProfileHelper.inspect(cpuProfile);
totalNodeCount += inspectResult.nodeCount;
totalSourceNodeLocationCount += inspectResult.sourceNodeLocationCount;
totalSampleCount += inspectResult.sampleCount;
totalHits += inspectResult.totalHits;
totalCPUTime += inspectResult.totalCPUTime;
const reportsTotal = report.totalAndMaxMetaData().total.sensorValues.aggregatedCPUTime;
if (reportsTotal !== inspectResult.totalCPUTime) {
profiler_core_1.LoggerHelper.warn(`Inconsistent CPU time in report: ${title}.\n` +
`Profile CPU Time: ${inspectResult.totalCPUTime}\n` +
`Report CPU Time: ${reportsTotal}`);
}
else {
profiler_core_1.LoggerHelper.success(`Consistent CPU time in report: ${title}. CPU Time: ${reportsTotal}`);
}
}
profiler_core_1.LoggerHelper.table([
{
type: 'Files Inspected',
value: reportPaths.length
},
{
type: 'Total Node Count',
value: totalNodeCount
},
{
type: 'Source Node Location Count',
value: totalSourceNodeLocationCount
},
{
type: 'Sample Count',
value: totalSampleCount
},
{
type: 'Total Hits',
value: totalHits
},
{
type: 'Total CPU Time',
value: totalCPUTime,
unit: 'µs'
}
], ['type', 'value', 'unit']);
});
}
verifyTrees() {
return __awaiter(this, void 0, void 0, function* () {
const profilerConfig = profiler_core_1.ProfilerConfig.autoResolve();
const exportAssetHelper = new profiler_core_1.ExportAssetHelper(profilerConfig.getOutDir().join('jest'));
const reportPaths = exportAssetHelper.allReportPathsInOutputDir();
let totalDiff = 0;
for (const reportPath of reportPaths) {
const projectReport = profiler_core_1.ProjectReport.loadFromFile(reportPath, 'bin');
if (!projectReport) {
profiler_core_1.LoggerHelper.error(`ProjectReport could not be found: ${reportPath}`);
continue;
}
const sourceFileMetaDataTree = profiler_core_1.SourceFileMetaDataTree.fromProjectReport(projectReport).filter(projectReport.asSourceNodeGraph(), undefined, undefined).node;
if (!sourceFileMetaDataTree) {
profiler_core_1.LoggerHelper.error(`SourceFileMetaDataTree could not be constructed from ProjectReport: ${reportPath}`);
continue;
}
const total = projectReport.totalAndMaxMetaData().total;
const treeSum = sourceFileMetaDataTree.aggregatedInternSourceMetaData.total.sensorValues
.aggregatedCPUTime +
sourceFileMetaDataTree.headlessSensorValues.langInternalCPUTime +
sourceFileMetaDataTree.headlessSensorValues.externCPUTime;
// const treeSum = sourceFileMetaDataTree.aggregatedInternSourceMetaData.total.sensorValues.selfCPUTime +
// sourceFileMetaDataTree.aggregatedLangInternalSourceNodeMetaData.total.sensorValues.selfCPUTime +
// sourceFileMetaDataTree.aggregatedExternSourceMetaData.total.sensorValues.selfCPUTime
const diff = total.sensorValues.aggregatedCPUTime - treeSum;
totalDiff += diff;
if (diff !== 0) {
profiler_core_1.LoggerHelper.error(`Inconsistent SourceFileMetaDataTree in report: ${reportPath.toPlatformString()}.\n` +
'Tree sum does not match total CPU time.\n', `Tree Sum: ${treeSum}`, `Total CPU Time: ${total.sensorValues.aggregatedCPUTime}`, `Difference: ${diff}`);
continue;
}
}
if (totalDiff === 0) {
profiler_core_1.LoggerHelper.success('All SourceFileMetaDataTrees are consistent.');
}
else {
profiler_core_1.LoggerHelper.error(`Total CPU time difference across all reports: ${totalDiff}`);
}
});
}
}
exports.default = JestCommands;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiSmVzdENvbW1hbmRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbW1hbmRzL0plc3RDb21tYW5kcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLHVDQUF3QjtBQUV4QixnREFBNEM7QUFDNUMsMERBYStCO0FBQy9CLHlDQUFtQztBQUVuQyxNQUFxQixZQUFZO0lBQ2hDO1FBQ0MsTUFBTSxXQUFXLEdBQUcsbUJBQU87YUFDekIsT0FBTyxDQUFDLE1BQU0sQ0FBQzthQUNmLFdBQVcsQ0FDWCwwRkFBMEYsQ0FDMUYsQ0FBQTtRQUVGLFdBQVc7YUFDVCxPQUFPLENBQUMsUUFBUSxDQUFDO2FBQ2pCLFdBQVcsQ0FDWCxvSEFBb0gsQ0FDcEg7YUFDQSxNQUFNLENBQ04sdUJBQXVCLEVBQ3ZCLGtGQUFrRixDQUNsRjthQUNBLE1BQU0sQ0FDTixZQUFZLEVBQ1osK0RBQStELENBQy9EO2FBQ0EsTUFBTSxDQUNOLGVBQWUsRUFDZixzSUFBc0ksQ0FDdEk7YUFDQSxNQUFNLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQTtRQUVoQyxXQUFXO2FBQ1QsT0FBTyxDQUFDLGtCQUFrQixDQUFDO2FBQzNCLFdBQVcsQ0FDWCxvR0FBb0csQ0FDcEc7YUFDQSxNQUFNLENBQUMsSUFBSSxDQUFDLGtCQUFrQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFBO1FBRTVDLFdBQVc7YUFDVCxPQUFPLENBQUMsY0FBYyxDQUFDO2FBQ3ZCLFdBQVcsQ0FDWCx1RkFBdUYsQ0FDdkY7YUFDQSxNQUFNLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQTtJQUN0QyxDQUFDO0lBRUQsTUFBTSxDQUFDLElBQUk7UUFDVixPQUFPLElBQUksWUFBWSxFQUFFLENBQUE7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