@angular/benchpress
Version:
Benchpress - a framework for e2e performance tests
394 lines • 55.6 kB
JavaScript
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var PerflogMetric_1;
import { __decorate, __metadata, __param } from "tslib";
import { Inject, Injectable, InjectionToken } from '@angular/core';
import { Options } from '../common_options';
import { Metric } from '../metric';
import { WebDriverExtension } from '../web_driver_extension';
/**
* A metric that reads out the performance log
*/
let PerflogMetric = PerflogMetric_1 = class PerflogMetric extends Metric {
/**
* @param driverExtension
* @param setTimeout
* @param microMetrics Name and description of metrics provided via console.time / console.timeEnd
* @param ignoreNavigation If true, don't measure from navigationStart events. These events are
* usually triggered by a page load, but can also be triggered when adding iframes to the DOM.
**/
constructor(_driverExtension, _setTimeout, _microMetrics, _forceGc, _captureFrames, _receivedData, _requestCount, _ignoreNavigation) {
super();
this._driverExtension = _driverExtension;
this._setTimeout = _setTimeout;
this._microMetrics = _microMetrics;
this._forceGc = _forceGc;
this._captureFrames = _captureFrames;
this._receivedData = _receivedData;
this._requestCount = _requestCount;
this._ignoreNavigation = _ignoreNavigation;
this._remainingEvents = [];
this._measureCount = 0;
this._perfLogFeatures = _driverExtension.perfLogFeatures();
if (!this._perfLogFeatures.userTiming) {
// User timing is needed for navigationStart.
this._receivedData = false;
this._requestCount = false;
}
}
describe() {
const res = {
'scriptTime': 'script execution time in ms, including gc and render',
'pureScriptTime': 'script execution time in ms, without gc nor render'
};
if (this._perfLogFeatures.render) {
res['renderTime'] = 'render time in ms';
}
if (this._perfLogFeatures.gc) {
res['gcTime'] = 'gc time in ms';
res['gcAmount'] = 'gc amount in kbytes';
res['majorGcTime'] = 'time of major gcs in ms';
if (this._forceGc) {
res['forcedGcTime'] = 'forced gc time in ms';
res['forcedGcAmount'] = 'forced gc amount in kbytes';
}
}
if (this._receivedData) {
res['receivedData'] = 'encoded bytes received since navigationStart';
}
if (this._requestCount) {
res['requestCount'] = 'count of requests sent since navigationStart';
}
if (this._captureFrames) {
if (!this._perfLogFeatures.frameCapture) {
const warningMsg = 'WARNING: Metric requested, but not supported by driver';
// using dot syntax for metric name to keep them grouped together in console reporter
res['frameTime.mean'] = warningMsg;
res['frameTime.worst'] = warningMsg;
res['frameTime.best'] = warningMsg;
res['frameTime.smooth'] = warningMsg;
}
else {
res['frameTime.mean'] = 'mean frame time in ms (target: 16.6ms for 60fps)';
res['frameTime.worst'] = 'worst frame time in ms';
res['frameTime.best'] = 'best frame time in ms';
res['frameTime.smooth'] = 'percentage of frames that hit 60fps';
}
}
for (const name in this._microMetrics) {
res[name] = this._microMetrics[name];
}
return res;
}
beginMeasure() {
let resultPromise = Promise.resolve(null);
if (this._forceGc) {
resultPromise = resultPromise.then((_) => this._driverExtension.gc());
}
return resultPromise.then((_) => this._beginMeasure());
}
endMeasure(restart) {
if (this._forceGc) {
return this._endPlainMeasureAndMeasureForceGc(restart);
}
else {
return this._endMeasure(restart);
}
}
/** @internal */
_endPlainMeasureAndMeasureForceGc(restartMeasure) {
return this._endMeasure(true).then((measureValues) => {
// disable frame capture for measurements during forced gc
const originalFrameCaptureValue = this._captureFrames;
this._captureFrames = false;
return this._driverExtension.gc()
.then((_) => this._endMeasure(restartMeasure))
.then((forceGcMeasureValues) => {
this._captureFrames = originalFrameCaptureValue;
measureValues['forcedGcTime'] = forceGcMeasureValues['gcTime'];
measureValues['forcedGcAmount'] = forceGcMeasureValues['gcAmount'];
return measureValues;
});
});
}
_beginMeasure() {
return this._driverExtension.timeBegin(this._markName(this._measureCount++));
}
_endMeasure(restart) {
const markName = this._markName(this._measureCount - 1);
const nextMarkName = restart ? this._markName(this._measureCount++) : null;
return this._driverExtension.timeEnd(markName, nextMarkName)
.then((_) => this._readUntilEndMark(markName));
}
_readUntilEndMark(markName, loopCount = 0, startEvent = null) {
if (loopCount > _MAX_RETRY_COUNT) {
throw new Error(`Tried too often to get the ending mark: ${loopCount}`);
}
return this._driverExtension.readPerfLog().then((events) => {
this._addEvents(events);
const result = this._aggregateEvents(this._remainingEvents, markName);
if (result) {
this._remainingEvents = events;
return result;
}
let resolve;
const promise = new Promise(res => {
resolve = res;
});
this._setTimeout(() => resolve(this._readUntilEndMark(markName, loopCount + 1)), 100);
return promise;
});
}
_addEvents(events) {
let needSort = false;
events.forEach(event => {
if (event['ph'] === 'X') {
needSort = true;
const startEvent = {};
const endEvent = {};
for (const prop in event) {
startEvent[prop] = event[prop];
endEvent[prop] = event[prop];
}
startEvent['ph'] = 'B';
endEvent['ph'] = 'E';
endEvent['ts'] = startEvent['ts'] + startEvent['dur'];
this._remainingEvents.push(startEvent);
this._remainingEvents.push(endEvent);
}
else {
this._remainingEvents.push(event);
}
});
if (needSort) {
// Need to sort because of the ph==='X' events
this._remainingEvents.sort((a, b) => {
const diff = a['ts'] - b['ts'];
return diff > 0 ? 1 : diff < 0 ? -1 : 0;
});
}
}
_aggregateEvents(events, markName) {
const result = { 'scriptTime': 0, 'pureScriptTime': 0 };
if (this._perfLogFeatures.gc) {
result['gcTime'] = 0;
result['majorGcTime'] = 0;
result['gcAmount'] = 0;
}
if (this._perfLogFeatures.render) {
result['renderTime'] = 0;
}
if (this._captureFrames) {
result['frameTime.mean'] = 0;
result['frameTime.best'] = 0;
result['frameTime.worst'] = 0;
result['frameTime.smooth'] = 0;
}
for (const name in this._microMetrics) {
result[name] = 0;
}
if (this._receivedData) {
result['receivedData'] = 0;
}
if (this._requestCount) {
result['requestCount'] = 0;
}
let markStartEvent = null;
let markEndEvent = null;
events.forEach((event) => {
const ph = event['ph'];
const name = event['name'];
// Here we are determining if this is the event signaling the start or end of our performance
// testing (this is triggered by us calling #timeBegin and #timeEnd).
//
// Previously, this was done by checking that the event name matched our mark name and that
// the phase was either "B" or "E" ("begin" or "end"). However, since Chrome v90 this is
// showing up as "-bpstart" and "-bpend" ("benchpress start/end"), which is what one would
// actually expect since that is the mark name used in ChromeDriverExtension - see the
// #timeBegin and #timeEnd implementations in chrome_driver_extension.ts. For
// backwards-compatibility with Chrome v89 (and older), we do both checks: the phase-based
// one ("B" or "E") and event name-based (the "-bp(start/end)" suffix).
const isStartEvent = (ph === 'B' && name === markName) || name === markName + '-bpstart';
const isEndEvent = (ph === 'E' && name === markName) || name === markName + '-bpend';
if (isStartEvent) {
markStartEvent = event;
}
else if (ph === 'I' && name === 'navigationStart' && !this._ignoreNavigation) {
// if a benchmark measures reload of a page, use the last
// navigationStart as begin event
markStartEvent = event;
}
else if (isEndEvent) {
markEndEvent = event;
}
});
if (!markStartEvent || !markEndEvent) {
// not all events have been received, no further processing for now
return null;
}
if (markStartEvent.pid !== markEndEvent.pid) {
result['invalid'] = 1;
}
let gcTimeInScript = 0;
let renderTimeInScript = 0;
const frameTimestamps = [];
const frameTimes = [];
let frameCaptureStartEvent = null;
let frameCaptureEndEvent = null;
const intervalStarts = {};
const intervalStartCount = {};
let inMeasureRange = false;
events.forEach((event) => {
const ph = event['ph'];
let name = event['name'];
let microIterations = 1;
const microIterationsMatch = name.match(_MICRO_ITERATIONS_REGEX);
if (microIterationsMatch) {
name = microIterationsMatch[1];
microIterations = parseInt(microIterationsMatch[2], 10);
}
if (event === markStartEvent) {
inMeasureRange = true;
}
else if (event === markEndEvent) {
inMeasureRange = false;
}
if (!inMeasureRange || event['pid'] !== markStartEvent['pid']) {
return;
}
if (this._requestCount && name === 'sendRequest') {
result['requestCount'] += 1;
}
else if (this._receivedData && name === 'receivedData' && ph === 'I') {
result['receivedData'] += event['args']['encodedDataLength'];
}
if (ph === 'B' && name === _MARK_NAME_FRAME_CAPTURE) {
if (frameCaptureStartEvent) {
throw new Error('can capture frames only once per benchmark run');
}
if (!this._captureFrames) {
throw new Error('found start event for frame capture, but frame capture was not requested in benchpress');
}
frameCaptureStartEvent = event;
}
else if (ph === 'E' && name === _MARK_NAME_FRAME_CAPTURE) {
if (!frameCaptureStartEvent) {
throw new Error('missing start event for frame capture');
}
frameCaptureEndEvent = event;
}
if (ph === 'I' && frameCaptureStartEvent && !frameCaptureEndEvent && name === 'frame') {
frameTimestamps.push(event['ts']);
if (frameTimestamps.length >= 2) {
frameTimes.push(frameTimestamps[frameTimestamps.length - 1] -
frameTimestamps[frameTimestamps.length - 2]);
}
}
if (ph === 'B') {
if (!intervalStarts[name]) {
intervalStartCount[name] = 1;
intervalStarts[name] = event;
}
else {
intervalStartCount[name]++;
}
}
else if ((ph === 'E') && intervalStarts[name]) {
intervalStartCount[name]--;
if (intervalStartCount[name] === 0) {
const startEvent = intervalStarts[name];
const duration = (event['ts'] - startEvent['ts']);
intervalStarts[name] = null;
if (name === 'gc') {
result['gcTime'] += duration;
const amount = (startEvent['args']['usedHeapSize'] - event['args']['usedHeapSize']) / 1000;
result['gcAmount'] += amount;
const majorGc = event['args']['majorGc'];
if (majorGc && majorGc) {
result['majorGcTime'] += duration;
}
if (intervalStarts['script']) {
gcTimeInScript += duration;
}
}
else if (name === 'render') {
result['renderTime'] += duration;
if (intervalStarts['script']) {
renderTimeInScript += duration;
}
}
else if (name === 'script') {
result['scriptTime'] += duration;
}
else if (this._microMetrics[name]) {
result[name] += duration / microIterations;
}
}
}
});
if (frameCaptureStartEvent && !frameCaptureEndEvent) {
throw new Error('missing end event for frame capture');
}
if (this._captureFrames && !frameCaptureStartEvent) {
throw new Error('frame capture requested in benchpress, but no start event was found');
}
if (frameTimes.length > 0) {
this._addFrameMetrics(result, frameTimes);
}
result['pureScriptTime'] = result['scriptTime'] - gcTimeInScript - renderTimeInScript;
return result;
}
_addFrameMetrics(result, frameTimes) {
result['frameTime.mean'] = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;
const firstFrame = frameTimes[0];
result['frameTime.worst'] = frameTimes.reduce((a, b) => a > b ? a : b, firstFrame);
result['frameTime.best'] = frameTimes.reduce((a, b) => a < b ? a : b, firstFrame);
result['frameTime.smooth'] =
frameTimes.filter(t => t < _FRAME_TIME_SMOOTH_THRESHOLD).length / frameTimes.length;
}
_markName(index) {
return `${_MARK_NAME_PREFIX}${index}`;
}
};
PerflogMetric.SET_TIMEOUT = new InjectionToken('PerflogMetric.setTimeout');
PerflogMetric.IGNORE_NAVIGATION = new InjectionToken('PerflogMetric.ignoreNavigation');
PerflogMetric.PROVIDERS = [
{
provide: PerflogMetric_1,
deps: [
WebDriverExtension, PerflogMetric_1.SET_TIMEOUT, Options.MICRO_METRICS, Options.FORCE_GC,
Options.CAPTURE_FRAMES, Options.RECEIVED_DATA, Options.REQUEST_COUNT,
PerflogMetric_1.IGNORE_NAVIGATION
]
},
{
provide: PerflogMetric_1.SET_TIMEOUT,
useValue: (fn, millis) => setTimeout(fn, millis)
},
{ provide: PerflogMetric_1.IGNORE_NAVIGATION, useValue: false }
];
PerflogMetric = PerflogMetric_1 = __decorate([
Injectable(),
__param(1, Inject(PerflogMetric_1.SET_TIMEOUT)),
__param(2, Inject(Options.MICRO_METRICS)),
__param(3, Inject(Options.FORCE_GC)),
__param(4, Inject(Options.CAPTURE_FRAMES)),
__param(5, Inject(Options.RECEIVED_DATA)),
__param(6, Inject(Options.REQUEST_COUNT)),
__param(7, Inject(PerflogMetric_1.IGNORE_NAVIGATION)),
__metadata("design:paramtypes", [WebDriverExtension,
Function, Object, Boolean, Boolean, Boolean, Boolean, Boolean])
], PerflogMetric);
export { PerflogMetric };
const _MICRO_ITERATIONS_REGEX = /(.+)\*(\d+)$/;
const _MAX_RETRY_COUNT = 20;
const _MARK_NAME_PREFIX = 'benchpress';
const _MARK_NAME_FRAME_CAPTURE = 'frameCapture';
// using 17ms as a somewhat looser threshold, instead of 16.6666ms
const _FRAME_TIME_SMOOTH_THRESHOLD = 17;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVyZmxvZ19tZXRyaWMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9wYWNrYWdlcy9iZW5jaHByZXNzL3NyYy9tZXRyaWMvcGVyZmxvZ19tZXRyaWMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztHQU1HOzs7QUFFSCxPQUFPLEVBQUMsTUFBTSxFQUFFLFVBQVUsRUFBRSxjQUFjLEVBQUMsTUFBTSxlQUFlLENBQUM7QUFFakUsT0FBTyxFQUFDLE9BQU8sRUFBQyxNQUFNLG1CQUFtQixDQUFDO0FBQzFDLE9BQU8sRUFBQyxNQUFNLEVBQUMsTUFBTSxXQUFXLENBQUM7QUFDakMsT0FBTyxFQUFnQyxrQkFBa0IsRUFBQyxNQUFNLHlCQUF5QixDQUFDO0FBRzFGOztHQUVHO0FBRUgsSUFBYSxhQUFhLHFCQUExQixNQUFhLGFBQWMsU0FBUSxNQUFNO0lBd0J2Qzs7Ozs7O1FBTUk7SUFDSixZQUNZLGdCQUFvQyxFQUNELFdBQXFCLEVBQ3pCLGFBQXNDLEVBQzNDLFFBQWlCLEVBQ1gsY0FBdUIsRUFDeEIsYUFBc0IsRUFDdEIsYUFBc0IsRUFDWixpQkFBMEI7UUFDN0UsS0FBSyxFQUFFLENBQUM7UUFSRSxxQkFBZ0IsR0FBaEIsZ0JBQWdCLENBQW9CO1FBQ0QsZ0JBQVcsR0FBWCxXQUFXLENBQVU7UUFDekIsa0JBQWEsR0FBYixhQUFhLENBQXlCO1FBQzNDLGFBQVEsR0FBUixRQUFRLENBQVM7UUFDWCxtQkFBYyxHQUFkLGNBQWMsQ0FBUztRQUN4QixrQkFBYSxHQUFiLGFBQWEsQ0FBUztRQUN0QixrQkFBYSxHQUFiLGFBQWEsQ0FBUztRQUNaLHNCQUFpQixHQUFqQixpQkFBaUIsQ0FBUztRQUc3RSxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsRUFBRSxDQUFDO1FBQzNCLElBQUksQ0FBQyxhQUFhLEdBQUcsQ0FBQyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxnQkFBZ0IsQ0FBQyxlQUFl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QUMsQ0FBQTtJQUN4QixXQUFBLE1BQU0sQ0FBQyxPQUFPLENBQUMsY0FBYyxDQUFDLENBQUE7SUFDOUIsV0FBQSxNQUFNLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFBO0lBQzdCLFdBQUEsTUFBTSxDQUFDLE9BQU8sQ0FBQyxhQUFhLENBQUMsQ0FBQTtJQUM3QixXQUFBLE1BQU0sQ0FBQyxlQUFhLENBQUMsaUJBQWlCLENBQUMsQ0FBQTtxQ0FQZCxrQkFBa0I7UUFDWSxRQUFRO0dBakN6RCxhQUFhLENBd1h6QjtTQXhYWSxhQUFhO0FBMFgxQixNQUFNLHVCQUF1QixHQUFHLGNBQWMsQ0FBQztBQUUvQyxNQUFNLGdCQUFnQixHQUFHLEVBQUUsQ0FBQztBQUM1QixNQUFNLGlCQUFpQixHQUFHLFlBQVksQ0FBQztBQUV2QyxNQUFNLHdCQUF3QixHQUFHLGNBQWMsQ0FBQztBQUNoRCxrRUFBa0U7QUFDbEUsTUFBTSw0QkFBNEIsR0FBRyxFQUFFLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEBsaWNlbnNlXG4gKiBDb3B5cmlnaHQgR29vZ2xlIExMQyBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuaW1wb3J0IHtJbmplY3QsIEluamVjdGFibGUsIEluamVjdGlvblRva2VufSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuaW1wb3J0IHtPcHRpb25zfSBmcm9tICcuLi9jb21tb25fb3B0aW9ucyc7XG5pbXBvcnQge01ldHJpY30gZnJvbSAnLi4vbWV0cmljJztcbmltcG9ydCB7UGVyZkxvZ0V2ZW50LCBQZXJmTG9nRmVhdHVyZXMsIFdlYkRyaXZlckV4dGVuc2lvbn0gZnJvbSAnLi4vd2ViX2RyaXZlcl9leHRlbnNpb24nO1xuXG5cbi8qKlxuICogQSBtZXRyaWMgdGhhdCByZWFkcyBvdXQgdGhlIHBlcmZvcm1hbmNlIGxvZ1xuICovXG5ASW5qZWN0YWJsZSgpXG5leHBvcnQgY2xhc3MgUGVyZmxvZ01ldHJpYyBleHRlbmRzIE1ldHJpYyB7XG4gIHN0YXRpYyBTRVRfVElNRU9VVCA9IG5ldyBJbmplY3Rpb25Ub2tlbignUGVyZmxvZ01ldHJpYy5zZXRUaW1lb3V0Jyk7XG4gIHN0YXRpYyBJR05PUkVfTkFWSUdBVElPTiA9IG5ldyBJbmplY3Rpb25Ub2tlbignUGVyZmxvZ01ldHJpYy5pZ25vcmVOYXZpZ2F0aW9uJyk7XG4gIHN0YXRpYyBQUk9WSURFUlMgPSBbXG4gICAge1xuICAgICAgcHJvdmlkZTogUGVyZmxvZ01ldHJpYyxcbiAgICAgIGRlcHM6XG4gICAgICAgICAgW1xuICAgICAgICAgICAgV2ViRHJpdmVyRXh0ZW5zaW9uLCBQZXJmbG9nTWV0cmljLlNFVF9USU1FT1VULCBPcHRpb25zLk1JQ1JPX01FVFJJQ1MsIE9wdGlvbnMuRk9SQ0VfR0MsXG4gICAgICAgICAgICBPcHRpb25zLkNBUFRVUkVfRlJBTUVTLCBPcHRpb25zLlJFQ0VJVkVEX0RBVEEsIE9wdGlvbnMuUkVRVUVTVF9DT1VOVCxcbiAgICAgICAgICAgIFBlcmZsb2dNZXRyaWMuSUdOT1JFX05BVklHQVRJT05cbiAgICAgICAgICBdXG4gICAgfSxcbiAgICB7XG4gICAgICBwcm92aWRlOiBQZXJmbG9nTWV0cmljLlNFVF9USU1FT1VULFxuICAgICAgdXNlVmFsdWU6IChmbjogRnVuY3Rpb24sIG1pbGxpczogbnVtYmVyKSA9PiA8YW55PnNldFRpbWVvdXQoZm4sIG1pbGxpcylcbiAgICB9LFxuICAgIHtwcm92aWRlOiBQZXJmbG9nTWV0cmljLklHTk9SRV9OQVZJR0FUSU9OLCB1c2VWYWx1ZTogZmFsc2V9XG4gIF07XG5cbiAgcHJpdmF0ZSBfcmVtYWluaW5nRXZlbnRzOiBQZXJmTG9nRXZlbnRbXTtcbiAgcHJpdmF0ZSBfbWVhc3VyZUNvdW50OiBudW1iZXI7XG4gIHByaXZhdGUgX3BlcmZMb2dGZWF0dXJlczogUGVyZkxvZ0ZlYXR1cmVzO1xuXG4gIC8qKlxuICAgKiBAcGFyYW0gZHJpdmVyRXh0ZW5zaW9uXG4gICAqIEBwYXJhbSBzZXRUaW1lb3V0XG4gICAqIEBwYXJhbSBtaWNyb01ldHJpY3MgTmFtZSBhbmQgZGVzY3JpcHRpb24gb2YgbWV0cmljcyBwcm92aWRlZCB2aWEgY29uc29sZS50aW1lIC8gY29uc29sZS50aW1lRW5kXG4gICAqIEBwYXJhbSBpZ25vcmVOYXZpZ2F0aW9uIElmIHRydWUsIGRvbid0IG1lYXN1cmUgZnJvbSBuYXZpZ2F0aW9uU3RhcnQgZXZlbnRzLiBUaGVzZSBldmVudHMgYXJlXG4gICAqICAgdXN1YWxseSB0cmlnZ2VyZWQgYnkgYSBwYWdlIGxvYWQsIGJ1dCBjYW4gYWxzbyBiZSB0cmlnZ2VyZWQgd2hlbiBhZGRpbmcgaWZyYW1lcyB0byB0aGUgRE9NLlxuICAgKiovXG4gIGNvbnN0cnVjdG9yKFxuICAgICAgcHJpdmF0ZSBfZHJpdmVyRXh0ZW5zaW9uOiBXZWJEcml2ZXJFeHRlbnNpb24sXG4gICAgICBASW5qZWN0KFBlcmZsb2dNZXRyaWMuU0VUX1RJTUVPVVQpIHByaXZhdGUgX3NldFRpbWVvdXQ6IEZ1bmN0aW9uLFxuICAgICAgQEluamVjdChPcHRpb25zLk1JQ1JPX01FVFJJQ1MpIHByaXZhdGUgX21pY3JvTWV0cmljczoge1trZXk6IHN0cmluZ106IHN0cmluZ30sXG4gICAgICBASW5qZWN0KE9wdGlvbnMuRk9SQ0VfR0MpIHByaXZhdGUgX2ZvcmNlR2M6IGJvb2xlYW4sXG4gICAgICBASW5qZWN0KE9wdGlvbnMuQ0FQVFVSRV9GUkFNRVMpIHByaXZhdGUgX2NhcHR1cmVGcmFtZXM6IGJvb2xlYW4sXG4gICAgICBASW5qZWN0KE9wdGlvbnMuUkVDRUlWRURfREFUQSkgcHJpdmF0ZSBfcmVjZWl2ZWREYXRhOiBib29sZWFuLFxuICAgICAgQEluamVjdChPcHRpb25zLlJFUVVFU1RfQ09VTlQpIHByaXZhdGUgX3JlcXVlc3RDb3VudDogYm9vbGVhbixcbiAgICAgIEBJbmplY3QoUGVyZmxvZ01ldHJpYy5JR05PUkVfTkFWSUdBVElPTikgcHJpdmF0ZSBfaWdub3JlTmF2aWdhdGlvbjogYm9vbGVhbikge1xuICAgIHN1cGVyKCk7XG5cbiAgICB0aGlzLl9yZW1haW5pbmdFdmVudHMgPSBbXTtcbiAgICB0aGlzLl9tZWFzdXJlQ291bnQgPSAwO1xuICAgIHRoaXMuX3BlcmZMb2dGZWF0dXJlcyA9IF9kcml2ZXJFeHRlbnNpb24ucGVyZkxvZ0ZlYXR1cmVzKCk7XG4gICAgaWYgKCF0aGlzLl9wZXJmTG9nRmVhdHVyZXMudXNlclRpbWluZykge1xuICAgICAgLy8gVXNlciB0aW1pbmcgaXMgbmVlZGVkIGZvciBuYXZpZ2F0aW9uU3RhcnQuXG4gICAgICB0aGlzLl9yZWNlaXZlZERhdGEgPSBmYWxzZTtcbiAgICAgIHRoaXMuX3JlcXVlc3RDb3VudCA9IGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIG92ZXJyaWRlIGRlc2NyaWJlKCk6IHtba2V5OiBzdHJpbmddOiBzdHJpbmd9IHtcbiAgICBjb25zdCByZXM6IHtba2V5OiBzdHJpbmddOiBhbnl9ID0ge1xuICAgICAgJ3NjcmlwdFRpbWUnOiAnc2NyaXB0IGV4ZWN1dGlvbiB0aW1lIGluIG1zLCBpbmNsdWRpbmcgZ2MgYW5kIHJlbmRlcicsXG4gICAgICAncHVyZVNjcmlwdFRpbWUnOiAnc2NyaXB0IGV4ZWN1dGlvbiB0aW1lIGluIG1zLCB3aXRob3V0IGdjIG5vciByZW5kZXInXG4gICAgfTtcbiAgICBpZiAodGhpcy5fcGVyZkxvZ0ZlYXR1cmVzLnJlbmRlcikge1xuICAgICAgcmVzWydyZW5kZXJUaW1lJ10gPSAncmVuZGVyIHRpbWUgaW4gbXMnO1xuICAgIH1cbiAgICBpZiAodGhpcy5fcGVyZkxvZ0ZlYXR1cmVzLmdjKSB7XG4gICAgICByZXNbJ2djVGltZSddID0gJ2djIHRpbWUgaW4gbXMnO1xuICAgICAgcmVzWydnY0Ftb3VudCddID0gJ2djIGFtb3VudCBpbiBrYnl0ZXMnO1xuICAgICAgcmVzWydtYWpvckdjVGltZSddID0gJ3RpbWUgb2YgbWFqb3IgZ2NzIGluIG1zJztcbiAgICAgIGlmICh0aGlzLl9mb3JjZUdjKSB7XG4gICAgICAgIHJlc1snZm9yY2VkR2NUaW1lJ10gPSAnZm9yY2VkIGdjIHRpbWUgaW4gbXMnO1xuICAgICAgICByZXNbJ2ZvcmNlZEdjQW1vdW50J10gPSAnZm9yY2VkIGdjIGFtb3VudCBpbiBrYnl0ZXMnO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAodGhpcy5fcmVjZWl2ZWREYXRhKSB7XG4gICAgICByZXNbJ3JlY2VpdmVkRGF0YSddID0gJ2VuY29kZWQgYnl0ZXMgcmVjZWl2ZWQgc2luY2UgbmF2aWdhdGlvblN0YXJ0JztcbiAgICB9XG4gICAgaWYgKHRoaXMuX3JlcXVlc3RDb3VudCkge1xuICAgICAgcmVzWydyZXF1ZXN0Q291bnQnXSA9ICdjb3VudCBvZiByZXF1ZXN0cyBzZW50IHNpbmNlIG5hdmlnYXRpb25TdGFydCc7XG4gICAgfVxuICAgIGlmICh0aGlzLl9jYXB0dXJlRnJhbWVzKSB7XG4gICAgICBpZiAoIXRoaXMuX3BlcmZMb2dGZWF0dXJlcy5mcmFtZUNhcHR1cmUpIHtcbiAgICAgICAgY29uc3Qgd2FybmluZ01zZyA9ICdXQVJOSU5HOiBNZXRyaWMgcmVxdWVzdGVkLCBidXQgbm90IHN1cHBvcnRlZCBieSBkcml2ZXInO1xuICAgICAgICAvLyB1c2luZyBkb3Qgc3ludGF4IGZvciBtZXRyaWMgbmFtZSB0byBrZWVwIHRoZW0gZ3JvdXBlZCB0b2dldGhlciBpbiBjb25zb2xlIHJlcG9ydGVyXG4gICAgICAgIHJlc1snZnJhbWVUaW1lLm1lYW4nXSA9IHdhcm5pbmdNc2c7XG4gICAgICAgIHJlc1snZnJhbWVUaW1lLndvcnN0J10gPSB3YXJuaW5nTXNnO1xuICAgICAgICByZXNbJ2ZyYW1lVGltZS5iZXN0J10gPSB3YXJuaW5nTXNnO1xuICAgICAgICByZXNbJ2ZyYW1lVGltZS5zbW9vdGgnXSA9IHdhcm5pbmdNc2c7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXNbJ2ZyYW1lVGltZS5tZWFuJ10gPSAnbWVhbiBmcmFtZSB0aW1lIGluIG1zICh0YXJnZXQ6IDE2LjZtcyBmb3IgNjBmcHMpJztcbiAgICAgICAgcmVzWydmcmFtZVRpbWUud29yc3QnXSA9ICd3b3JzdCBmcmFtZSB0aW1lIGluIG1zJztcbiAgICAgICAgcmVzWydmcmFtZVRpbWUuYmVzdCddID0gJ2Jlc3QgZnJhbWUgdGltZSBpbiBtcyc7XG4gICAgICAgIHJlc1snZnJhbWVUaW1lLnNtb290aCddID0gJ3BlcmNlbnRhZ2Ugb2YgZnJhbWVzIHRoYXQgaGl0IDYwZnBzJztcbiAgICAgIH1cbiAgICB9XG4gICAgZm9yIChjb25zdCBuYW1lIGluIHRoaXMuX21pY3JvTWV0cmljcykge1xuICAgICAgcmVzW25hbWVdID0gdGhpcy5fbWljcm9NZXRyaWNzW25hbWVdO1xuICAgIH1cbiAgICByZXR1cm4gcmVzO1xuICB9XG5cbiAgb3ZlcnJpZGUgYmVnaW5NZWFzdXJlKCk6IFByb21pc2U8YW55PiB7XG4gICAgbGV0IHJlc3VsdFByb21pc2UgPSBQcm9taXNlLnJlc29sdmUobnVsbCk7XG4gICAgaWYgKHRoaXMuX2ZvcmNlR2MpIHtcbiAgICAgIHJlc3VsdFByb21pc2UgPSByZXN1bHRQcm9taXNlLnRoZW4oKF8pID0+IHRoaXMuX2RyaXZlckV4dGVuc2lvbi5nYygpKTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdFByb21pc2UudGhlbigoXykgPT4gdGhpcy5fYmVnaW5NZWFzdXJlKCkpO1xuICB9XG5cbiAgb3ZlcnJpZGUgZW5kTWVhc3VyZShyZXN0YXJ0OiBib29sZWFuKTogUHJvbWlzZTx7W2tleTogc3RyaW5nXTogbnVtYmVyfT4ge1xuICAgIGlmICh0aGlzLl9mb3JjZUdjKSB7XG4gICAgICByZXR1cm4gdGhpcy5fZW5kUGxhaW5NZWFzdXJlQW5kTWVhc3VyZUZvcmNlR2MocmVzdGFydCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHJldHVybiB0aGlzLl9lbmRNZWFzdXJlKHJlc3RhcnQpO1xuICAgIH1cbiAgfVxuXG4gIC8qKiBAaW50ZXJuYWwgKi9cbiAgcHJpdmF0ZSBfZW5kUGxhaW5NZWFzdXJlQW5kTWVhc3VyZUZvcmNlR2MocmVzdGFydE1lYXN1cmU6IGJvb2xlYW4pIHtcbiAgICByZXR1cm4gdGhpcy5fZW5kTWVhc3VyZSh0cnVlKS50aGVuKChtZWFzdXJlVmFsdWVzKSA9PiB7XG4gICAgICAvLyBkaXNhYmxlIGZyYW1lIGNhcHR1cmUgZm9yIG1lYXN1cmVtZW50cyBkdXJpbmcgZm9yY2VkIGdjXG4gICAgICBjb25zdCBvcmlnaW5hbEZyYW1lQ2FwdHVyZVZhbHVlID0gdGhpcy5fY2FwdHVyZUZyYW1lcztcbiAgICAgIHRoaXMuX2NhcHR1cmVGcmFtZXMgPSBmYWxzZTtcbiAgICAgIHJldHVybiB0aGlzLl9kcml2ZXJFeHRlbnNpb24uZ2MoKVxuICAgICAgICAgIC50aGVuKChfKSA9PiB0aGlzLl9lbmRNZWFzdXJlKHJlc3RhcnRNZWFzdXJlKSlcbiAgICAgICAgICAudGhlbigoZm9yY2VHY01lYXN1cmVWYWx1ZXMpID0+IHtcbiAgICAgICAgICAgIHRoaXMuX2NhcHR1cmVGcmFtZXMgPSBvcmlnaW5hbEZyYW1lQ2FwdHVyZVZhbHVlO1xuICAgICAgICAgICAgbWVhc3VyZVZhbHVlc1snZm9yY2VkR2NUaW1lJ10gPSBmb3JjZUdjTWVhc3VyZVZhbHVlc1snZ2NUaW1lJ107XG4gICAgICAgICAgICBtZWFzdXJlVmFsdWVzWydmb3JjZWRHY0Ftb3VudCddID0gZm9yY2VHY01lYXN1cmVWYWx1ZXNbJ2djQW1vdW50J107XG4gICAgICAgICAgICByZXR1cm4gbWVhc3VyZVZhbHVlcztcbiAgICAgICAgICB9KTtcbiAgICB9KTtcbiAgfVxuXG4gIHByaXZhdGUgX2JlZ2luTWVhc3VyZSgpOiBQcm9taXNlPGFueT4ge1xuICAgIHJldHVybiB0aGlzLl9kcml2ZXJFeHRlbnNpb24udGltZUJlZ2luKHRoaXMuX21hcmtOYW1lKHRoaXMuX21lYXN1cmVDb3VudCsrKSk7XG4gIH1cblxuICBwcml2YXRlIF9lbmRNZWFzdXJlKHJlc3RhcnQ6IGJvb2xlYW4pOiBQcm9taXNlPHtba2V5OiBzdHJpbmddOiBudW1iZXJ9PiB7XG4gICAgY29uc3QgbWFya05hbWUgPSB0aGlzLl9tYXJrTmFtZSh0aGlzLl9tZWFzdXJlQ291bnQgLSAxKTtcbiAgICBjb25zdCBuZXh0TWFya05hbWUgPSByZXN0YXJ0ID8gdGhpcy5fbWFya05hbWUodGhpcy5fbWVhc3VyZUNvdW50KyspIDogbnVsbDtcbiAgICByZXR1cm4gdGhpcy5fZHJpdmVyRXh0ZW5zaW9uLnRpbWVFbmQobWFya05hbWUsIG5leHRNYXJrTmFtZSlcbiAgICAgICAgLnRoZW4oKF86IGFueSkgPT4gdGhpcy5fcmVhZFVudGlsRW5kTWFyayhtYXJrTmFtZSkpO1xuICB9XG5cbiAgcHJpdmF0ZSBfcmVhZFVudGlsRW5kTWFyayhcbiAgICAgIG1hcmtOYW1lOiBzdHJpbmcsIGxvb3BDb3VudDogbnVtYmVyID0gMCwgc3RhcnRFdmVudDogUGVyZkxvZ0V2ZW50fG51bGwgPSBudWxsKSB7XG4gICAgaWYgKGxvb3BDb3VudCA+IF9NQVhfUkVUUllfQ09VTlQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgVHJpZWQgdG9vIG9mdGVuIHRvIGdldCB0aGUgZW5kaW5nIG1hcms6ICR7bG9vcENvdW50fWApO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5fZHJpdmVyRXh0ZW5zaW9uLnJlYWRQZXJmTG9nKCkudGhlbigoZXZlbnRzKSA9PiB7XG4gICAgICB0aGlzLl9hZGRFdmVudHMoZXZlbnRzKTtcbiAgICAgIGNvbnN0IHJlc3VsdCA9IHRoaXMuX2FnZ3JlZ2F0ZUV2ZW50cyh0aGlzLl9yZW1haW5pbmdFdmVudHMsIG1hcmtOYW1lKTtcbiAgICAgIGlmIChyZXN1bHQpIHtcbiAgICAgICAgdGhpcy5fcmVtYWluaW5nRXZlbnRzID0gZXZlbnRzO1xuICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgfVxuICAgICAgbGV0IHJlc29sdmU6IChyZXN1bHQ6IGFueSkgPT4gdm9pZDtcbiAgICAgIGNvbnN0IHByb21pc2UgPSBuZXcgUHJvbWlzZTx7W2tleTogc3RyaW5nXTogbnVtYmVyfT4ocmVzID0+IHtcbiAgICAgICAgcmVzb2x2ZSA9IHJlcztcbiAgICAgIH0pO1xuICAgICAgdGhpcy5fc2V0VGltZW91dCgoKSA9PiByZXNvbHZlKHRoaXMuX3JlYWRVbnRpbEVuZE1hcmsobWFya05hbWUsIGxvb3BDb3VudCArIDEpKSwgMTAwKTtcbiAgICAgIHJldHVybiBwcm9taXNlO1xuICAgIH0pO1xuICB9XG5cbiAgcHJpdmF0ZSBfYWRkRXZlbnRzKGV2ZW50czogUGVyZkxvZ0V2ZW50W10pIHtcbiAgICBsZXQgbmVlZFNvcnQgPSBmYWxzZTtcbiAgICBldmVudHMuZm9yRWFjaChldmVudCA9PiB7XG4gICAgICBpZiAoZXZlbnRbJ3BoJ10gPT09ICdYJykge1xuICAgICAgICBuZWVkU29ydCA9IHRydWU7XG4gICAgICAgIGNvbnN0IHN0YXJ0RXZlbnQ6IFBlcmZMb2dFdmVudCA9IHt9O1xuICAgICAgICBjb25zdCBlbmRFdmVudDogUGVyZkxvZ0V2ZW50ID0ge307XG4gICAgICAgIGZvciAoY29uc3QgcHJvcCBpbiBldmVudCkge1xuICAgICAgICAgIHN0YXJ0RXZlbnRbcHJvcF0gPSBldmVudFtwcm9wXTtcbiAgICAgICAgICBlbmRFdmVudFtwcm9wXSA9IGV2ZW50W3Byb3BdO1xuICAgICAgICB9XG4gICAgICAgIHN0YXJ0RXZlbnRbJ3BoJ10gPSAnQic7XG4gICAgICAgIGVuZEV2ZW50WydwaCddID0gJ0UnO1xuICAgICAgICBlbmRFdmVudFsndHMnXSA9IHN0YXJ0RXZlbnRbJ3RzJ10hICsgc3RhcnRFdmVudFsnZHVyJ10hO1xuICAgICAgICB0aGlzLl9yZW1haW5pbmdFdmVudHMucHVzaChzdGFydEV2ZW50KTtcbiAgICAgICAgdGhpcy5fcmVtYWluaW5nRXZlbnRzLnB1c2goZW5kRXZlbnQpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5fcmVtYWluaW5nRXZlbnRzLnB1c2goZXZlbnQpO1xuICAgICAgfVxuICAgIH0pO1xuICAgIGlmIChuZWVkU29ydCkge1xuICAgICAgLy8gTmVlZCB0byBzb3J0IGJlY2F1c2Ugb2YgdGhlIHBoPT09J1gnIGV2ZW50c1xuICAgICAgdGhpcy5fcmVtYWluaW5nRXZlbnRzLnNvcnQoKGEsIGIpID0+IHtcbiAgICAgICAgY29uc3QgZGlmZiA9IGFbJ3RzJ10hIC0gYlsndHMnXSE7XG4gICAgICAgIHJldHVybiBkaWZmID4gMCA/IDEgOiBkaWZmIDwgMCA/IC0xIDogMDtcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgX2FnZ3JlZ2F0ZUV2ZW50cyhldmVudHM6IFBlcmZMb2dFdmVudFtdLCBtYXJrTmFtZTogc3RyaW5nKToge1trZXk6IHN0cmluZ106IG51bWJlcn18bnVsbCB7XG4gICAgY29uc3QgcmVzdWx0OiB7W2tleTogc3RyaW5nXTogbnVtYmVyfSA9IHsnc2NyaXB0VGltZSc6IDAsICdwdXJlU2NyaXB0VGltZSc6IDB9O1xuICAgIGlmICh0aGlzLl9wZXJmTG9nRmVhdHVyZXMuZ2MpIHtcbiAgICAgIHJlc3VsdFsnZ2NUaW1lJ10gPSAwO1xuICAgICAgcmVzdWx0WydtYWpvckdjVGltZSddID0gMDtcbiAgICAgIHJlc3VsdFsnZ2NBbW91bnQnXSA9IDA7XG4gICAgfVxuICAgIGlmICh0aGlzLl9wZXJmTG9nRmVhdHVyZXMucmVuZGVyKSB7XG4gICAgICByZXN1bHRbJ3JlbmRlclRpbWUnXSA9IDA7XG4gICAgfVxuICAgIGlmICh0aGlzLl9jYXB0dXJlRnJhbWVzKSB7XG4gICAgICByZXN1bHRbJ2ZyYW1lVGltZS5tZWFuJ10gPSAwO1xuICAgICAgcmVzdWx0WydmcmFtZVRpbWUuYmVzdCddID0gMDtcbiAgICAgIHJlc3VsdFsnZnJhbWVUaW1lLndvcnN0J10gPSAwO1xuICAgICAgcmVzdWx0WydmcmFtZVRpbWUuc21vb3RoJ10gPSAwO1xuICAgIH1cbiAgICBmb3IgKGNvbnN0IG5hbWUgaW4gdGhpcy5fbWljcm9NZXRyaWNzKSB7XG4gICAgICByZXN1bHRbbmFtZV0gPSAwO1xuICAgIH1cbiAgICBpZiAodGhpcy5fcmVjZWl2ZWREYXRhKSB7XG4gICAgICByZXN1bHRbJ3JlY2VpdmVkRGF0YSddID0gMDtcbiAgICB9XG4gICAgaWYgKHRoaXMuX3JlcXVlc3RDb3VudCkge1xuICAgICAgcmVzdWx0WydyZXF1ZXN0Q291bnQnXSA9IDA7XG4gICAgfVxuXG4gICAgbGV0IG1hcmtTdGFydEV2ZW50OiBQZXJmTG9nRXZlbnQgPSBudWxsITtcbiAgICBsZXQgbWFya0VuZEV2ZW50OiBQZXJmTG9nRXZlbnQgPSBudWxsITtcbiAgICBldmVudHMuZm9yRWFjaCgoZXZlbnQpID0+IHtcbiAgICAgIGNvbnN0IHBoID0gZXZlbnRbJ3BoJ107XG4gICAgICBjb25zdCBuYW1lID0gZXZlbnRbJ25hbWUnXTtcblxuICAgICAgLy8gSGVyZSB3ZSBhcmUgZGV0ZXJtaW5pbmcgaWYgdGhpcyBpcyB0aGUgZXZlbnQgc2lnbmFsaW5nIHRoZSBzdGFydCBvciBlbmQgb2Ygb3VyIHBlcmZvcm1hbmNlXG4gICAgICAvLyB0ZXN0aW5nICh0aGlzIGlzIHRyaWdnZXJlZCBieSB1cyBjYWxsaW5nICN0aW1lQmVnaW4gYW5kICN0aW1lRW5kKS5cbiAgICAgIC8vXG4gICAgICAvLyBQcmV2aW91c2x5LCB0aGlzIHdhcyBkb25lIGJ5IGNoZWNraW5nIHRoYXQgdGhlIGV2ZW50IG5hbWUgbWF0Y2hlZCBvdXIgbWFyayBuYW1lIGFuZCB0aGF0XG4gICAgICAvLyB0aGUgcGhhc2Ugd2FzIGVpdGhlciBcIkJcIiBvciBcIkVcIiAoXCJiZWdpblwiIG9yIFwiZW5kXCIpLiBIb3dldmVyLCBzaW5jZSBDaHJvbWUgdjkwIHRoaXMgaXNcbiAgICAgIC8vIHNob3dpbmcgdXAgYXMgXCItYnBzdGFydFwiIGFuZCBcIi1icGVuZFwiIChcImJlbmNocHJlc3Mgc3RhcnQvZW5kXCIpLCB3aGljaCBpcyB3aGF0IG9uZSB3b3VsZFxuICAgICAgLy8gYWN0dWFsbHkgZXhwZWN0IHNpbmNlIHRoYXQgaXMgdGhlIG1hcmsgbmFtZSB1c2VkIGluIENocm9tZURyaXZlckV4dGVuc2lvbiAtIHNlZSB0aGVcbiAgICAgIC8vICN0aW1lQmVnaW4gYW5kICN0aW1lRW5kIGltcGxlbWVudGF0aW9ucyBpbiBjaHJvbWVfZHJpdmVyX2V4dGVuc2lvbi50cy4gRm9yXG4gICAgICAvLyBiYWNrd2FyZHMtY29tcGF0aWJpbGl0eSB3aXRoIENocm9tZSB2ODkgKGFuZCBvbGRlciksIHdlIGRvIGJvdGggY2hlY2tzOiB0aGUgcGhhc2UtYmFzZWRcbiAgICAgIC8vIG9uZSAoXCJCXCIgb3IgXCJFXCIpIGFuZCBldmVudCBuYW1lLWJhc2VkICh0aGUgXCItYnAoc3RhcnQvZW5kKVwiIHN1ZmZpeCkuXG4gICAgICBjb25zdCBpc1N0YXJ0RXZlbnQgPSAocGggPT09ICdCJyAmJiBuYW1lID09PSBtYXJrTmFtZSkgfHwgbmFtZSA9PT0gbWFya05hbWUgKyAnLWJwc3RhcnQnO1xuICAgICAgY29uc3QgaXNFbmRFdmVudCA9IChwaCA9PT0gJ0UnICYmIG5hbWUgPT09IG1hcmtOYW1lKSB8fCBuYW1lID09PSBtYXJrTmFtZSArICctYnBlbmQnO1xuICAgICAgaWYgKGlzU3RhcnRFdmVudCkge1xuICAgICAgICBtYXJrU3RhcnRFdmVudCA9IGV2ZW50O1xuICAgICAgfSBlbHNlIGlmIChwaCA9PT0gJ0knICYmIG5hbWUgPT09ICduYXZpZ2F0aW9uU3RhcnQnICYmICF0aGlzLl9pZ25vcmVOYXZpZ2F0aW9uKSB7XG4gICAgICAgIC8vIGlmIGEgYmVuY2htYXJrIG1lYXN1cmVzIHJlbG9hZCBvZiBhIHBhZ2UsIHVzZSB0aGUgbGFzdFxuICAgICAgICAvLyBuYXZpZ2F0aW9uU3RhcnQgYXMgYmVnaW4gZXZlbnRcbiAgICAgICAgbWFya1N0YXJ0RXZlbnQgPSBldmVudDtcbiAgICAgIH0gZWxzZSBpZiAoaXNFbmRFdmVudCkge1xuICAgICAgICBtYXJrRW5kRXZlbnQgPSBldmVudDtcbiAgICAgIH1cbiAgICB9KTtcbiAgICBpZiAoIW1hcmtTdGFydEV2ZW50IHx8ICFtYXJrRW5kRXZlbnQpIHtcbiAgICAgIC8vIG5vdCBhbGwgZXZlbnRzIGhhdmUgYmVlbiByZWNlaXZlZCwgbm8gZnVydGhlciBwcm9jZXNzaW5nIGZvciBub3dcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICBpZiAobWFya1N0YXJ0RXZlbnQucGlkICE9PSBtYXJrRW5kRXZlbnQucGlkKSB7XG4gICAgICByZXN1bHRbJ2ludmFsaWQnXSA9IDE7XG4gICAgfVxuXG4gICAgbGV0IGdjVGltZUluU2NyaXB0ID0gMDtcbiAgICBsZXQgcmVuZGVyVGltZUluU2NyaXB0ID0gMDtcblxuICAgIGNvbnN0IGZyYW1lVGltZXN0YW1wczogbnVtYmVyW10gPSBbXTtcbiAgICBjb25zdCBmcmFtZVRpbWVzOiBudW1iZXJbXSA9IFtdO1xuICAgIGxldCBmcmFtZUNhcHR1cmVTdGFydEV2ZW50OiBQZXJmTG9nRXZlbnR8bnVsbCA9IG51bGw7XG4gICAgbGV0IGZyYW1lQ2FwdHVyZUVuZEV2ZW50OiBQZXJmTG9nRXZlbnR8bnVsbCA9IG51bGw7XG5cbiAgICBjb25zdCBpbnRlcnZhbFN0YXJ0czoge1trZXk6IHN0cmluZ106IFBlcmZMb2dFdmVudH0gPSB7fTtcbiAgICBjb25zdCBpbnRlcnZhbFN0YXJ0Q291bnQ6IHtba2V5OiBzdHJpbmddOiBudW1iZXJ9ID0ge307XG5cbiAgICBsZXQgaW5NZWFzdXJlUmFuZ2UgPSBmYWxzZTtcbiAgICBldmVudHMuZm9yRWFjaCgoZXZlbnQpID0+IHtcbiAgICAgIGNvbnN0IHBoID0gZXZlbnRbJ3BoJ107XG4gICAgICBsZXQgbmFtZSA9IGV2ZW50WyduYW1lJ10hO1xuICAgICAgbGV0IG1pY3JvSXRlcmF0aW9ucyA9IDE7XG4gICAgICBjb25zdCBtaWNyb0l0ZXJhdGlvbnNNYXRjaCA9IG5hbWUubWF0Y2goX01JQ1JPX0lURVJBVElPTlNfUkVHRVgpO1xuICAgICAgaWYgKG1pY3JvSXRlcmF0aW9uc01hdGNoKSB7XG4gICAgICAgIG5hbWUgPSBtaWNyb0l0ZXJhdGlvbnNNYXRjaFsxXTtcbiAgICAgICAgbWljcm9JdGVyYXRpb25zID0gcGFyc2VJbnQobWljcm9JdGVyYXRpb25zTWF0Y2hbMl0sIDEwKTtcbiAgICAgIH1cbiAgICAgIGlmIChldmVudCA9PT0gbWFya1N0YXJ0RXZlbnQpIHtcbiAgICAgICAgaW5NZWFzdXJlUmFuZ2UgPSB0cnVlO1xuICAgICAgfSBlbHNlIGlmIChldmVudCA9PT0gbWFya0VuZEV2ZW50KSB7XG4gICAgICAgIGluTWVhc3VyZVJhbmdlID0gZmFsc2U7XG4gICAgICB9XG4gICAgICBpZiAoIWluTWVhc3VyZVJhbmdlIHx8