UNPKG

@oaklean/profiler

Version:

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

243 lines 18.4 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 () { 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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PowerMetricsSensorInterface = void 0; const fs = __importStar(require("fs")); const child_process_1 = require("child_process"); const plist_1 = __importDefault(require("plist")); const profiler_core_1 = require("@oaklean/profiler-core"); const BaseSensorInterface_1 = require("../BaseSensorInterface"); /** * This SensorInterface uses the data provided by the powermetrics command line tool. * This SensorInterface can only be used on Mac OS * * Man Page to powermetrics: * https://www.unix.com/man-page/osx/1/powermetrics/ */ class PowerMetricsSensorInterface 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 = 'powermetrics'; this._options = options; this._commandLineArgs = [ '--sample-rate', this._options.sampleInterval.toString(), '--buffer-size', '0', '--show-process-energy', '-f', 'plist', '-o', this._options.outputFilePath ]; 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.powermetrics; } canBeExecuted() { return new Promise((resolve) => { if (this._platform !== 'darwin') { profiler_core_1.LoggerHelper.appPrefix.error('PowerMetricsSensorInterface: This sensor interface can only be used on MacOS. Your platform:', this._platform); resolve(false); return; } try { const childProcess = (0, child_process_1.spawn)(this._executable, { detached: false, stdio: 'pipe' }); let isExecutable = false; childProcess.once('error', () => { resolve(false); }); childProcess.stderr.once('data', () => { childProcess.kill('SIGTERM'); isExecutable = false; }); childProcess.stdout.once('data', () => { childProcess.kill('SIGTERM'); isExecutable = true; }); childProcess.once('exit', () => { resolve(isExecutable); }); } 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 powermetrics', { encoding: 'utf-8' }); return result.trim().split('\n'); // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return []; } } 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('PowerMetricsSensorInterface.readSensorValues: start or stop time could not be determined'); } if (!fs.existsSync(this._options.outputFilePath) || !(yield this.canBeExecuted())) { return new profiler_core_1.MetricsDataCollection(pid, profiler_core_1.MetricsDataCollectionType.PowerMetricsPerProcess, [], { startTime: this.startTime, stopTime: this.stopTime }); } const content = fs.readFileSync(this._options.outputFilePath).toString(); const contents = content.split('\x00'); const data = contents.map((content) => new profiler_core_1.PowerMetricsData(plist_1.default.parse(content))); return new profiler_core_1.MetricsDataCollection(pid, profiler_core_1.MetricsDataCollectionType.PowerMetricsPerProcess, 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 (PowerMetricsSensorInterface.runningInstances().length > 0) { throw new Error('PowerMetricsSensorInterface.startProfiling: ' + 'PowerMetrics 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, [...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 powermetrics if the parent process exits // detach from current node.js process this._childProcess.unref(); }); } /* 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 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.PowerMetricsSensorInterface = PowerMetricsSensorInterface; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUG93ZXJNZXRyaWNzU2Vuc29ySW50ZXJmYWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2ludGVyZmFjZXMvcG93ZXJtZXRyaWNzL1Bvd2VyTWV0cmljc1NlbnNvckludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSx1Q0FBd0I7QUFDeEIsaURBQTZEO0FBRTdELGtEQUF5QjtBQUN6QiwwREFXK0I7QUFFL0IsZ0VBQTREO0FBTTVEOzs7Ozs7R0FNRztBQUNILE1BQWEsMkJBQTRCLFNBQVEseUNBQW1CO0lBa0JuRSxZQUNDLE9BQTRDLEVBQzVDLFlBSUM7O1FBRUQsS0FBSyxFQUFFLENBQUE7UUFDUCxJQUFJLENBQUMsU0FBUyxHQUFHLE1BQUEsWUFBWSxhQUFaLFlBQVksdUJBQVosWUFBWSxDQUFFLFFBQVEsbUNBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQTtRQUMzRCxJQUFJLENBQUMsV0FBVyxHQUFHLGNBQWMsQ0FBQTtRQUNqQyxJQUFJLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQTtRQUN2QixJQUFJLENBQUMsZ0JBQWdCLEdBQUc7WUFDdkIsZUFBZTtZQUNmLElBQUksQ0FBQyxRQUFRLENBQUMsY0FBYyxDQUFDLFFBQVEsRUFBRTtZQUN2QyxlQUFlO1lBQ2YsR0FBRztZQUNILHVCQUF1QjtZQUN2QixJQUFJO1lBQ0osT0FBTztZQUNQLElBQUk7WUFDSixJQUFJLENBQUMsUUFBUSxDQUFDLGNBQWM7U0FDNUIsQ0FBQTtRQUNELElBQUksWUFBWSxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQ2hDLElBQUksQ0FBQyxVQUFVLEdBQUcsWUFBWSxDQUFDLFNBQVMsQ0FBQTtZQUN4QyxJQUFJLENBQUMsU0FBUyxHQUFHLFlBQVksQ0FBQyxRQUFRLENBQUE7WUFDdEMsSUFBSSxDQUFDLGdCQUFnQixHQUFHLElBQUksQ0FBQTtRQUM3QixDQUFDO1FBQ0QsSUFBSSxDQUFDLGFBQWEsR0FBRyxJQUFJLDRCQUFZLEVBQUUsQ0FBQTtJQUN4QyxDQUFDO0lBRUQsSUFBSTtRQUNILE9BQU8sbUNBQW1CLENBQUMsWUFBWSxDQUFBO0lBQ3hDLENBQUM7SUFFRCxhQUFhO1FBQ1osT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFO1lBQzlCLElBQUksSUFBSSxDQUFDLFNBQVMsS0FBSyxRQUFRLEVBQUUsQ0FBQztnQkFDakMsNEJBQVksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUMzQiw4RkFBOEYsRUFDOUYsSUFBSSxDQUFDLFNBQVMsQ0FDZCxDQUFBO2dCQUNELE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQTtnQkFDZCxPQUFNO1lBQ1AsQ0FBQztZQUNELElBQUksQ0FBQztnQkFDSixNQUFNLFlBQVksR0FBRyxJQUFBLHFCQUFLLEVBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRTtvQkFDNUMsUUFBUSxFQUFFLEtBQUs7b0JBQ2YsS0FBSyxFQUFFLE1BQU07aUJBQ2IsQ0FBQyxDQUFBO2dCQUNGLElBQUksWUFBWSxHQUFHLEtBQUssQ0FBQTtnQkFFeEIsWUFBWSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsR0FBRyxFQUFFO29CQUMvQixPQUFPLENBQUMsS0FBSyxDQUFDLENBQUE7Z0JBQ2YsQ0FBQyxDQUFDLENBQUE7Z0JBRUYsWUFBWSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEdBQUcsRUFBRTtvQkFDckMsWUFBWSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQTtvQkFDNUIsWUFBWSxHQUFHLEtBQUssQ0FBQTtnQkFDckIsQ0FBQyxDQUFDLENBQUE7Z0JBRUYsWUFBWSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEdBQUcsRUFBRTtvQkFDckMsWUFBWSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQTtvQkFDNUIsWUFBWSxHQUFHLElBQUksQ0FBQTtnQkFDcEIsQ0FBQyxDQUFDLENBQUE7Z0JBRUYsWUFBWSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFO29CQUM5QixPQUFPLENBQUMsWUFBWSxDQUFDLENBQUE7Z0JBQ3RCLENBQUMsQ0FBQyxDQUFBO1lBQ0gsQ0FBQztZQUFDLFdBQU0sQ0FBQztnQkFDUixPQUFPLENBQUMsS0FBSyxDQUFDLENBQUE7WUFDZixDQUFDO1FBQ0YsQ0FBQyxDQUFDLENBQUE7SUFDSCxDQUFDO0lBRUQsU0FBUzs7UUFDUixPQUFPLENBQ04sQ0FBQSxNQUFBLElBQUksQ0FBQyxhQUFhLDBDQUFFLEdBQUcsTUFBSyxTQUFTO1lBQ3JDLHlDQUFtQixDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLEdBQUcsQ0FBQyxDQUN4RCxDQUFBO0lBQ0YsQ0FBQztJQUVELE1BQU0sQ0FBQyxnQkFBZ0I7UUFDdEIsSUFBSSxDQUFDO1lBQ0osTUFBTSxNQUFNLEdBQUcsSUFBQSx3QkFBUSxFQUFDLHdCQUF3QixFQUFFLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUE7WUFDeEUsT0FBTyxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFBO1lBQ2hDLDZEQUE2RDtRQUM5RCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNoQixPQUFPLEVBQUUsQ0FBQTtRQUNWLENBQUM7SUFDRixDQUFDO0lBRUssZ0JBQWdCLENBQ3JCLEdBQVc7O1lBRVgsSUFBSSxDQUFDLENBQUMsTUFBTSxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUMsRUFBRSxDQUFDO2dCQUNyQyxPQUFPLFNBQVMsQ0FBQTtZQUNqQixDQUFDO1lBQ0QsSUFBSSxLQUFLLEdBQUcsQ0FBQyxDQUFBO1lBQ2IsT0FBTyxJQUFJLENBQUMsU0FBUyxFQUFFLElBQUksS0FBSyxHQUFHLEVBQUUsRUFBRSxDQUFDO2dCQUN2Qyw0QkFBWSxDQUFDLEtBQUssQ0FDakIsd0RBQXdELEtBQUssR0FBRyxDQUFDLDRCQUE0QixDQUM3RixDQUFBO2dCQUNELEtBQUssSUFBSSxDQUFDLENBQUE7Z0JBQ1YsTUFBTSwwQkFBVSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQTtZQUM3QixDQUFDO1lBRUQsSUFBSSxJQUFJLENBQUMsU0FBUyxLQUFLLFNBQVMsSUFBSSxJQUFJLENBQUMsUUFBUSxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUNqRSxNQUFNLElBQUksS0FBSyxDQUNkLDBGQUEwRixDQUMxRixDQUFBO1lBQ0YsQ0FBQztZQUVELElBQ0MsQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsY0FBYyxDQUFDO2dCQUM1QyxDQUFDLENBQUMsTUFBTSxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUMsRUFDNUIsQ0FBQztnQkFDRixPQUFPLElBQUkscUNBQXFCLENBQy9CLEdBQUcsRUFDSCx5Q0FBeUIsQ0FBQyxzQkFBc0IsRUFDaEQsRUFBRSxFQUNGO29CQUNDLFNBQVMsRUFBRSxJQUFJLENBQUMsU0FBUztvQkFDekIsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRO2lCQUN2QixDQUNELENBQUE7WUFDRixDQUFDO1lBRUQsTUFBTSxPQUFPLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLGNBQWMsQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFBO1lBQ3hFLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUE7WUFFdEMsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FDeEIsQ0FBQyxPQUFlLEVBQUUsRUFBRSxDQUNuQixJQUFJLGdDQUFnQixDQUNuQixlQUFLLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBeUMsQ0FDNUQsQ0FDRixDQUFBO1lBRUQsT0FBTyxJQUFJLHFDQUFxQixDQUMvQixHQUFHLEVBQ0gseUNBQXlCLENBQUMsc0JBQXNCLEVBQ2hELElBQUksRUFDSjtnQkFDQyxTQUFTLEVBQUUsSUFBSSxDQUFDLFNBQVM7Z0JBQ3pCLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTthQUN2QixDQUNELENBQUE7UUFDRixDQUFDO0tBQUE7SUFFRCxJQUFJLFNBQVM7UUFDWixPQUFPLElBQUksQ0FBQyxVQUFVLENBQUE7SUFDdkIsQ0FBQztJQUVELElBQUksUUFBUTtRQUNYLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQTtJQUN0QixDQUFDO0lBRUssY0FBYzs7WUFDbkIsSUFBSSxDQUFDLENBQUMsTUFBTSxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUMsRUFBRSxDQUFDO2dCQUNyQyxPQUFNO1lBQ1AsQ0FBQztZQUNELElBQUksRUFBRSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLGNBQWMsQ0FBQyxFQUFFLENBQUM7Z0JBQ2pELEVBQUUsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxjQUFjLENBQUMsQ0FBQSxDQUFDLGtEQUFrRDtZQUMvRixDQUFDO1lBQ0QsSUFBSSwyQkFBMkIsQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztnQkFDL0QsTUFBTSxJQUFJLEtBQUssQ0FDZCw4Q0FBOEM7b0JBQzdDLGdGQUFnRixDQUNqRixDQUFBO1lBQ0YsQ0FBQztZQUVELElBQUksQ0FBQyxVQUFVLEdBQUcsMEJBQVUsQ0FBQyw0QkFBNEIsRUFBRSxDQUFBO1lBQzNELElBQUksQ0FBQyxhQUFhLEdBQUcsSUFBQSxxQkFBSyxFQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQyxHQUFHLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxFQUFFO2dCQUN4RSxRQUFRLEVBQUUsSUFBSTthQUNkLENBQUMsQ0FBQTtZQUVGLElBQUksQ0FBQyxTQUFTLEdBQUcsR0FBRyxFQUFFO2dCQUNyQixJQUFJLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztvQkFDeEIsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUE7Z0JBQ25DLENBQUM7WUFDRixDQUFDLENBQUE7WUFFRCxJQUFJLENBQUMsWUFBWSxHQUFHLEVBQUUsQ0FBQyxTQUFTLENBQy9CLElBQUksQ0FBQyxRQUFRLENBQUMsY0FBYyxFQUM1QixDQUFDLElBQUksRUFBRSxJQUFJLEVBQUUsRUFBRTtnQkFDZCxJQUFJLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO29CQUMzQixJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxDQUFBO2dCQUMvQyxDQUFDO1lBQ0YsQ0FBQyxDQUNELENBQUE7WUFFRCxPQUFPLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUEsQ0FBQyx1RUFBdUU7WUFFMUcsc0NBQXNDO1lBQ3RDLElBQUksQ0FBQyxhQUFhLENBQUMsS0FBSyxFQUFFLENBQUE7UUFDM0IsQ0FBQztLQUFBO0lBRUQ7O01BRUU7SUFDSSxrQkFBa0I7O1lBQ3ZCLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FBQyxxQkFBcUIsQ0FBQyxxQkFBcUIsQ0FBQyxDQUFBO1FBQ3RFLENBQUM7S0FBQTtJQUVLLGFBQWE7O1lBQ2xCLElBQUksQ0FBQyxDQUFDLE1BQU0sSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDLEVBQUUsQ0FBQztnQkFDckMsT0FBTTtZQUNQLENBQUM7WUFDRCxJQUFJLElBQUksQ0FBQyxhQUFhLEtBQUssU0FBUyxFQUFFLENBQUM7Z0JBQ3RDLE9BQU07WUFDUCxDQUFDO1lBQ0QsbUNBQW1DO1lBQ25DLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FBQyxjQUFjLENBQUMscUJBQXFCLENBQUMsQ0FBQTtZQUU5RCxJQUFJLElBQUksQ0FBQyxZQUFZLEtBQUssU0FBUyxFQUFFLENBQUM7Z0JBQ3JDLEVBQUUsQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxjQUFjLENBQUMsQ0FBQTtZQUM3QyxDQUFDO1lBRUQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUEsQ0FBQyw0QkFBNEI7WUFDN0QsSUFBSSxDQUFDLFNBQVMsR0FBRywwQkFBVSxDQUFDLDRCQUE0QixFQUFFLENBQUE7WUFDMUQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUE7WUFDbEMsSUFBSSxPQUFPLEdBQUcsQ0FBQyxDQUFBO1lBQ2YsT0FBTyxJQUFJLENBQUMsU0FBUyxFQUFFLEVBQUUsQ0FBQztnQkFDekIsSUFBSSxPQUFPLEdBQUcsRUFBRSxFQUFFLENBQUM7b0JBQ2xCLE1BQU0sSUFBSSxLQUFLLENBQ2Qsc0VBQXNFLENBQ3RFLENBQUE7Z0JBQ0YsQ0FBQztnQkFDRCxNQUFNLDBCQUFVLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFBO2dCQUM1QixPQUFPLEVBQUUsQ0FBQTtZQUNWLENBQUM7WUFDRCxJQUFJLElBQUksQ0FBQyxTQUFTLEtBQUssU0FBUyxFQUFFLENBQUM7Z0JBQ2xDLE9BQU8sQ0FBQyxjQUFjLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQSxDQUFDLDBCQUEwQjtZQUMxRSxDQUFDO1FBQ0YsQ0FBQztLQUFBO0NBQ0Q7QUE3UEQsa0VBNlBDIn0=