@oaklean/profiler
Version:
A library to measure the energy consumption of your javascript/typescript code
323 lines • 25.5 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 });
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) {
var _a;
super();
this._platform = (_a = debugOptions === null || debugOptions === void 0 ? void 0 : debugOptions.platform) !== null && _a !== void 0 ? _a : process.platform;
this._executable = 'perf';
this._options = options;
if (debugOptions !== undefined) {
this._startTime = debugOptions.startTime;
this._stopTime = debugOptions.stopTime;
this._couldBeExecuted = true;
}
this._eventHandler = new profiler_core_1.EventHandler();
}
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* () {
if (this._platform !== 'linux') {
profiler_core_1.LoggerHelper.appPrefix.error('PerfSensorInterface: This sensor interface can only be used on Linux. Your platform:', this._platform);
return false;
}
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');
// eslint-disable-next-line @typescript-eslint/no-unused-vars
}
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');
}
};
this._fileWatcher = fs.watchFile(this._options.outputFilePath, (curr, prev) => {
if (curr.size > prev.size) {
this._eventHandler.fire('measurementCaptured');
}
});
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
});
}
/*
Blocks until the first measurements started
*/
measurementStarted() {
return __awaiter(this, void 0, void 0, function* () {
yield this._eventHandler.waitForFirstEventCall('measurementCaptured');
});
}
stopProfiling() {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield this.couldBeExecuted())) {
return;
}
if (this._childProcess === undefined) {
return;
}
// wait to capture last measurement
yield this._eventHandler.awaitEventCall('measurementCaptured');
if (this._fileWatcher !== undefined) {
fs.unwatchFile(this._options.outputFilePath);
}
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUGVyZlNlbnNvckludGVyZmFjZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9pbnRlcmZhY2VzL3BlcmYvUGVyZlNlbnNvckludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSx1Q0FBd0I7QUFDeEIsaURBQTZEO0FBRTdELDBEQVcrQjtBQUUvQixnRUFBNEQ7QUFFNUQ7Ozs7OztHQU1HO0FBRUgsSUFBWSxTQUdYO0FBSEQsV0FBWSxTQUFTO0lBQ3BCLGlEQUFvQyxDQUFBO0lBQ3BDLDZDQUFnQyxDQUFBO0FBQ2pDLENBQUMsRUFIVyxTQUFTLHlCQUFULFNBQVMsUUFHcEI7QUFXRCxNQUFhLG1CQUFvQixTQUFRLHlDQUFtQjtJQWtCM0QsWUFDQyxPQUFvQyxFQUNwQyxZQUlDOztRQUVELEtBQUssRUFBRSxDQUFBO1FBQ1AsSUFBSSxDQUFDLFNBQVMsR0FBRyxNQUFBLFlBQVksYUFBWixZQUFZLHVCQUFaLFlBQVksQ0FBRSxRQUFRLG1DQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUE7UUFDM0QsSUFBSSxDQUFDLFdBQVcsR0FBRyxNQUFNLENBQUE7UUFDekIsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUE7UUFFdkIsSUFBSSxZQUFZLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDaEMsSUFBSSxDQUFDLFVBQVUsR0FBRyxZQUFZLENBQUMsU0FBUyxDQUFBO1lBQ3hDLElBQUksQ0FBQyxTQUFTLEdBQUcsWUFBWSxDQUFDLFFBQVEsQ0FBQTtZQUN0QyxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsSUFBSSxDQUFBO1FBQzdCLENBQUM7UUFDRCxJQUFJLENBQUMsYUFBYSxHQUFHLElBQUksNEJBQVksRUFBRSxDQUFBO0lBQ3hDLENBQUM7SUFFRCxJQUFJO1FBQ0gsT0FBTyxtQ0FBbUIsQ0FBQyxJQUFJLENBQUE7SUFDaEMsQ0FBQztJ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