UNPKG

@oaklean/profiler

Version:

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

277 lines 22.6 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()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PerfSensorInterface = exports.PerfEvent = void 0; const fs = __importStar(require("fs")); const child_process_1 = require("child_process"); const profiler_core_1 = require("@oaklean/profiler-core"); const BaseSensorInterface_1 = require("../BaseSensorInterface"); /** * This SensorInterface uses the data provided by the perf command line tool. * This Provider can only be used on Linux with Perf installed and a CPU that supports RAPL * * Man Page to perf: * https://linux.die.net/man/1/perf */ var PerfEvent; (function (PerfEvent) { PerfEvent["ENERGY_CORES"] = "power/energy-cores/"; PerfEvent["ENERGY_RAM"] = "power/energy-ram/"; })(PerfEvent || (exports.PerfEvent = PerfEvent = {})); class PerfSensorInterface extends BaseSensorInterface_1.BaseSensorInterface { constructor(options, debugOptions) { super(); this._executable = 'perf'; this._options = options; if (debugOptions !== undefined) { this._startTime = debugOptions.startTime, this._stopTime = debugOptions.stopTime; this._couldBeExecuted = true; } } type() { return profiler_core_1.SensorInterfaceType.perf; } commandLineArgs() { return __awaiter(this, void 0, void 0, function* () { const availableMeasurementTypes = yield this.availableMeasurementTypes(); return [ 'stat', ...(availableMeasurementTypes[PerfEvent.ENERGY_CORES] ? ['-e', PerfEvent.ENERGY_CORES] : []), ...(availableMeasurementTypes[PerfEvent.ENERGY_RAM] ? ['-e', PerfEvent.ENERGY_RAM] : []), '-x', '\'|\'', '-I', this._options.sampleInterval.toString(), '-o', this._options.outputFilePath ]; }); } canBeExecuted() { return __awaiter(this, void 0, void 0, function* () { const availableMeasurementTypes = yield this.availableMeasurementTypes(); return availableMeasurementTypes[PerfEvent.ENERGY_CORES] || availableMeasurementTypes[PerfEvent.ENERGY_RAM]; }); } availableMeasurementTypes() { return __awaiter(this, void 0, void 0, function* () { if (this._availableMeasurementTypes === undefined) { this._availableMeasurementTypes = { [PerfEvent.ENERGY_CORES]: yield this.checkEventAvailability(PerfEvent.ENERGY_CORES), [PerfEvent.ENERGY_RAM]: yield this.checkEventAvailability(PerfEvent.ENERGY_RAM), }; } return this._availableMeasurementTypes; }); } checkEventAvailability(eventName) { return new Promise((resolve) => { try { const childProcess = (0, child_process_1.spawn)(this._executable + ' stat -e ' + eventName + ' -- sleep 0.001', { detached: false, stdio: 'pipe', shell: true }); childProcess.on('close', (code) => { if (code === 0) { resolve(true); } else { resolve(false); } }); } catch (_a) { resolve(false); } }); } isRunning() { var _a; return ((_a = this._childProcess) === null || _a === void 0 ? void 0 : _a.pid) !== undefined && BaseSensorInterface_1.BaseSensorInterface.pidIsRunning(this._childProcess.pid); } static runningInstances() { try { const result = (0, child_process_1.execSync)('pgrep -ix perf', { encoding: 'utf-8' }); return result.trim().split('\n'); } catch (error) { return []; } } getOutputContent() { if (!fs.existsSync(this._options.outputFilePath)) { return undefined; } return fs.readFileSync(this._options.outputFilePath).toString(); } readSensorValues(pid) { return __awaiter(this, void 0, void 0, function* () { if (!(yield this.couldBeExecuted())) { return undefined; } let tries = 0; while (this.isRunning() && tries < 10) { profiler_core_1.LoggerHelper.error(`Cannot read sensor values, wait for process to exit: ${tries + 1}, try again after 1 second`); tries += 1; yield profiler_core_1.TimeHelper.sleep(1000); } if (this.startTime === undefined || this.stopTime === undefined) { throw new Error('PerfSensorInterface.readSensorValues: start or stop time could not be determined'); } const content = this.getOutputContent(); if (content === undefined) { return new profiler_core_1.MetricsDataCollection(pid, profiler_core_1.MetricsDataCollectionType.PerfTotalSystem, [], { startTime: this.startTime, stopTime: this.stopTime }); } const lines = content.split('\n'); let lastDuration = 0; // seconds let cpu_energy = 0; let ram_energy = 0; const data = []; const availableMeasurementTypes = (yield this.availableMeasurementTypes()); const captured = { [PerfEvent.ENERGY_CORES]: false, [PerfEvent.ENERGY_RAM]: false }; for (let i = 2; i < lines.length; i++) { if (lines[i] === '') { break; } const values = lines[i].trim().replace(/'/g, '').split('|'); const duration = parseFloat(values[0].replace(/,/g, '.')); // seconds const joules = parseFloat(values[1].replace(/,/g, '.')); const type = values[3]; /** * power/energy-cores/ and power/ram/ values come alternating like this: * * 0.001105752|0,01|Joules|power/energy-cores/|1127122|100,00|| * 0.001105752|0,00|Joules|power/energy-ram/|1127583|100,00|| * 0.002236542|0,01|Joules|power/energy-cores/|1139557|100,00|| * 0.002236542|0,00|Joules|power/energy-ram/|1139006|100,00|| * */ switch (type) { case PerfEvent.ENERGY_CORES: cpu_energy = joules * 1e3; captured[PerfEvent.ENERGY_CORES] = true; break; case PerfEvent.ENERGY_RAM: ram_energy = joules * 1e3; captured[PerfEvent.ENERGY_RAM] = true; break; default: break; } if (captured[PerfEvent.ENERGY_CORES] === availableMeasurementTypes[PerfEvent.ENERGY_CORES] && captured[PerfEvent.ENERGY_RAM] === availableMeasurementTypes[PerfEvent.ENERGY_RAM]) { data.push(new profiler_core_1.PerfMetricsData({ elapsed_ns: BigInt(Math.round((duration - lastDuration) * 1e9)), // convert into nano seconds timestamp: (this.startTime + BigInt(Math.ceil(duration * 1e9))), cpu_energy: cpu_energy, ram_energy: ram_energy, })); lastDuration = duration; captured[PerfEvent.ENERGY_CORES] = false; captured[PerfEvent.ENERGY_RAM] = false; } } return new profiler_core_1.MetricsDataCollection(pid, profiler_core_1.MetricsDataCollectionType.PerfTotalSystem, data, { startTime: this.startTime, stopTime: this.stopTime }); }); } get startTime() { return this._startTime; } get stopTime() { return this._stopTime; } startProfiling() { return __awaiter(this, void 0, void 0, function* () { if (!(yield this.couldBeExecuted())) { return; } if (fs.existsSync(this._options.outputFilePath)) { fs.unlinkSync(this._options.outputFilePath); // remove output file to ensure clean measurements } if (PerfSensorInterface.runningInstances().length > 0) { throw new Error('PerfSensorInterface.startProfiling: Perf instance already running, close it before taking any measurements'); } this._startTime = profiler_core_1.TimeHelper.getCurrentHighResolutionTime(); this._childProcess = (0, child_process_1.spawn)(this._executable, [...yield this.commandLineArgs()], { detached: true }); this.cleanExit = () => { if (this._childProcess) { this._childProcess.kill('SIGTERM'); } }; process.on('exit', this.cleanExit); // add event listener to close perf if the parent process exits // detach from current node.js process this._childProcess.unref(); yield profiler_core_1.TimeHelper.sleep(1000 + this._options.sampleInterval); // wait to ensure measurements started, since the measurements only starts at full seconds }); } stopProfiling() { return __awaiter(this, void 0, void 0, function* () { if (!(yield this.couldBeExecuted())) { return; } if (this._childProcess === undefined) { return; } yield profiler_core_1.TimeHelper.sleep(1000 + this._options.sampleInterval); // wait to capture last measurement this._childProcess.kill('SIGIO'); // flush all buffered output this._stopTime = profiler_core_1.TimeHelper.getCurrentHighResolutionTime(); this._childProcess.kill('SIGTERM'); let seconds = 0; while (this.isRunning()) { if (seconds > 10) { throw new Error('Waited 10 seconds for perf to shut down, it is still running'); } yield profiler_core_1.TimeHelper.sleep(1000); seconds++; } if (this.cleanExit !== undefined) { process.removeListener('exit', this.cleanExit); // clean up event listener } }); } } exports.PerfSensorInterface = PerfSensorInterface; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUGVyZlNlbnNvckludGVyZmFjZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9pbnRlcmZhY2VzL3BlcmYvUGVyZlNlbnNvckludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLHVDQUF3QjtBQUN4QixpREFBNkQ7QUFFN0QsMERBVStCO0FBRS9CLGdFQUE0RDtBQUU1RDs7Ozs7O0dBTUc7QUFFSCxJQUFZLFNBR1g7QUFIRCxXQUFZLFNBQVM7SUFDcEIsaURBQW9DLENBQUE7SUFDcEMsNkNBQWdDLENBQUE7QUFDakMsQ0FBQyxFQUhXLFNBQVMseUJBQVQsU0FBUyxRQUdwQjtBQU9ELE1BQWEsbUJBQW9CLFNBQVEseUNBQW1CO0lBWTNELFlBQVksT0FBb0MsRUFBRSxZQUdqRDtRQUNBLEtBQUssRUFBRSxDQUFBO1FBQ1AsSUFBSSxDQUFDLFdBQVcsR0FBRyxNQUFNLENBQUE7UUFDekIsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUE7UUFFdkIsSUFBSSxZQUFZLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDaEMsSUFBSSxDQUFDLFVBQVUsR0FBRyxZQUFZLENBQUMsU0FBUztnQkFDeEMsSUFBSSxDQUFDLFNBQVMsR0FBRyxZQUFZLENBQUMsUUFBUSxDQUFBO1lBQ3RDLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxJQUFJLENBQUE7UUFDN0IsQ0FBQztJQUNGLENBQUM7SUFFRCxJQUFJO1FBQ0gsT0FBTyxtQ0FBbUIsQ0FBQyxJQUFJLENBQUE7SUFDaEMsQ0FBQztJQUVLLGVBQWU7O1lBQ3BCLE1BQU0seUJBQXlCLEdBQUcsTUFBTSxJQUFJLENBQUMseUJBQXlCLEVBQUUsQ0FBQTtZQUN4RSxPQUFPO2dCQUNOLE1BQU07Z0JBQ04sR0FBRyxDQUFDLHlCQUF5QixDQUFDLFNBQVMsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJ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