@oaklean/profiler
Version:
A library to measure the energy consumption of your javascript/typescript code
304 lines • 26.1 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 () {
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.WindowsSensorInterface = exports.WindowsSensorInterfaceEvent = void 0;
const fs = __importStar(require("fs"));
const child_process_1 = require("child_process");
const profiler_core_1 = require("@oaklean/profiler-core");
const windows_sensorinterface_1 = require("@oaklean/windows-sensorinterface");
const BaseSensorInterface_1 = require("../BaseSensorInterface");
/**
* This SensorInterface uses the data provided by the libre hardware monitor command line tool.
* This Provider can only be used on Windows
*/
var WindowsSensorInterfaceEvent;
(function (WindowsSensorInterfaceEvent) {
WindowsSensorInterfaceEvent["ENERGY_CPU_PACKAGE"] = "CPU Package";
WindowsSensorInterfaceEvent["ENERGY_GPU"] = "GPU Power";
})(WindowsSensorInterfaceEvent || (exports.WindowsSensorInterfaceEvent = WindowsSensorInterfaceEvent = {}));
class WindowsSensorInterface 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 = (0, windows_sensorinterface_1.getPlatformSpecificBinaryPath)('win32').toPlatformString();
this._options = options;
if (debugOptions !== undefined) {
this._startTime = debugOptions.startTime;
this._stopTime = debugOptions.stopTime;
this._offsetTime = debugOptions.offsetTime;
this._couldBeExecuted = true;
}
this._eventHandler = new profiler_core_1.EventHandler();
}
/*
Blocks until the first measurements started
*/
measurementStarted() {
return __awaiter(this, void 0, void 0, function* () {
yield this._eventHandler.waitForFirstEventCall('measurementCaptured');
});
}
type() {
return profiler_core_1.SensorInterfaceType.windows;
}
executable() {
return this._executable;
}
canBeExecuted() {
return __awaiter(this, void 0, void 0, function* () {
if (this._platform !== 'win32') {
profiler_core_1.LoggerHelper.appPrefix.error('WindowsSensorInterface: This sensor interface can only be used on Windows. Your platform:', this._platform);
return false;
}
yield windows_sensorinterface_1.InstallHelper.installPlatformSpecificPackage('win32');
return yield profiler_core_1.PermissionHelper.checkWindowsAdminRights();
});
}
commandLineArgs() {
return __awaiter(this, void 0, void 0, function* () {
return [
'samplerate=' + this._options.sampleInterval.toString(),
'filename=' + this._options.outputFilePath
];
});
}
isRunning() {
var _a;
return (((_a = this._childProcess) === null || _a === void 0 ? void 0 : _a.pid) !== undefined &&
BaseSensorInterface_1.BaseSensorInterface.pidIsRunning(this._childProcess.pid));
}
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('WindowsSensorInterface.readSensorValues: start or stop time could not be determined');
}
if (this._offsetTime === undefined) {
throw new Error('WindowsSensorInterface.readSensorValues: offset time could not be determined');
}
const content = this.getOutputContent();
if (content === undefined) {
return new profiler_core_1.MetricsDataCollection(pid, profiler_core_1.MetricsDataCollectionType.WindowsSensorInterfaceTotalSystem, [], {
startTime: this.startTime,
stopTime: this.stopTime
});
}
const lines = content.split('\n');
const data = [];
let cpu_energy = 0;
let gpu_energy = 0;
const captured = {
[WindowsSensorInterfaceEvent.ENERGY_CPU_PACKAGE]: false,
[WindowsSensorInterfaceEvent.ENERGY_GPU]: false
};
let lastDuration = this._offsetTime; // seconds
// skip first line since the first measurement is used to determine the start time
for (let i = 1; i < lines.length; i++) {
if (lines[i] === 'EOF' || lines[i] === '') {
break;
}
const values = lines[i].trim().split('|');
const duration = parseFloat(values[0].replace(/,/g, '.')) / 1e3; // seconds
const delta = duration - lastDuration;
for (let col = 1; col < values.length; col++) {
const valueType = values[col];
switch (valueType) {
case WindowsSensorInterfaceEvent.ENERGY_CPU_PACKAGE:
{
if (col + 1 < values.length && values[col + 1] !== undefined) {
cpu_energy = (parseFloat(values[++col].replace(/,/g, '.')) *
delta *
1e3);
}
captured[WindowsSensorInterfaceEvent.ENERGY_CPU_PACKAGE] = true;
}
break;
case WindowsSensorInterfaceEvent.ENERGY_GPU:
{
if (col + 1 < values.length && values[col + 1] !== undefined) {
gpu_energy = (parseFloat(values[++col].replace(/,/g, '.')) *
delta *
1e3);
}
captured[WindowsSensorInterfaceEvent.ENERGY_GPU] = true;
}
break;
default:
break;
}
}
data.push(new profiler_core_1.WindowsSensorInterfaceMetricsData({
elapsed_ns: BigInt(Math.round(delta * 1e9)), // convert into nano seconds
timestamp: (this.startTime +
BigInt(Math.ceil(duration * 1e9 - this._offsetTime * 1e9))),
cpu_energy: captured[WindowsSensorInterfaceEvent.ENERGY_CPU_PACKAGE]
? cpu_energy
: 0,
ram_energy: 0,
gpu_energy: captured[WindowsSensorInterfaceEvent.ENERGY_GPU]
? gpu_energy
: 0
}));
captured[WindowsSensorInterfaceEvent.ENERGY_CPU_PACKAGE] = false;
captured[WindowsSensorInterfaceEvent.ENERGY_GPU] = false;
lastDuration = duration;
}
return new profiler_core_1.MetricsDataCollection(pid, profiler_core_1.MetricsDataCollectionType.WindowsSensorInterfaceTotalSystem, 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* () {
var _a;
if (!(yield this.couldBeExecuted())) {
return;
}
if (fs.existsSync(this._options.outputFilePath)) {
fs.unlinkSync(this._options.outputFilePath); // remove output file to ensure clean measurements
}
this._childProcess = (0, child_process_1.spawn)(this._executable, [...(yield this.commandLineArgs())], {
detached: true
});
this.cleanExit = () => {
if (this._childProcess) {
this._childProcess.kill('SIGTERM');
}
};
// capture first measurement to determine start time
let firstCapture = false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onFirstCapture = (data) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (!firstCapture) {
const currentTime = profiler_core_1.TimeHelper.getCurrentHighResolutionTime();
const content = data.toString();
if (!content.startsWith('BEGIN_MEASUREMENT')) {
profiler_core_1.LoggerHelper.error('WindowsSensorInterface.startProfiling: ' +
'Could not capture first measurement, unexpected output:', content);
return;
}
firstCapture = true;
const values = content.trim().split(' ');
const duration = parseFloat(values[1].replace(/,/g, '.')) / 1e3; // seconds
this._offsetTime = duration;
this._startTime = currentTime;
(_b = (_a = this._childProcess) === null || _a === void 0 ? void 0 : _a.stdout) === null || _b === void 0 ? void 0 : _b.removeListener('data', onFirstCapture);
}
});
this._fileWatcher = fs.watchFile(this._options.outputFilePath, (curr, prev) => {
if (curr.size > prev.size) {
this._eventHandler.fire('measurementCaptured');
}
});
(_a = this._childProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', onFirstCapture);
process.on('exit', this.cleanExit); // add event listener to close powermetrics if the parent process exits
// detach from current node.js process
this._childProcess.unref();
const sampleInterval = this._options.sampleInterval;
// wait until the start time is set (the first measurement is captured)
return new Promise((resolve) => {
const interval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
if (this._startTime !== undefined) {
clearInterval(interval);
yield profiler_core_1.TimeHelper.sleep(1000 + sampleInterval); // wait to ensure measurements started, since the measurements only starts at full seconds
resolve();
}
}), 100);
});
});
}
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._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 powermetrics 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.WindowsSensorInterface = WindowsSensorInterface;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiV2luZG93c1NlbnNvckludGVyZmFjZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9pbnRlcmZhY2VzL3dpbmRvd3MvV2luZG93c1NlbnNvckludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSx1Q0FBd0I7QUFDeEIsaURBQW1EO0FBRW5ELDBEQVkrQjtBQUMvQiw4RUFJeUM7QUFFekMsZ0VBQTREO0FBTTVEOzs7R0FHRztBQUVILElBQVksMkJBR1g7QUFIRCxXQUFZLDJCQUEyQjtJQUN0QyxpRUFBa0MsQ0FBQTtJQUNsQyx1REFBd0IsQ0FBQTtBQUN6QixDQUFDLEVBSFcsMkJBQTJCLDJDQUEzQiwyQkFBMkIsUUFHdEM7QUFFRCxNQUFhLHNCQUF1QixTQUFRLHlDQUFtQjtJQW1COUQsWUFDQyxPQUF1QyxFQUN2QyxZQUtDOztRQUVELEtBQUssRUFBRSxDQUFBO1FBQ1AsSUFBSSxDQUFDLFNBQVMsR0FBRyxNQUFBLFlBQVksYUFBWixZQUFZLHVCQUFaLFlBQVksQ0FBRSxRQUFRLG1DQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUE7UUFDM0QsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFBLHVEQUE2QixFQUFDLE9BQU8sQ0FBQyxDQUFDLGdCQUFnQixFQUFFLENBQUE7UUFDNUUsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUE7UUFFdkIsSUFBSSxZQUFZLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDaEMsSUFBSSxDQUFDLFVBQVUsR0FBRyxZQUFZLENBQUMsU0FBUyxDQUFBO1lBQ3hDLElBQUksQ0FBQyxTQUFTLEdBQUcsWUFBWSxDQUFDLFFBQVEsQ0FBQTtZQUN0QyxJQUFJLENBQUMsV0FBVyxHQUFHLFlBQVksQ0FBQyxVQUFVLENBQUE7WUFDMUMsSUFBSSxDQUFDLGdCQUFnQixHQUFHLElBQUksQ0FBQTtRQUM3QixDQUFD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