@oaklean/profiler
Version:
A library to measure the energy consumption of your javascript/typescript code
323 lines • 33 kB
JavaScript
"use strict";
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 (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__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());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Profiler = void 0;
const fs = __importStar(require("fs"));
const inspector_1 = require("inspector");
const seedrandom_1 = __importDefault(require("seedrandom"));
const profiler_core_1 = require("@oaklean/profiler-core");
const V8Profiler_1 = require("./model/V8Profiler");
const TraceEventHelper_1 = require("./helper/TraceEventHelper");
const PowerMetricsSensorInterface_1 = require("./interfaces/powermetrics/PowerMetricsSensorInterface");
const PerfSensorInterface_1 = require("./interfaces/perf/PerfSensorInterface");
const WindowsSensorInterface_1 = require("./interfaces/windows/WindowsSensorInterface");
class Profiler {
constructor(subOutputDir, options) {
this.subOutputDir = subOutputDir;
this.config = profiler_core_1.ProfilerConfig.autoResolve();
this.options = options;
this.loadSensorInterface();
}
static getSensorInterface(config) {
const sensorInterfaceType = config.getSensorInterfaceType();
switch (sensorInterfaceType) {
case 'powermetrics': {
const options = config.getSensorInterfaceOptions();
if (options === undefined) {
throw new Error('Profiler.getSensorInterface: sensorInterfaceOptions are not defined');
}
options.outputFilePath = config.getOutDir().join(options.outputFilePath).toPlatformString();
return new PowerMetricsSensorInterface_1.PowerMetricsSensorInterface(options);
}
case 'perf': {
const options = config.getSensorInterfaceOptions();
if (options === undefined) {
throw new Error('Profiler.getSensorInterface: sensorInterfaceOptions are not defined');
}
options.outputFilePath = config.getOutDir().join(options.outputFilePath).toPlatformString();
return new PerfSensorInterface_1.PerfSensorInterface(options);
}
case 'windows': {
const options = config.getSensorInterfaceOptions();
if (options === undefined) {
throw new Error('Profiler.getSensorInterface: sensorInterfaceOptions are not defined');
}
options.outputFilePath = config.getOutDir().join(options.outputFilePath).toPlatformString();
return new WindowsSensorInterface_1.WindowsSensorInterface(options);
}
}
}
loadSensorInterface() {
this._sensorInterface = Profiler.getSensorInterface(this.config);
}
static inject(subOutputDir) {
return __awaiter(this, void 0, void 0, function* () {
const profiler = new Profiler(subOutputDir);
const title = new Date().getTime().toString();
const exitResolve = () => resolve('exit');
const sigIntResolve = () => resolve('SIGINT');
const sigUsr1Resolve = () => resolve('SIGUSR1');
const sigUsr2Resolve = () => resolve('SIGUSR2');
let stopped = false;
function resolve(origin) {
return __awaiter(this, void 0, void 0, function* () {
if (!stopped) {
stopped = true;
profiler_core_1.LoggerHelper.log(`(${profiler_core_1.APP_NAME} Profiler) Finish Measurement, please wait...`);
yield profiler.finish(title);
process.removeListener('exit', exitResolve);
process.removeListener('SIGINT', sigIntResolve);
process.removeListener('SIGUSR1', sigUsr1Resolve);
process.removeListener('SIGUSR2', sigUsr2Resolve);
if (origin !== 'exit') {
process.exit();
}
}
});
}
profiler_core_1.LoggerHelper.log(`(${profiler_core_1.APP_NAME} Profiler) Measurement started`);
yield profiler.start(title);
process.on('exit', exitResolve);
// //catches ctrl+c event
process.on('SIGINT', sigIntResolve);
// // catches "kill pid" (for example: nodemon restart)
process.on('SIGUSR1', sigUsr1Resolve);
process.on('SIGUSR2', sigUsr2Resolve);
return profiler;
});
}
startCapturingProfilerTracingEvents() {
return __awaiter(this, void 0, void 0, function* () {
if (this._traceEventSession !== undefined) {
throw new Error('startCapturingProfilerTracingEvents: Trace Event Session should not already be defined');
}
const session = new inspector_1.Session();
this._traceEventSession = session;
session.connect();
session.on('NodeTracing.dataCollected', (chunk) => {
for (const event of chunk.params.value) {
if (event.pid === process.pid && event.cat === 'v8') {
if (event.name === 'CpuProfiler::StartProfiling') { // captured start event of cpu profiler
this._profilerStartTime = event.ts; // store high resolution begin time
}
}
}
});
const traceConfig = { includedCategories: ['v8'] }; // config to capture v8's trace events
yield TraceEventHelper_1.TraceEventHelper.post(session, 'NodeTracing.start', { traceConfig }); // start trace event capturing
});
}
stopCapturingProfilerTracingEvents() {
return __awaiter(this, void 0, void 0, function* () {
if (this._traceEventSession === undefined) {
throw new Error('stopCapturingProfilerTracingEvents: Trace Event Session should be defined');
}
yield TraceEventHelper_1.TraceEventHelper.post(this._traceEventSession, 'NodeTracing.stop', undefined);
this._traceEventSession.disconnect();
this._traceEventSession = undefined;
});
}
getCPUProfilerBeginTime() {
return __awaiter(this, void 0, void 0, function* () {
let tries = 0;
while (this._profilerStartTime === undefined && tries < 10) {
profiler_core_1.LoggerHelper.error(`Cannot capture profiler start time on try: ${tries + 1}, try again after 1 second`);
tries += 1;
yield profiler_core_1.TimeHelper.sleep(1000);
}
if (this._profilerStartTime === undefined) {
throw new Error(`Could not capture cpu profilers begin time after ${tries} tries, measurements failed`);
}
return this._profilerStartTime;
});
}
start(title, executionDetails) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const performance = new profiler_core_1.PerformanceHelper();
performance.start('Profiler.start');
const outFileReport = this.outputReportPath(title);
const outDir = outFileReport.dirName();
performance.start('Profiler.start.createOutDir');
if (!fs.existsSync(outDir.toPlatformString())) {
profiler_core_1.PermissionHelper.mkdirRecursivelyWithUserPermission(outDir);
}
performance.stop('Profiler.start.createOutDir');
performance.start('Profiler.start.seedRandom');
const mathRandomSeed = this.config.getSeedForMathRandom();
if (mathRandomSeed) {
(0, seedrandom_1.default)(mathRandomSeed, { global: true });
}
performance.stop('Profiler.start.seedRandom');
if (executionDetails) {
this.executionDetails = executionDetails;
}
else {
performance.start('Profiler.start.resolveExecutionDetails');
this.executionDetails = yield profiler_core_1.ExecutionDetails.resolveExecutionDetails();
performance.stop('Profiler.start.resolveExecutionDetails');
}
performance.start('Profiler.start.V8Profiler.setGenerateType');
V8Profiler_1.V8Profiler.setGenerateType(1); // must be set to generate new cpuprofile format
performance.stop('Profiler.start.V8Profiler.setGenerateType');
performance.start('Profiler.start.getV8CPUSamplingInterval');
V8Profiler_1.V8Profiler.setSamplingInterval(this.config.getV8CPUSamplingInterval()); // sets the sampling interval in microseconds
performance.stop('Profiler.start.getV8CPUSamplingInterval');
performance.start('Profiler.start.startCapturingProfilerTracingEvents');
yield this.startCapturingProfilerTracingEvents();
performance.stop('Profiler.start.startCapturingProfilerTracingEvents');
performance.start('Profiler.start.sensorInterface.couldBeExecuted');
if (this._sensorInterface !== undefined && !(yield this._sensorInterface.couldBeExecuted())) {
// remove sensor interface from execution details since it cannot be executed
this.executionDetails.runTimeOptions.sensorInterface = undefined;
profiler_core_1.LoggerHelper.warn(`(${profiler_core_1.APP_NAME} Profiler) Warning: ` +
'Sensor Interface can not be executed, no energy measurements will be collected');
}
performance.stop('Profiler.start.sensorInterface.couldBeExecuted');
performance.start('Profiler.start.sensorInterface.startProfiling');
yield ((_a = this._sensorInterface) === null || _a === void 0 ? void 0 : _a.startProfiling());
performance.stop('Profiler.start.sensorInterface.startProfiling');
// title - handle to stop profile again
// recsampels(boolean) - record samples, if false no cpu times will be captured
performance.start('Profiler.start.V8Profiler.startProfiling');
V8Profiler_1.V8Profiler.startProfiling(title, true);
performance.stop('Profiler.start.V8Profiler.startProfiling');
performance.stop('Profiler.start');
performance.printReport('Profiler.start');
performance.exportAndSum(this.outputDir().join('performance.json'));
});
}
outputDir() {
return this.config.getOutDir().join(this.subOutputDir || '');
}
outputReportPath(title) {
return this.outputDir().join(`${title}.oak`);
}
outputMetricCollectionPath(title) {
return this.outputDir().join(`${title}.mcollection`);
}
outputProfilePath(title) {
return this.outputDir().join(`${title}.cpuprofile`);
}
finish(title, highResolutionStopTime) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
const performance = new profiler_core_1.PerformanceHelper();
performance.start('Profiler.finish');
if (this.executionDetails === undefined) {
throw new Error('Profiler.finish: Profiler was not started yet');
}
if (highResolutionStopTime !== undefined) {
this.executionDetails.highResolutionStopTime = highResolutionStopTime.toString();
}
performance.start('Profiler.finish.stopProfiling');
const profile = V8Profiler_1.V8Profiler.stopProfiling(title);
performance.stop('Profiler.finish.stopProfiling');
performance.start('Profiler.finish.stopCapturingProfilerTracingEvents');
this.stopCapturingProfilerTracingEvents();
performance.stop('Profiler.finish.stopCapturingProfilerTracingEvents');
performance.start('Profiler.finish.sensorInterface.stopProfiling');
yield ((_a = this._sensorInterface) === null || _a === void 0 ? void 0 : _a.stopProfiling());
performance.stop('Profiler.finish.sensorInterface.stopProfiling');
const CPUProfilerBeginTime = BigInt(yield this.getCPUProfilerBeginTime()) * BigInt(1000);
this.executionDetails.highResolutionBeginTime = CPUProfilerBeginTime.toString();
const exportData = {
nodes: profile.nodes,
startTime: profile.startTime,
endTime: profile.endTime,
samples: profile.samples,
timeDeltas: profile.timeDeltas
};
let transformerAdapter = undefined;
if (((_b = this.options) === null || _b === void 0 ? void 0 : _b.transformerAdapter) === 'ts-jest') {
if (!this.options.jestAdapter.config || !this.options.jestAdapter.context) {
throw new Error('Please provide the JestEnvironmentConfig and EnvironmentContext in the profiler options at options.jestAdapter');
}
transformerAdapter = new profiler_core_1.JestAdapter(this.options.jestAdapter.config, this.options.jestAdapter.context);
if (!fs.existsSync(this.outputDir().toPlatformString())) {
profiler_core_1.PermissionHelper.mkdirRecursivelyWithUserPermission(this.outputDir());
}
performance.start('Profiler.finish.exportJestConfig');
profiler_core_1.PermissionHelper.writeFileWithUserPermission(this.outputDir().join('jest.config').toPlatformString(), JSON.stringify({
config: this.options.jestAdapter.config,
context: this.options.jestAdapter.context
}));
performance.stop('Profiler.finish.exportJestConfig');
}
const outFileCPUProfile = this.outputProfilePath(title);
const outFileReport = this.outputReportPath(title);
const outFileMetricCollection = this.outputMetricCollectionPath(title);
if (this.config.shouldExportV8Profile()) {
performance.start('Profiler.finish.exportV8Profile');
profiler_core_1.PermissionHelper.writeFileWithUserPermission(outFileCPUProfile.toPlatformString(), JSON.stringify(exportData, null, 2));
performance.stop('Profiler.finish.exportV8Profile');
}
performance.start('Profiler.finish.sensorInterface.readSensorValues');
const metricsDataCollection = yield ((_c = this._sensorInterface) === null || _c === void 0 ? void 0 : _c.readSensorValues(process.pid));
performance.stop('Profiler.finish.sensorInterface.readSensorValues');
const rootDir = this.config.getRootDir();
const report = new profiler_core_1.ProjectReport(this.executionDetails, profiler_core_1.ReportKind.measurement);
if (this.config.shouldExportSensorInterfaceData()) {
if (metricsDataCollection !== undefined) {
performance.start('Profiler.finish.exportMetricsDataCollection');
metricsDataCollection.storeToFile(outFileMetricCollection);
performance.stop('Profiler.finish.exportMetricsDataCollection');
}
}
performance.start('Profiler.finish.insertCPUProfile');
yield report.insertCPUProfile(rootDir, profile, transformerAdapter, metricsDataCollection);
performance.stop('Profiler.finish.insertCPUProfile');
performance.start('Profiler.finish.trackUncommittedFiles');
yield report.trackUncommittedFiles(rootDir);
performance.stop('Profiler.finish.trackUncommittedFiles');
if (this.config.shouldExportReport()) {
performance.start('Profiler.finish.exportReport');
report.storeToFile(outFileReport, 'bin', this.config);
performance.stop('Profiler.finish.exportReport');
}
if (yield report.shouldBeStoredInRegistry()) {
yield report.uploadToRegistry(this.config);
}
performance.stop('Profiler.finish');
performance.printReport('Profiler.finish');
performance.exportAndSum(this.outputDir().join('performance.json'));
return report;
});
}
}
exports.Profiler = Profiler;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUHJvZmlsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvUHJvZmlsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSx1Q0FBd0I7QUFDeEIseUNBQW1DO0FBRW5DLDREQUFtQztBQUNuQywwREFlK0I7QUFHL0IsbURBQStDO0FBQy9DLGdFQUE0RDtBQUU1RCx1R0FBbUc7QUFDbkcsK0VBQTJFO0FBQzNFLHdGQUFvRjtBQXdCcEYsTUFBYSxRQUFRO0lBVXBCLFlBQ0MsWUFBcUIsRUFDckIsT0FBeUI7UUFFekIsSUFBSSxDQUFDLFlBQVksR0FBRyxZQUFZLENBQUE7UUFDaEMsSUFBSSxDQUFDLE1BQU0sR0FBRyw4QkFBYyxDQUFDLFdBQVcsRUFBRSxDQUFBO1FBQzFDLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFBO1FBQ3RCLElBQUksQ0FBQyxtQkFBbUIsRUFBRSxDQUFBO0lBQzNCLENBQUM7SUFFRCxNQUFNLENBQUMsa0JBQWtCLENBQUMsTUFBc0I7UUFDL0MsTUFBTSxtQkFBbUIsR0FBRyxNQUFNLENBQUMsc0JBQXNCLEVBQUUsQ0FBQTtRQUMzRCxRQUFRLG1CQUFtQixFQUFFLENBQUM7WUFDN0IsS0FBSyxjQUFjLENBQUMsQ0FBQyxDQUFDO2dCQUNyQixNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMseUJBQXlCLEVBQUUsQ0FBQTtnQkFDbEQsSUFBSSxPQUFPLEtBQUssU0FBUyxFQUFFLENBQUM7b0JBQzNCLE1BQU0sSUFBSSxLQUFLLENBQUMscUVBQXFFLENBQUMsQ0FBQTtnQkFDdkYsQ0FBQztnQkFDRCxPQUFPLENBQUMsY0FBYyxHQUFHLE1BQU0sQ0FBQyxTQUFTLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLGNBQWMsQ0FBQyxDQUFDLGdCQUFnQixFQUFFLENBQUE7Z0JBQzNGLE9BQU8sSUFBSSx5REFBMkIsQ0FBQyxPQUFPLENB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