UNPKG

@oaklean/profiler

Version:

A library to measure the energy consumption of your javascript/typescript code

323 lines 33 kB
"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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