UNPKG

@oaklean/profiler

Version:

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

263 lines 23.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.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(); const platform = (_a = debugOptions === null || debugOptions === void 0 ? void 0 : debugOptions.platform) !== null && _a !== void 0 ? _a : process.platform; if (platform !== 'win32') { throw new Error('WindowsSensorInterface: This sensor interface can only be used on Windows'); } this._executable = (0, windows_sensorinterface_1.getPlatformSpecificBinaryPath)(platform).toPlatformString(); this._options = options; if (debugOptions !== undefined) { this._startTime = debugOptions.startTime, this._stopTime = debugOptions.stopTime; this._offsetTime = debugOptions.offsetTime; this._couldBeExecuted = true; } } type() { return profiler_core_1.SensorInterfaceType.windows; } executable() { return this._executable; } canBeExecuted() { return __awaiter(this, void 0, void 0, function* () { 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() { var _a; 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 } 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 _b, _c; 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; (_c = (_b = this._childProcess) === null || _b === void 0 ? void 0 : _b.stdout) === null || _c === void 0 ? void 0 : _c.removeListener('data', onFirstCapture); } }); (_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; } yield profiler_core_1.TimeHelper.sleep(1000 + this._options.sampleInterval); // wait to capture last measurement 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiV2luZG93c1NlbnNvckludGVyZmFjZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9pbnRlcmZhY2VzL3dpbmRvd3MvV2luZG93c1NlbnNvckludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLHVDQUF3QjtBQUN4QixpREFBbUQ7QUFFbkQsMERBVytCO0FBQy9CLDhFQUd5QztBQUV6QyxnRUFBNEQ7QUFFNUQ7OztHQUdHO0FBRUgsSUFBWSwyQkFHWDtBQUhELFdBQVksMkJBQTJCO0lBQ3RDLGlFQUFrQyxDQUFBO0lBQ2xDLHVEQUF3QixDQUFBO0FBQ3pCLENBQUMsRUFIVywyQkFBMkIsMkNBQTNCLDJCQUEyQixRQUd0QztBQUVELE1BQWEsc0JBQXVCLFNBQVEseUNBQW1CO0lBYTlELFlBQVksT0FBdUMsRUFBRSxZQUtwRDs7UUFDQSxLQUFLLEVBQUUsQ0FBQTtRQUNQLE1BQU0sUUFBUSxHQUFHLE1BQUEsWUFBWSxhQUFaLFlBQVksdUJBQVosWUFBWSxDQUFFLFFBQVEsbUNBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQTtRQUMzRCxJQUFJLFFBQVEsS0FBSyxPQUFPLEVBQUUsQ0FBQztZQUMxQixNQUFNLElBQUksS0FBSyxDQUFDLDJFQUEyRSxDQUFDLENBQUE7UUFDN0YsQ0FBQztRQUNELElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBQSx1REFBNkIsRUFBQyxRQUFRLENBQUMsQ0FBQyxnQkFBZ0IsRUFBRSxDQUFBO1FBQzdFLElBQUksQ0FBQyxRQUFRLEdBQUcsT0FBTyxDQUFBO1FBRXZCLElBQUksWUFBWSxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQ2hDLElBQUksQ0FBQyxVQUFVLEdBQUcsWUFBWSxDQUFDLFNBQVM7Z0JBQ3hDLElBQUksQ0FBQyxTQUFTLEdBQUcsWUFBWSxDQUFDLFFBQVEsQ0FBQTtZQUN0QyxJQUFJLENBQUMsV0FBVyxHQUFHLFlBQVksQ0FBQyxVQUFVLENBQUE7WUFDMUMsSUFBSSxDQUFDLGdCQUFnQixHQUFHLElBQUksQ0FBQTtRQUM3QixDQUFDO0lBQ0YsQ0FBQztJQUVELElBQUk7UUFDSCxPQUFPLG1DQUFtQixDQUFDLE9BQU8sQ0FBQTtJQUNuQyxDQUFDO0lBRUQsVUFBVTtRQUNULE9BQU8sSUFBSSxDQUFDLFdBQVcsQ0FBQTtJQUN4QixDQUFDO0lBRUssYUFBYTs7WUFDbEIsT0FBTyxNQUFNLGdDQUFnQixDQUFDLHVCQUF1QixFQUFFLENBQUE7UUFDeEQsQ0FBQztLQUFBO0lBRUssZUFBZTs7WUFDcEIsT0FBTztnQkFDTixhQUFhLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxjQUFjLENBQUMsUUFBUSxFQUFFO2dCQUN2RCxXQUFXLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxjQUFjO2FBQzFDLENBQUE7UUFDRixDQUFDO0tBQUE7SUFFRCxTQUFTOztRQUNSLE9BQU8sQ0FBQSxNQUFBLElBQUksQ0FBQyxhQUFhLDBDQUFFLEdBQUcsTUFBSyxTQUFTLElBQUkseUNBQW1CLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUE7SUFDekcsQ0FBQztJQUVELGdCQUFnQjtRQUNmLElBQUksQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FBQztZQUNsRCxPQUFPLFNBQVMsQ0FBQTtRQUNqQixDQUFDO1FBRUQsT0FBTyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsY0FBYyxDQUFDLENBQUMsUUFBUSxFQUFFLENBQUE7SUFDaEUsQ0FBQztJQUVLLGdCQUFnQixDQUFDLEdBQVc7O1lBQ2pDLElBQUksQ0FBQyxDQUFBLE1BQU0sSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFBLEVBQUUsQ0FBQztnQkFDbkMsT0FBTyxTQUFTLENBQUE7WUFDakIsQ0FBQztZQUNELElBQUksS0FBSyxHQUFHLENBQUMsQ0FBQTtZQUNiLE9BQU8sSUFBSSxDQUFDLFNBQVMsRUFBRSxJQUFJLEtBQUssR0FBRyxFQUFFLEVBQUUsQ0FBQztnQkFDdkMsNEJBQVksQ0FBQyxLQUFLLENBQ2pCLHdEQUF3RCxLQUFLLEdBQUcsQ0FBQyw0QkFBNEIsQ0FBQyxDQUFBO2dCQUMvRixLQUFLLElBQUksQ0FBQyxDQUFBO2dCQUNWLE1BQU0sMEJBQVUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUE7WUFDN0IsQ0FBQztZQUVELElBQUksSUFBSSxDQUFDLFNBQVMsS0FBSyxTQUFTLElBQUksSUFBSSxDQUFDLFFBQVEsS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDakUsTUFBTSxJQUFJLEtBQUssQ0FBQyxxRkFBcUYsQ0FBQyxDQUFBO1lBQ3ZHLENBQUM7WUFDRCxJQUFJLElBQUksQ0FBQyxXQUFXLEtBQUssU0FBUyxFQUFFLENBQUM7Z0JBQ3BDLE1BQU0sSUFBSSxLQUFLLENBQUMsOEVBQThFLENBQUMsQ0FBQTtZQUNoRyxDQUFDO1lBRUQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUE7WUFDdkMsSUFBSSxPQUFPLEtBQUssU0FBUyxFQUFDLENBQUM7Z0JBQzFCLE9BQU8sSUFBSSxxQ0FBcUIsQ0FDL0IsR0FBRyxFQUNILHlDQUF5QixDQUFDLGlDQUFpQyxFQUMzRCxFQUFFLEVBQ0Y7b0JBQ0MsU0FBUyxFQUFFLElBQUksQ0FBQyxTQUFTO29CQUN6QixRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVE7aUJBQ3ZCLENBQ0QsQ0FBQTtZQUNGLENBQUM7WUFFRCxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFBO1lBR2pDLE1BQU0sSUFBSSxHQUF3QyxFQUFFLENBQUE7WUFFcEQsSUFBSSxVQUFVLEdBQXNCLENBQXNCLENBQUE7WUFDMUQsSUFBSSxVQUFVLEdBQXNCLENBQXNCLENBQUE7WUFDMUQsTUFBTSxRQUFRLEdBQUc7Z0JBQ2hCLENBQUMsMkJBQTJCLENBQUMsa0JBQWtCLENBQUMsRUFBRSxLQUFLO2dCQUN2RCxDQUFDLDJCQUEyQixDQUFDLFVBQVUsQ0FBQyxFQUFFLEtBQUs7YUFDL0MsQ0FBQTtZQUNELElBQUksWUFBWSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUEsQ0FBQyxVQUFVO1lBRTlDLGtGQUFrRjtZQUNsRixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO2dCQUN2QyxJQUFJLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxLQUFLLElBQUksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDO29CQUMzQyxNQUFLO2dCQUNOLENBQUM7Z0JBQ0QsTUFBTSxNQUFNLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQTtnQkFDekMsTUFBTSxRQUFRLEdBQUcsVUFBVSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQyxDQUFDLEdBQUcsR0FBRyxDQUFBLENBQUMsVUFBVTtnQkFFMUUsTUFBTSxLQUFLLEdBQUcsUUFBUSxHQUFHLFlBQVksQ0FBQTtnQkFFckMsS0FBSyxJQUFJLEdBQUcsR0FBRyxDQUFDLEVBQUUsR0FBRyxHQUFHLE1BQU0sQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFLEVBQUUsQ0FBQztvQkFDOUMsTUFBTSxTQUFTLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFBO29CQUM3QixRQUFRLFNBQVMsRUFBRSxDQUFDO3dCQUNuQixLQUFLLDJCQUEyQixDQUFDLGtCQUFrQjs0QkFBRSxDQUFDO2dDQUNyRCxJQUFJLEdBQUcsR0FBRyxDQUFDLEdBQUcsTUFBTSxDQUFDLE1BQU0sSUFBSSxNQUFNLENBQUMsR0FBRyxHQUFHLENBQUMsQ0FBQyxLQUFLLFNBQVMsRUFBRSxDQUFDO29DQUM5RCxVQUFVLEdBQUcsVUFBVSxDQUFDLE1BQU0sQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUMsR0FBRyxLQUFLLEdBQUcsR0FBd0IsQ0FBQTtnQ0FDN0YsQ0FBQztnQ0FDRCxRQUFRLENBQUMsMkJBQTJCLENBQUMsa0JBQWtCLENBQUMsR0FBRyxJQUFJLENBQUE7NEJBQ2hFLENBQUM7NEJBQUMsTUFBSzt3QkFDUCxLQUFLLDJCQUEyQixDQUFDLFVBQVU7NEJBQUUsQ0FBQztnQ0FDN0MsSUFBSSxHQUFHLEdBQUcsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxNQUFNLElBQUksTUFBTSxDQUFDLEdBQUcsR0FBRyxDQUFDLENBQUMsS0FBSyxTQUFTLEVBQUUsQ0FBQztvQ0FDOUQsVUFBVSxHQUFHLFVBQVUsQ0FBQyxNQUFNLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQyxDQUFDLEdBQUcsS0FBSyxHQUFHLEdBQXdCLENBQUE7Z0NBQzdGLENBQUM7Z0NBQ0QsUUFBUSxDQUFDLDJCQUEyQixDQUFDLFVBQVUsQ0FBQyxHQUFHLElBQUksQ0FBQTs0QkFDeEQsQ0FBQzs0QkFBQyxNQUFLO3dCQUNQOzRCQUNDLE1BQUs7b0JBQ1AsQ0FBQztnQkFDRixDQUFDO2dCQUVELElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxpREFBaUMsQ0FBQztvQkFDL0MsVUFBVSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssR0FBRyxHQUFHLENBQUMsQ0FBdUIsRUFBRSw0QkFBNEI7b0JBQy9GLFNBQVMsRUFDVCxDQUFDLElBQUksQ0FBQyxTQUFTLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxHQUFHLEdBQUcsR0FBRyxJQUFJLENBQUMsV0FBVyxHQUFHLEdBQUcsQ0FBQyxDQUFDLENBQXVCO29CQUNuRyxVQUFVLEVBQ1QsUUFBUSxDQUFDLDJCQUEyQixDQUFDLGtCQUFrQixDQUFDLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBc0I7b0JBQy9GLFVBQVUsRUFBRSxDQUFzQjtvQkFDbEMsVUFBVSxFQUFFLFFBQVEsQ0FBQywyQkFBMkIsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFzQjtpQkFDbEcsQ0FBQyxDQUFDLENBQUE7Z0JBQ0gsUUFBUSxDQUFDLDJCQUEyQixDQUFDLGtCQUFrQixDQUFDLEdBQUcsS0FBSyxDQUFBO2dCQUNoRSxRQUFRLENBQUMsMkJBQTJCLENBQUMsVUFBVSxDQUFDLEdBQUcsS0FBSyxDQUFBO2dCQUN4RCxZQUFZLEdBQUcsUUFBUSxDQUFBO1lBQ3hCLENBQUM7WUFFRCxPQUFPLElBQUkscUNBQXFCLENBQy9CLEdBQUcsRUFDSCx5Q0FBeUIsQ0FBQyxpQ0FBaUMsRUFDM0QsSUFBSSxFQUNKO2dCQUNDLFNBQVMsRUFBRSxJQUFJLENBQUMsU0FBUztnQkFDekIsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRO2FBQ3ZCLENBQ0QsQ0FBQTtRQUNGLENBQUM7S0FBQTtJQUVELElBQUksU0FBUztRQUNaLE9BQU8sSUFBSSxDQUFDLFVBQVUsQ0FBQTtJQUN2QixDQUFDO0lBRUQsSUFBSSxRQUFRO1FBQ1gsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFBO0lBQ3RCLENBQUM7SUFFSyxjQUFjOzs7WUFDbkIsSUFBSSxDQUFDLENBQUEsTUFBTSxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUEsRUFBRSxDQUFDO2dCQUNuQyxPQUFNO1lBQ1AsQ0FBQztZQUNELElBQUksRUFBRSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLGNBQWMsQ0FBQyxFQUFFLENBQUM7Z0JBQ2pELEVBQUUsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxjQUFjLENBQUMsQ0FBQSxDQUFDLGtEQUFrRDtZQUMvRixDQUFDO1lBRUQsSUFBSSxDQUFDLGFBQWEsR0FBRyxJQUFBLHFCQUFLLEVBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDLEdBQUcsTUFBTSxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUMsRUFBRTtnQkFDL0UsUUFBUSxFQUFFLElBQUk7YUFDZCxDQUFDLENBQUE7WUFFRixJQUFJLENBQUMsU0FBUyxHQUFHLEdBQUcsRUFBRTtnQkFDckIsSUFBSSxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUM7b0JBQ3hCLElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFBO2dCQUNuQyxDQUFDO1lBQ0YsQ0FBQyxDQUFBO1lBRUQsb0RBQW9EO1lBQ3BELElBQUksWUFBWSxHQUFHLEtBQUssQ0FBQTtZQUN4QiwrREFBK0Q7WUFDL0QsTUFBTSxjQUFjLEdBQUksQ0FBTyxJQUFTLEVBQUUsRUFBRTs7Z0JBQzNDLElBQUksQ0FBQyxZQUFZLEVBQUMsQ0FBQztvQkFDbEIsTUFBTSxXQUFXLEdBQUcsMEJBQVUsQ0FBQyw0QkFBNEIsRUFBRSxDQUFBO29CQUM3RCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUE7b0JBQy9CLElBQUksQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLG1CQUFtQixDQUFDLEVBQUUsQ0FBQzt3QkFDOUMsNEJBQVksQ0FBQyxLQUFLLENBQUMsZ0dBQWdHLEVBQUUsT0FBTyxDQUFDLENBQUE7d0JBQzdILE9BQU07b0JBQ1AsQ0FBQztvQkFDRCxZQUFZLEdBQUcsSUFBSSxDQUFBO29CQUNuQixNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsSUFBSSxFQUFFLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFBO29CQUN4QyxNQUFNLFFBQVEsR0FBRyxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUMsR0FBRyxHQUFHLENBQUEsQ0FBQyxVQUFVO29CQUMxRSxJQUFJLENBQUMsV0FBVyxHQUFHLFFBQVEsQ0FBQTtvQkFDM0IsSUFBSSxDQUFDLFVBQVUsR0FBRyxXQUFXLENBQUE7b0JBRTdCLE1BQUEsTUFBQSxJQUFJLENBQUMsYUFBYSwwQ0FBRSxNQUFNLDBDQUFFLGNBQWMsQ0FBQyxNQUFNLEVBQUUsY0FBYyxDQUFDLENBQUE7Z0JBQ25FLENBQUM7WUFDRixDQUFDLENBQUEsQ0FBQTtZQUNELE1BQUEsSUFBSSxDQUFDLGFBQWEsQ0FBQyxNQUFNLDBDQUFFLEVBQUUsQ0FBQyxNQUFNLEVBQUUsY0FBYyxDQUFDLENBQUE7WUFFckQsT0FBTyxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFBLENBQUMsdUVBQXVFO1lBRTFHLHNDQUFzQztZQUN0QyxJQUFJLENBQUMsYUFBYSxDQUFDLEtBQUssRUFBRSxDQUFBO1lBQzFCLE1BQU0sY0FBYyxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsY0FBYyxDQUFBO1lBQ25ELHVFQUF1RTtZQUN2RSxPQUFPLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLEVBQUU7Z0JBQ3BDLE1BQU0sUUFBUSxHQUFHLFdBQVcsQ0FBQyxHQUFTLEVBQUU7b0JBQ3ZDLElBQUksSUFBSSxDQUFDLFVBQVUsS0FBSyxTQUFTLEVBQUUsQ0FBQzt3QkFDbkMsYUFBYSxDQUFDLFFBQVEsQ0FBQyxDQUFBO3dCQUN2QixNQUFNLDBCQUFVLENBQUMsS0FBSyxDQUFDLElBQUksR0FBRyxjQUFjLENBQUMsQ0FBQSxDQUFDLDBGQUEwRjt3QkFDeEksT0FBTyxFQUFFLENBQUE7b0JBQ1YsQ0FBQztnQkFDRixDQUFDLENBQUEsRUFBRSxHQUFHLENBQUMsQ0FBQTtZQUNSLENBQUMsQ0FBQyxDQUFBOztLQUNGO0lBRUssYUFBYTs7WUFDbEIsSUFBSSxDQUFDLENBQUEsTUFBTSxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUEsRUFBRSxDQUFDO2dCQUNuQyxPQUFNO1lBQ1AsQ0FBQztZQUNELElBQUksSUFBSSxDQUFDLGFBQWEsS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDdEMsT0FBTTtZQUNQLENBQUM7WUFDRCxNQUFNLDBCQUFVLENBQUMsS0FBSyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLGNBQWMsQ0FBQyxDQUFBLENBQUMsbUNBQW1DO1lBQy9GLElBQUksQ0FBQyxTQUFTLEdBQUcsMEJBQVUsQ0FBQyw0QkFBNEIsRUFBRSxDQUFBO1lBQzFELElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFBO1lBQ2xDLElBQUksT0FBTyxHQUFHLENBQUMsQ0FBQTtZQUNmLE9BQU8sSUFBSSxDQUFDLFNBQVMsRUFBRSxFQUFFLENBQUM7Z0JBQ3pCLElBQUksT0FBTyxHQUFHLEVBQUUsRUFBRSxDQUFDO29CQUNsQixNQUFNLElBQUksS0FBSyxDQUFDLHNFQUFzRSxDQUFDLENBQUE7Z0JBQ3hGLENBQUM7Z0JBQ0QsTUFBTSwwQkFBVSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQTtnQkFDNUIsT0FBTyxFQUFFLENBQUE7WUFDVixDQUFDO1lBQ0QsSUFBSSxJQUFJLENBQUMsU0FBUyxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUNsQyxPQUFPLENBQUMsY0FBYyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUEsQ0FBQywwQkFBMEI7WUFDMUUsQ0FBQztRQUNGLENBQUM7S0FBQTtDQUNEO0FBN1BELHdEQTZQQyJ9