metaapi.cloud-sdk
Version:
SDK for MetaApi, a professional cloud forex API which includes MetaTrader REST API and MetaTrader websocket API. Supports both MetaTrader 5 (MT5) and MetaTrader 4 (MT4). CopyFactory copy trading API included. (https://metaapi.cloud)
196 lines (195 loc) • 27.9 kB
JavaScript
'use strict';
import SynchronizationListener from '../clients/metaApi/synchronizationListener';
import moment from 'moment';
import Reservoir from './reservoir/reservoir';
import LoggerManager from '../logger';
let ConnectionHealthMonitor = class ConnectionHealthMonitor extends SynchronizationListener {
/**
* Starts health monitor
*/ start() {
const updateQuoteHealthStatusInterval = ()=>{
this._updateQuoteHealthStatus();
this._updateQuoteHealthStatusInterval = setTimeout(updateQuoteHealthStatusInterval.bind(this), this._getRandomTimeout());
};
this._updateQuoteHealthStatusInterval = setTimeout(updateQuoteHealthStatusInterval.bind(this), this._getRandomTimeout());
const measureUptimeInterval = ()=>{
this._measureUptime();
this._measureUptimeInterval = setTimeout(measureUptimeInterval.bind(this), this._getRandomTimeout());
};
this._measureUptimeInterval = setTimeout(measureUptimeInterval.bind(this), this._getRandomTimeout());
}
/**
* Stops health monitor
*/ stop() {
this._logger.debug(`${this._connection.account.id}: Stopping the monitor`);
clearTimeout(this._updateQuoteHealthStatusInterval);
clearTimeout(this._measureUptimeInterval);
}
/**
* Invoked when a symbol price was updated
* @param {String} instanceIndex index of an account instance connected
* @param {MetatraderSymbolPrice} price updated MetaTrader symbol price
*/ onSymbolPriceUpdated(instanceIndex, price) {
try {
let brokerTimestamp = moment(price.brokerTime).toDate().getTime();
this._priceUpdatedAt = new Date();
this._offset = this._priceUpdatedAt.getTime() - brokerTimestamp;
} catch (err) {
// eslint-disable-next-line no-console
this._logger.error(`${this._connection.account.id}: Failed to update quote ` + 'streaming health status on price update', err);
}
}
/**
* Invoked when a server-side application health status is received from MetaApi
* @param {String} instanceIndex index of an account instance connected
* @param {HealthStatus} status server-side application health status
* @return {Promise} promise which resolves when the asynchronous event is processed
*/ onHealthStatus(instanceIndex, status) {
this._serverHealthStatus['' + instanceIndex] = status;
}
/**
* Invoked when connection to MetaTrader terminal terminated
* @param {String} instanceIndex index of an account instance connected
* @return {Promise} promise which resolves when the asynchronous event is processed
*/ onDisconnected(instanceIndex) {
delete this._serverHealthStatus['' + instanceIndex];
}
/**
* Returns server-side application health status
* @return {HealthStatus} server-side application health status
*/ get serverHealthStatus() {
let result;
for (let s of Object.values(this._serverHealthStatus)){
if (!result) {
result = s;
} else {
for (let field of Object.keys(s)){
result[field] = result[field] || s[field];
}
}
}
return result || {};
}
/**
* Connection health status
* @typedef {Object} ConnectionHealthStatus
* @property {Boolean} connected flag indicating successful connection to API server
* @property {Boolean} connectedToBroker flag indicating successful connection to broker
* @property {Boolean} quoteStreamingHealthy flag indicating that quotes are being streamed successfully from the
* broker
* @property {Boolean} synchronized flag indicating a successful synchronization
* @property {Boolean} healthy flag indicating overall connection health status
* @property {String} message health status message
*/ /**
* Returns health status
* @returns {ConnectionHealthStatus} connection health status
*/ // eslint-disable-next-line complexity
get healthStatus() {
let status = {
connected: this._connection.terminalState.connected,
connectedToBroker: this._connection.terminalState.connectedToBroker,
quoteStreamingHealthy: this._quotesHealthy,
synchronized: this._connection.synchronized
};
status.healthy = status.connected && status.connectedToBroker && status.quoteStreamingHealthy && status.synchronized;
let message;
if (status.healthy) {
message = 'Connection to broker is stable. No health issues detected.';
} else {
message = 'Connection is not healthy because ';
let reasons = [];
if (!status.connected) {
reasons.push('connection to API server is not established or lost');
}
if (!status.connectedToBroker) {
reasons.push('connection to broker is not established or lost');
}
if (!status.synchronized) {
reasons.push('local terminal state is not synchronized to broker');
}
if (!status.quoteStreamingHealthy) {
reasons.push('quotes are not streamed from the broker properly');
}
message = message + reasons.join(' and ') + '.';
}
status.message = message;
return status;
}
/**
* Returns uptime in percents measured over specific periods of time
* @returns {Object} uptime in percents measured over specific periods of time
*/ get uptime() {
let uptime = {};
for (let e of Object.entries(this._uptimeReservoirs)){
uptime[e[0]] = e[1].getStatistics().average;
}
return uptime;
}
_measureUptime() {
try {
Object.values(this._uptimeReservoirs).forEach((r)=>r.pushMeasurement(this._connection.terminalState.connected && this._connection.terminalState.connectedToBroker && this._connection.synchronized && this._quotesHealthy ? 100 : 0));
} catch (err) {
// eslint-disable-next-line no-console
this._logger.error('failed to measure uptime for account ' + this._connection.account.id, err);
}
}
// eslint-disable-next-line complexity
_updateQuoteHealthStatus() {
try {
let serverDateTime = moment(new Date(Date.now() - this._offset));
let serverTime = serverDateTime.format('HH:mm:ss.SSS');
let dayOfWeek = serverDateTime.day();
let daysOfWeek = {
0: 'SUNDAY',
1: 'MONDAY',
2: 'TUESDAY',
3: 'WEDNESDAY',
4: 'THURSDAY',
5: 'FRIDAY',
6: 'SATURDAY'
};
let inQuoteSession = false;
if (!this._priceUpdatedAt) {
this._priceUpdatedAt = new Date();
}
if (!(this._connection.subscribedSymbols || []).length) {
this._priceUpdatedAt = new Date();
}
for (let symbol of this._connection.subscribedSymbols || []){
let specification = this._connection.terminalState.specification(symbol) || {};
let quoteSessions = (specification.quoteSessions || [])[daysOfWeek[dayOfWeek]] || [];
for (let session of quoteSessions){
if (session.from <= serverTime && session.to >= serverTime) {
inQuoteSession = true;
}
}
}
this._quotesHealthy = !this._connection.subscribedSymbols.length || !inQuoteSession || Date.now() - this._priceUpdatedAt.getTime() < this._minQuoteInterval;
} catch (err) {
this._logger.error('failed to update quote streaming health status for account ' + this._connection.account.id, err);
}
}
_getRandomTimeout() {
return (Math.random() * 59 + 1) * 1000;
}
/**
* Constructs the listener
* @param {StreamingMetaApiConnection} connection MetaApi connection instance
*/ constructor(connection){
super();
this._connection = connection;
this._minQuoteInterval = 60000;
this._serverHealthStatus = {};
this._uptimeReservoirs = {
'5m': new Reservoir(300, 5 * 60 * 1000),
'1h': new Reservoir(600, 60 * 60 * 1000),
'1d': new Reservoir(24 * 60, 24 * 60 * 60 * 1000),
'1w': new Reservoir(24 * 7, 7 * 24 * 60 * 60 * 1000)
};
this._logger = LoggerManager.getLogger('ConnectionHealthMonitor');
}
};
/**
* Tracks connection health status
*/ export { ConnectionHealthMonitor as default };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBTeW5jaHJvbml6YXRpb25MaXN0ZW5lciBmcm9tICcuLi9jbGllbnRzL21ldGFBcGkvc3luY2hyb25pemF0aW9uTGlzdGVuZXInO1xuaW1wb3J0IG1vbWVudCBmcm9tICdtb21lbnQnO1xuaW1wb3J0IFJlc2Vydm9pciBmcm9tICcuL3Jlc2Vydm9pci9yZXNlcnZvaXInO1xuaW1wb3J0IExvZ2dlck1hbmFnZXIgZnJvbSAnLi4vbG9nZ2VyJztcblxuLyoqXG4gKiBUcmFja3MgY29ubmVjdGlvbiBoZWFsdGggc3RhdHVzXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIENvbm5lY3Rpb25IZWFsdGhNb25pdG9yIGV4dGVuZHMgU3luY2hyb25pemF0aW9uTGlzdGVuZXIge1xuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3RzIHRoZSBsaXN0ZW5lclxuICAgKiBAcGFyYW0ge1N0cmVhbWluZ01ldGFBcGlDb25uZWN0aW9ufSBjb25uZWN0aW9uIE1ldGFBcGkgY29ubmVjdGlvbiBpbnN0YW5jZVxuICAgKi9cbiAgY29uc3RydWN0b3IoY29ubmVjdGlvbikge1xuICAgIHN1cGVyKCk7XG4gICAgdGhpcy5fY29ubmVjdGlvbiA9IGNvbm5lY3Rpb247XG4gICAgdGhpcy5fbWluUXVvdGVJbnRlcnZhbCA9IDYwMDAwO1xuICAgIHRoaXMuX3NlcnZlckhlYWx0aFN0YXR1cyA9IHt9O1xuICAgIHRoaXMuX3VwdGltZVJlc2Vydm9pcnMgPSB7XG4gICAgICAnNW0nOiBuZXcgUmVzZXJ2b2lyKDMwMCwgNSAqIDYwICogMTAwMCksXG4gICAgICAnMWgnOiBuZXcgUmVzZXJ2b2lyKDYwMCwgNjAgKiA2MCAqIDEwMDApLFxuICAgICAgJzFkJzogbmV3IFJlc2Vydm9pcigyNCAqIDYwLCAyNCAqIDYwICogNjAgKiAxMDAwKSxcbiAgICAgICcxdyc6IG5ldyBSZXNlcnZvaXIoMjQgKiA3LCA3ICogMjQgKiA2MCAqIDYwICogMTAwMCksXG4gICAgfTtcbiAgICB0aGlzLl9sb2dnZXIgPSBMb2dnZXJNYW5hZ2VyLmdldExvZ2dlcignQ29ubmVjdGlvbkhlYWx0aE1vbml0b3InKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdGFydHMgaGVhbHRoIG1vbml0b3JcbiAgICovXG4gIHN0YXJ0KCkge1xuICAgIGNvbnN0IHVwZGF0ZVF1b3RlSGVhbHRoU3RhdHVzSW50ZXJ2YWwgPSAoKSA9PiB7XG4gICAgICB0aGlzLl91cGRhdGVRdW90ZUhlYWx0aFN0YXR1cygpO1xuICAgICAgdGhpcy5fdXBkYXRlUXVvdGVIZWFsdGhTdGF0dXNJbnRlcnZhbCA9IHNldFRpbWVvdXQoXG4gICAgICAgIHVwZGF0ZVF1b3RlSGVhbHRoU3RhdHVzSW50ZXJ2YWwuYmluZCh0aGlzKSxcbiAgICAgICAgdGhpcy5fZ2V0UmFuZG9tVGltZW91dCgpXG4gICAgICApO1xuICAgIH07XG4gICAgdGhpcy5fdXBkYXRlUXVvdGVIZWFsdGhTdGF0dXNJbnRlcnZhbCA9IHNldFRpbWVvdXQoXG4gICAgICB1cGRhdGVRdW90ZUhlYWx0aFN0YXR1c0ludGVydmFsLmJpbmQodGhpcyksXG4gICAgICB0aGlzLl9nZXRSYW5kb21UaW1lb3V0KClcbiAgICApO1xuICAgIGNvbnN0IG1lYXN1cmVVcHRpbWVJbnRlcnZhbCA9ICgpID0+IHtcbiAgICAgIHRoaXMuX21lYXN1cmVVcHRpbWUoKTtcbiAgICAgIHRoaXMuX21lYXN1cmVVcHRpbWVJbnRlcnZhbCA9IHNldFRpbWVvdXQobWVhc3VyZVVwdGltZUludGVydmFsLmJpbmQodGhpcyksIHRoaXMuX2dldFJhbmRvbVRpbWVvdXQoKSk7XG4gICAgfTtcbiAgICB0aGlzLl9tZWFzdXJlVXB0aW1lSW50ZXJ2YWwgPSBzZXRUaW1lb3V0KG1lYXN1cmVVcHRpbWVJbnRlcnZhbC5iaW5kKHRoaXMpLCB0aGlzLl9nZXRSYW5kb21UaW1lb3V0KCkpO1xuICB9XG5cbiAgLyoqXG4gICAqIFN0b3BzIGhlYWx0aCBtb25pdG9yXG4gICAqL1xuICBzdG9wKCkge1xuICAgIHRoaXMuX2xvZ2dlci5kZWJ1ZyhgJHt0aGlzLl9jb25uZWN0aW9uLmFjY291bnQuaWR9OiBTdG9wcGluZyB0aGUgbW9uaXRvcmApO1xuICAgIGNsZWFyVGltZW91dCh0aGlzLl91cGRhdGVRdW90ZUhlYWx0aFN0YXR1c0ludGVydmFsKTtcbiAgICBjbGVhclRpbWVvdXQodGhpcy5fbWVhc3VyZVVwdGltZUludGVydmFsKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbnZva2VkIHdoZW4gYSBzeW1ib2wgcHJpY2Ugd2FzIHVwZGF0ZWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGluc3RhbmNlSW5kZXggaW5kZXggb2YgYW4gYWNjb3VudCBpbnN0YW5jZSBjb25uZWN0ZWRcbiAgICogQHBhcmFtIHtNZXRhdHJhZGVyU3ltYm9sUHJpY2V9IHByaWNlIHVwZGF0ZWQgTWV0YVRyYWRlciBzeW1ib2wgcHJpY2VcbiAgICovXG4gIG9uU3ltYm9sUHJpY2VVcGRhdGVkKGluc3RhbmNlSW5kZXgsIHByaWNlKSB7XG4gICAgdHJ5IHtcbiAgICAgIGxldCBicm9rZXJUaW1lc3RhbXAgPSBtb21lbnQocHJpY2UuYnJva2VyVGltZSkudG9EYXRlKCkuZ2V0VGltZSgpO1xuICAgICAgdGhpcy5fcHJpY2VVcGRhdGVkQXQgPSBuZXcgRGF0ZSgpO1xuICAgICAgdGhpcy5fb2Zmc2V0ID0gdGhpcy5fcHJpY2VVcGRhdGVkQXQuZ2V0VGltZSgpIC0gYnJva2VyVGltZXN0YW1wO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnNvbGVcbiAgICAgIHRoaXMuX2xvZ2dlci5lcnJvcihgJHt0aGlzLl9jb25uZWN0aW9uLmFjY291bnQuaWR9OiBGYWlsZWQgdG8gdXBkYXRlIHF1b3RlIGAgKyBcbiAgICAgICAgJ3N0cmVhbWluZyBoZWFsdGggc3RhdHVzIG9uIHByaWNlIHVwZGF0ZScsIGVycik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgd2hlbiBhIHNlcnZlci1zaWRlIGFwcGxpY2F0aW9uIGhlYWx0aCBzdGF0dXMgaXMgcmVjZWl2ZWQgZnJvbSBNZXRhQXBpXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBpbnN0YW5jZUluZGV4IGluZGV4IG9mIGFuIGFjY291bnQgaW5zdGFuY2UgY29ubmVjdGVkXG4gICAqIEBwYXJhbSB7SGVhbHRoU3RhdHVzfSBzdGF0dXMgc2VydmVyLXNpZGUgYXBwbGljYXRpb24gaGVhbHRoIHN0YXR1c1xuICAgKiBAcmV0dXJuIHtQcm9taXNlfSBwcm9taXNlIHdoaWNoIHJlc29sdmVzIHdoZW4gdGhlIGFzeW5jaHJvbm91cyBldmVudCBpcyBwcm9jZXNzZWRcbiAgICovXG4gIG9uSGVhbHRoU3RhdHVzKGluc3RhbmNlSW5kZXgsIHN0YXR1cykge1xuICAgIHRoaXMuX3NlcnZlckhlYWx0aFN0YXR1c1snJyArIGluc3RhbmNlSW5kZXhdID0gc3RhdHVzO1xuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgd2hlbiBjb25uZWN0aW9uIHRvIE1ldGFUcmFkZXIgdGVybWluYWwgdGVybWluYXRlZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gaW5zdGFuY2VJbmRleCBpbmRleCBvZiBhbiBhY2NvdW50IGluc3RhbmNlIGNvbm5lY3RlZFxuICAgKiBAcmV0dXJuIHtQcm9taXNlfSBwcm9taXNlIHdoaWNoIHJlc29sdmVzIHdoZW4gdGhlIGFzeW5jaHJvbm91cyBldmVudCBpcyBwcm9jZXNzZWRcbiAgICovXG4gIG9uRGlzY29ubmVjdGVkKGluc3RhbmNlSW5kZXgpIHtcbiAgICBkZWxldGUgdGhpcy5fc2VydmVySGVhbHRoU3RhdHVzWycnICsgaW5zdGFuY2VJbmRleF07XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBzZXJ2ZXItc2lkZSBhcHBsaWNhdGlvbiBoZWFsdGggc3RhdHVzXG4gICAqIEByZXR1cm4ge0hlYWx0aFN0YXR1c30gc2VydmVyLXNpZGUgYXBwbGljYXRpb24gaGVhbHRoIHN0YXR1c1xuICAgKi9cbiAgZ2V0IHNlcnZlckhlYWx0aFN0YXR1cygpIHtcbiAgICBsZXQgcmVzdWx0O1xuICAgIGZvciAobGV0IHMgb2YgT2JqZWN0LnZhbHVlcyh0aGlzLl9zZXJ2ZXJIZWFsdGhTdGF0dXMpKSB7XG4gICAgICBpZiAoIXJlc3VsdCkge1xuICAgICAgICByZXN1bHQgPSBzO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgZm9yIChsZXQgZmllbGQgb2YgT2JqZWN0LmtleXMocykpIHtcbiAgICAgICAgICByZXN1bHRbZmllbGRdID0gcmVzdWx0W2ZpZWxkXSB8fCBzW2ZpZWxkXTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0IHx8IHt9O1xuICB9XG5cbiAgLyoqXG4gICAqIENvbm5lY3Rpb24gaGVhbHRoIHN0YXR1c1xuICAgKiBAdHlwZWRlZiB7T2JqZWN0fSBDb25uZWN0aW9uSGVhbHRoU3RhdHVzXG4gICAqIEBwcm9wZXJ0eSB7Qm9vbGVhbn0gY29ubmVjdGVkIGZsYWcgaW5kaWNhdGluZyBzdWNjZXNzZnVsIGNvbm5lY3Rpb24gdG8gQVBJIHNlcnZlclxuICAgKiBAcHJvcGVydHkge0Jvb2xlYW59IGNvbm5lY3RlZFRvQnJva2VyIGZsYWcgaW5kaWNhdGluZyBzdWNjZXNzZnVsIGNvbm5lY3Rpb24gdG8gYnJva2VyXG4gICAqIEBwcm9wZXJ0eSB7Qm9vbGVhbn0gcXVvdGVTdHJlYW1pbmdIZWFsdGh5IGZsYWcgaW5kaWNhdGluZyB0aGF0IHF1b3RlcyBhcmUgYmVpbmcgc3RyZWFtZWQgc3VjY2Vzc2Z1bGx5IGZyb20gdGhlXG4gICAqIGJyb2tlclxuICAgKiBAcHJvcGVydHkge0Jvb2xlYW59IHN5bmNocm9uaXplZCBmbGFnIGluZGljYXRpbmcgYSBzdWNjZXNzZnVsIHN5bmNocm9uaXphdGlvblxuICAgKiBAcHJvcGVydHkge0Jvb2xlYW59IGhlYWx0aHkgZmxhZyBpbmRpY2F0aW5nIG92ZXJhbGwgY29ubmVjdGlvbiBoZWFsdGggc3RhdHVzXG4gICAqIEBwcm9wZXJ0eSB7U3RyaW5nfSBtZXNzYWdlIGhlYWx0aCBzdGF0dXMgbWVzc2FnZVxuICAgKi9cblxuICAvKipcbiAgICogUmV0dXJucyBoZWFsdGggc3RhdHVzXG4gICAqIEByZXR1cm5zIHtDb25uZWN0aW9uSGVhbHRoU3RhdHVzfSBjb25uZWN0aW9uIGhlYWx0aCBzdGF0dXNcbiAgICovXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBjb21wbGV4aXR5XG4gIGdldCBoZWFsdGhTdGF0dXMoKSB7XG4gICAgbGV0IHN0YXR1cyA9IHtcbiAgICAgIGNvbm5lY3RlZDogdGhpcy5fY29ubmVjdGlvbi50ZXJtaW5hbFN0YXRlLmNvbm5lY3RlZCxcbiAgICAgIGNvbm5lY3RlZFRvQnJva2VyOiB0aGlzLl9jb25uZWN0aW9uLnRlcm1pbmFsU3RhdGUuY29ubmVjdGVkVG9Ccm9rZXIsXG4gICAgICBxdW90ZVN0cmVhbWluZ0hlYWx0aHk6IHRoaXMuX3F1b3Rlc0hlYWx0aHksXG4gICAgICBzeW5jaHJvbml6ZWQ6IHRoaXMuX2Nvbm5lY3Rpb24uc3luY2hyb25pemVkXG4gICAgfTtcbiAgICBzdGF0dXMuaGVhbHRoeSA9IHN0YXR1cy5jb25uZWN0ZWQgJiYgc3RhdHVzLmNvbm5lY3RlZFRvQnJva2VyICYmIHN0YXR1cy5xdW90ZVN0cmVhbWluZ0hlYWx0aHkgJiZcbiAgICAgIHN0YXR1cy5zeW5jaHJvbml6ZWQ7XG4gICAgbGV0IG1lc3NhZ2U7XG4gICAgaWYgKHN0YXR1cy5oZWFsdGh5KSB7XG4gICAgICBtZXNzYWdlID0gJ0Nvbm5lY3Rpb24gdG8gYnJva2VyIGlzIHN0YWJsZS4gTm8gaGVhbHRoIGlzc3VlcyBkZXRlY3RlZC4nO1xuICAgIH0gZWxzZSB7XG4gICAgICBtZXNzYWdlID0gJ0Nvbm5lY3Rpb24gaXMgbm90IGhlYWx0aHkgYmVjYXVzZSAnO1xuICAgICAgbGV0IHJlYXNvbnMgPSBbXTtcbiAgICAgIGlmICghc3RhdHVzLmNvbm5lY3RlZCkge1xuICAgICAgICByZWFzb25zLnB1c2goJ2Nvbm5lY3Rpb24gdG8gQVBJIHNlcnZlciBpcyBub3QgZXN0YWJsaXNoZWQgb3IgbG9zdCcpO1xuICAgICAgfVxuICAgICAgaWYgKCFzdGF0dXMuY29ubmVjdGVkVG9Ccm9rZXIpIHtcbiAgICAgICAgcmVhc29ucy5wdXNoKCdjb25uZWN0aW9uIHRvIGJyb2tlciBpcyBub3QgZXN0YWJsaXNoZWQgb3IgbG9zdCcpO1xuICAgICAgfVxuICAgICAgaWYgKCFzdGF0dXMuc3luY2hyb25pemVkKSB7XG4gICAgICAgIHJlYXNvbnMucHVzaCgnbG9jYWwgdGVybWluYWwgc3RhdGUgaXMgbm90IHN5bmNocm9uaXplZCB0byBicm9rZXInKTtcbiAgICAgIH1cbiAgICAgIGlmICghc3RhdHVzLnF1b3RlU3RyZWFtaW5nSGVhbHRoeSkge1xuICAgICAgICByZWFzb25zLnB1c2goJ3F1b3RlcyBhcmUgbm90IHN0cmVhbWVkIGZyb20gdGhlIGJyb2tlciBwcm9wZXJseScpO1xuICAgICAgfVxuICAgICAgbWVzc2FnZSA9IG1lc3NhZ2UgKyByZWFzb25zLmpvaW4oJyBhbmQgJykgKyAnLic7XG4gICAgfVxuICAgIHN0YXR1cy5tZXNzYWdlID0gbWVzc2FnZTtcbiAgICByZXR1cm4gc3RhdHVzO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdXB0aW1lIGluIHBlcmNlbnRzIG1lYXN1cmVkIG92ZXIgc3BlY2lmaWMgcGVyaW9kcyBvZiB0aW1lXG4gICAqIEByZXR1cm5zIHtPYmplY3R9IHVwdGltZSBpbiBwZXJjZW50cyBtZWFzdXJlZCBvdmVyIHNwZWNpZmljIHBlcmlvZHMgb2YgdGltZVxuICAgKi9cbiAgZ2V0IHVwdGltZSgpIHtcbiAgICBsZXQgdXB0aW1lID0ge307XG4gICAgZm9yIChsZXQgZSBvZiBPYmplY3QuZW50cmllcyh0aGlzLl91cHRpbWVSZXNlcnZvaXJzKSkge1xuICAgICAgdXB0aW1lW2VbMF1dID0gZVsxXS5nZXRTdGF0aXN0aWNzKCkuYXZlcmFnZTtcbiAgICB9XG4gICAgcmV0dXJuIHVwdGltZTtcbiAgfVxuXG4gIF9tZWFzdXJlVXB0aW1lKCkge1xuICAgIHRyeSB7XG4gICAgICBPYmplY3QudmFsdWVzKHRoaXMuX3VwdGltZVJlc2Vydm9pcnMpLmZvckVhY2gociA9PiByLnB1c2hNZWFzdXJlbWVudCh0aGlzLl9jb25uZWN0aW9uLnRlcm1pbmFsU3RhdGUuY29ubmVjdGVkICYmXG4gICAgICAgIHRoaXMuX2Nvbm5lY3Rpb24udGVybWluYWxTdGF0ZS5jb25uZWN0ZWRUb0Jyb2tlciAmJiB0aGlzLl9jb25uZWN0aW9uLnN5bmNocm9uaXplZCAmJlxuICAgICAgICB0aGlzLl9xdW90ZXNIZWFsdGh5ID8gMTAwIDogMCkpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnNvbGVcbiAgICAgIHRoaXMuX2xvZ2dlci5lcnJvcignZmFpbGVkIHRvIG1lYXN1cmUgdXB0aW1lIGZvciBhY2NvdW50ICcgK1xuICAgICAgICB0aGlzLl9jb25uZWN0aW9uLmFjY291bnQuaWQsIGVycik7XG4gICAgfVxuICB9XG5cbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGNvbXBsZXhpdHlcbiAgX3VwZGF0ZVF1b3RlSGVhbHRoU3RhdHVzKCkge1xuICAgIHRyeSB7XG4gICAgICBsZXQgc2VydmVyRGF0ZVRpbWUgPSBtb21lbnQobmV3IERhdGUoRGF0ZS5ub3coKSAtIHRoaXMuX29mZnNldCkpO1xuICAgICAgbGV0IHNlcnZlclRpbWUgPSBzZXJ2ZXJEYXRlVGltZS5mb3JtYXQoJ0hIOm1tOnNzLlNTUycpO1xuICAgICAgbGV0IGRheU9mV2VlayA9IHNlcnZlckRhdGVUaW1lLmRheSgpO1xuICAgICAgbGV0IGRheXNPZldlZWsgPSB7XG4gICAgICAgIDA6ICdTVU5EQVknLFxuICAgICAgICAxOiAnTU9OREFZJyxcbiAgICAgICAgMjogJ1RVRVNEQVknLFxuICAgICAgICAzOiAnV0VETkVTREFZJyxcbiAgICAgICAgNDogJ1RIVVJTREFZJyxcbiAgICAgICAgNTogJ0ZSSURBWScsXG4gICAgICAgIDY6ICdTQVRVUkRBWSdcbiAgICAgIH07XG4gICAgICBsZXQgaW5RdW90ZVNlc3Npb24gPSBmYWxzZTtcbiAgICAgIGlmICghdGhpcy5fcHJpY2VVcGRhdGVkQXQpIHtcbiAgICAgICAgdGhpcy5fcHJpY2VVcGRhdGVkQXQgPSBuZXcgRGF0ZSgpO1xuICAgICAgfVxuICAgICAgaWYgKCEodGhpcy5fY29ubmVjdGlvbi5zdWJzY3JpYmVkU3ltYm9scyB8fCBbXSkubGVuZ3RoKSB7XG4gICAgICAgIHRoaXMuX3ByaWNlVXBkYXRlZEF0ID0gbmV3IERhdGUoKTtcbiAgICAgIH1cbiAgICAgIGZvciAobGV0IHN5bWJvbCBvZiB0aGlzLl9jb25uZWN0aW9uLnN1YnNjcmliZWRTeW1ib2xzIHx8IFtdKSB7XG4gICAgICAgIGxldCBzcGVjaWZpY2F0aW9uID0gdGhpcy5fY29ubmVjdGlvbi50ZXJtaW5hbFN0YXRlLnNwZWNpZmljYXRpb24oc3ltYm9sKSB8fCB7fTtcbiAgICAgICAgbGV0IHF1b3RlU2Vzc2lvbnMgPSAoc3BlY2lmaWNhdGlvbi5xdW90ZVNlc3Npb25zIHx8IFtdKVtkYXlzT2ZXZWVrW2RheU9mV2Vla11dIHx8IFtdO1xuICAgICAgICBmb3IgKGxldCBzZXNzaW9uIG9mIHF1b3RlU2Vzc2lvbnMpIHtcbiAgICAgICAgICBpZiAoc2Vzc2lvbi5mcm9tIDw9IHNlcnZlclRpbWUgJiYgc2Vzc2lvbi50byA+PSBzZXJ2ZXJUaW1lKSB7XG4gICAgICAgICAgICBpblF1b3RlU2Vzc2lvbiA9IHRydWU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgICB0aGlzLl9xdW90ZXNIZWFsdGh5ID0gIXRoaXMuX2Nvbm5lY3Rpb24uc3Vic2NyaWJlZFN5bWJvbHMubGVuZ3RoIHx8ICFpblF1b3RlU2Vzc2lvbiB8fFxuICAgICAgICAoRGF0ZS5ub3coKSAtIHRoaXMuX3ByaWNlVXBkYXRlZEF0LmdldFRpbWUoKSA8IHRoaXMuX21pblF1b3RlSW50ZXJ2YWwpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgdGhpcy5fbG9nZ2VyLmVycm9yKCdmYWlsZWQgdG8gdXBkYXRlIHF1b3RlIHN0cmVhbWluZyBoZWFsdGggc3RhdHVzIGZvciBhY2NvdW50ICcgK1xuICAgICAgICB0aGlzLl9jb25uZWN0aW9uLmFjY291bnQuaWQsIGVycik7XG4gICAgfVxuICB9XG5cbiAgX2dldFJhbmRvbVRpbWVvdXQoKSB7XG4gICAgcmV0dXJuIChNYXRoLnJhbmRvbSgpICogNTkgKyAxKSAqIDEwMDA7XG4gIH1cblxufVxuIl0sIm5hbWVzIjpbIlN5bmNocm9uaXphdGlvbkxpc3RlbmVyIiwibW9tZW50IiwiUmVzZXJ2b2lyIiwiTG9nZ2VyTWFuYWdlciIsIkNvbm5lY3Rpb25IZWFsdGhNb25pdG9yIiwic3RhcnQiLCJ1cGRhdGVRdW90ZUhlYWx0aFN0YXR1c0ludGVydmFsIiwiX3VwZGF0ZVF1b3RlSGVhbHRoU3RhdHVzIiwiX3VwZGF0ZVF1b3RlSGVhbHRoU3RhdHVzSW50ZXJ2YWwiLCJzZXRUaW1lb3V0IiwiYmluZCIsIl9nZXRSYW5kb21UaW1lb3V0IiwibWVhc3VyZVVwdGltZUludGVydmFsIiwiX21lYXN1cmVVcHRpbWUiLCJfbWVhc3VyZVVwdGltZUludGVydmFsIiwic3RvcCIsIl9sb2dnZXIiLCJkZWJ1ZyIsIl9jb25uZWN0aW9uIiwiYWNjb3VudCIsImlkIiwiY2xlYXJUaW1lb3V0Iiwib25TeW1ib2xQcmljZVVwZGF0ZWQiLCJpbnN0YW5jZUluZGV4IiwicHJpY2UiLCJicm9rZXJUaW1lc3RhbXAiLCJicm9rZXJUaW1lIiwidG9EYXRlIiwiZ2V0VGltZSIsIl9wcmljZVVwZGF0ZWRBdCIsIkRhdGUiLCJfb2Zmc2V0IiwiZXJyIiwiZXJyb3IiLCJvbkhlYWx0aFN0YXR1cyIsInN0YXR1cyIsIl9zZXJ2ZXJIZWFsdGhTdGF0dXMiLCJvbkRpc2Nvbm5lY3RlZCIsInNlcnZlckhlYWx0aFN0YXR1cyIsInJlc3VsdCIsInMiLCJPYmplY3QiLCJ2YWx1ZXMiLCJmaWVsZCIsImtleXMiLCJoZWFsdGhTdGF0dXMiLCJjb25uZWN0ZWQiLCJ0ZXJtaW5hbFN0YXRlIiwiY29ubmVjdGVkVG9Ccm9rZXIiLCJxdW90ZVN0cmVhbWluZ0hlYWx0aHkiLCJfcXVvdGVzSGVhbHRoeSIsInN5bmNocm9uaXplZCIsImhlYWx0aHkiLCJtZXNzYWdlIiwicmVhc29ucyIsInB1c2giLCJqb2luIiwidXB0aW1lIiwiZSIsImVudHJpZXMiLCJfdXB0aW1lUmVzZXJ2b2lycyIsImdldFN0YXRpc3RpY3MiLCJhdmVyYWdlIiwiZm9yRWFjaCIsInIiLCJwdXNoTWVhc3VyZW1lbnQiLCJzZXJ2ZXJEYXRlVGltZSIsIm5vdyIsInNlcnZlclRpbWUiLCJmb3JtYXQiLCJkYXlPZldlZWsiLCJkYXkiLCJkYXlzT2ZXZWVrIiwiaW5RdW90ZVNlc3Npb24iLCJzdWJzY3JpYmVkU3ltYm9scyIsImxlbmd0aCIsInN5bWJvbCIsInNwZWNpZmljYXRpb24iLCJxdW90ZVNlc3Npb25zIiwic2Vzc2lvbiIsImZyb20iLCJ0byIsIl9taW5RdW90ZUludGVydmFsIiwiTWF0aCIsInJhbmRvbSIsImNvbnN0cnVjdG9yIiwiY29ubmVjdGlvbiIsImdldExvZ2dlciJdLCJtYXBwaW5ncyI6IkFBQUE7QUFFQSxPQUFPQSw2QkFBNkIsNkNBQTZDO0FBQ2pGLE9BQU9DLFlBQVksU0FBUztBQUM1QixPQUFPQyxlQUFlLHdCQUF3QjtBQUM5QyxPQUFPQyxtQkFBbUIsWUFBWTtBQUt2QixJQUFBLEFBQU1DLDBCQUFOLE1BQU1BLGdDQUFnQ0o7SUFvQm5EOztHQUVDLEdBQ0RLLFFBQVE7UUFDTixNQUFNQyxrQ0FBa0M7WUFDdEMsSUFBSSxDQUFDQyx3QkFBd0I7WUFDN0IsSUFBSSxDQUFDQyxnQ0FBZ0MsR0FBR0MsV0FDdENILGdDQUFnQ0ksSUFBSSxDQUFDLElBQUksR0FDekMsSUFBSSxDQUFDQyxpQkFBaUI7UUFFMUI7UUFDQSxJQUFJLENBQUNILGdDQUFnQyxHQUFH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