@oaklean/profiler
Version:
A library to measure the energy consumption of your javascript/typescript code
243 lines • 18.4 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());
});
};
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=