UNPKG

31.8 kBJavaScriptView Raw
1'use strict';
2
3// Utility that collects real time function call performance and reliability
4// metrics. The stats are computed using rolling windows of call successes,
5// failures, timeouts, circuit breaker rejections and latencies.
6
7var _ = require('underscore');
8var events = require('abacus-events');
9
10var keys = _.keys;
11var filter = _.filter;
12var last = _.last;
13var extend = _.extend;
14var reduce = _.reduce;
15var map = _.map;
16var find = _.find;
17
18// Setup debug log
19var debug = require('abacus-debug')('abacus-perf');
20
21// Set up an event emitter allowing other modules to listen to accumulated call
22// stats events
23var emitter = events.emitter('call-metrics/emitter');
24var on = function on(e, l) {
25 emitter.on(e, l);
26};
27
28// Set up an event emitter used to report function call metrics
29var calls = events.emitter('abacus-perf/calls');
30
31// Report function call metrics
32var report = function report(name, time, latency, err, timeout, reject, circuit) {
33 var t = time || Date.now();
34 var call = function call() {
35 return {
36 name: name,
37 time: t,
38 latency: latency === undefined ? Date.now() - t : latency,
39 error: err || undefined,
40 timeout: timeout || 0,
41 reject: reject || false,
42 circuit: circuit || 'closed'
43 };
44 };
45 calls.emit('message', {
46 metrics: {
47 call: call()
48 }
49 });
50};
51
52// Convert a list of buckets to a list of buckets in a rolling time window.
53// Filter out the buckets that are out of the time window, and create a new
54// bucket if necessary for the given time.
55var roll = function roll(buckets, time, win, max, proto) {
56 var i = Math.ceil(time / (win / max));
57 var current = filter(buckets, function (b) {
58 return b.i > i - max;
59 });
60 var all = current.length !== 0 && last(current).i === i ? current : current.concat([extend({}, proto(), {
61 i: i
62 })]);
63 return last(all, max);
64};
65
66// Return a rolling window of 10 secs of counts
67var rollCounts = function rollCounts(buckets, time) {
68 return roll(buckets, time, 10000, 10, function () {
69 return {
70 i: 0,
71 ok: 0,
72 errors: 0,
73 timeouts: 0,
74 rejects: 0
75 };
76 });
77};
78
79// Return a rolling window of 60 secs of latencies
80var rollLatencies = function rollLatencies(buckets, time) {
81 return roll(buckets, time, 60000, 6, function () {
82 return {
83 i: 0,
84 latencies: []
85 };
86 });
87};
88
89// Return a rolling window of 2 secs of health reports
90var rollHealth = function rollHealth(buckets, time, counts) {
91 // Compute health from the given counts
92 var b = reduce(counts, function (a, c) {
93 return {
94 ok: a.ok + c.ok,
95 errors: a.errors + c.errors + c.timeouts + c.rejects
96 };
97 }, {
98 ok: 0,
99 errors: 0
100 });
101
102 // Roll the window and store the computed health in the last bucket
103 var health = roll(buckets, time, 2000, 4, function () {
104 return {
105 i: 0,
106 ok: 0,
107 errors: 0
108 };
109 });
110 extend(last(health), b);
111 return health;
112};
113
114// Store accumulated function call stats per function name
115// Warning: accumulatedStats is a mutable variable
116var accumulatedStats = {};
117
118// Return the accumulated stats for a function
119var stats = function stats(name, time, roll) {
120 var t = time || Date.now();
121 var s = accumulatedStats[name] || {
122 name: name,
123 time: t,
124 counts: [],
125 latencies: [],
126 health: [],
127 circuit: 'closed'
128 };
129 /* eslint no-extra-parens: 1 */
130 return roll !== false ? (function () {
131 var counts = rollCounts(s.counts, t);
132 return {
133 name: name,
134 time: t,
135 counts: counts,
136 latencies: rollLatencies(s.latencies, t),
137 health: rollHealth(s.health, t, counts),
138 circuit: s.circuit
139 };
140 })() : {
141 name: name,
142 time: t,
143 counts: s.counts,
144 latencies: s.latencies,
145 health: s.health,
146 circuit: s.circuit
147 };
148};
149
150// Reset the accumulated reliability stats for a function (but keep the
151// accumulated latencies)
152var reset = function reset(name, time) {
153 var t = time || Date.now();
154
155 // Warning: mutating variable accumulatedStats
156 /* eslint no-extra-parens: 1 */
157 var astats = (function (s) {
158 return s ? {
159 name: name,
160 time: t,
161 counts: [],
162 latencies: s.latencies,
163 health: [],
164 circuit: 'closed'
165 } : {
166 name: name,
167 time: t,
168 counts: [],
169 latencies: [],
170 health: [],
171 circuit: 'closed'
172 };
173 })(accumulatedStats[name]);
174 accumulatedStats[name] = astats;
175
176 // Propagate new stats to all the listeners
177 debug('Emitting stats for function %s', name);
178 emitter.emit('message', {
179 metrics: {
180 stats: astats
181 }
182 });
183 return astats;
184};
185
186// Return all the accumulated stats
187var all = function all(time, roll) {
188 var t = time || Date.now();
189 return map(keys(accumulatedStats), function (k) {
190 return stats(k, t, roll);
191 });
192};
193
194// Process function call metrics and update accumulated call stats
195var accumulateStats = function accumulateStats(name, time, latency, err, timeout, reject, circuit) {
196 debug('Accumulating stats for function %s', name);
197 debug('latency %d, err %s, timeout %d, reject %s, circuit %s', latency, err, timeout, reject, circuit);
198
199 // Retrieve the current call stats for the given function
200 var s = stats(name, time, false);
201
202 // Compute up to date counts window and increment counts in the last bucket
203 var counts = rollCounts(s ? s.counts : [], time);
204 var updateCount = function updateCount(c) {
205 c.ok = c.ok + (!err && !timeout && !reject ? 1 : 0);
206 c.errors = c.errors + (err ? 1 : 0);
207 c.timeouts = c.timeouts + (timeout ? 1 : 0);
208 c.rejects = c.rejects + (reject ? 1 : 0);
209 debug('%d ok, %d errors, %d timeouts, %d rejects, %d count buckets', c.ok, c.errors, c.timeouts, c.rejects, counts.length);
210 };
211 updateCount(last(counts));
212
213 // Compute up to date latencies window and add latency to the last bucket,
214 // up to the max bucket size
215 var latencies = rollLatencies(s ? s.latencies : [], time);
216 if (!err && !timeout && !reject) {
217 var updateLatency = function updateLatency(l) {
218 l.latencies = l.latencies.length < 100 ? l.latencies.concat([latency]) : l.latencies;
219 debug('%d latencies, %d latencies buckets', l.latencies.length, latencies.length);
220 };
221 updateLatency(last(latencies));
222 }
223
224 // Compute up to date health report window
225 var health = rollHealth(s ? s.health : [], time, counts);
226 var h = last(health);
227 debug('%d ok, %d errors, %d health buckets', h.ok, h.errors, health.length);
228
229 // Store and return the new accumulated function call stats
230 // Warning: mutating variable accumulatedStats
231 var astats = {
232 name: name,
233 time: time,
234 counts: counts,
235 latencies: latencies,
236 health: health,
237 circuit: circuit
238 };
239 accumulatedStats[name] = astats;
240
241 // Propagate new stats to all the listeners
242 debug('Emitting stats for function %s', name);
243 emitter.emit('message', {
244 metrics: {
245 stats: astats
246 }
247 });
248 return astats;
249};
250
251// Process function call metrics messages and function call stats messages
252var onMessage = function onMessage(msg) {
253 if (msg.metrics) {
254 debug('Received message %o', keys(msg).concat(keys(msg.metrics)));
255 if (msg.metrics.call) {
256 // Process call metrics and emit updated accumulated call stats
257 var c = msg.metrics.call;
258 accumulateStats(c.name, c.time, c.latency, c.error, c.timeout, c.reject, c.circuit);
259 }
260 if (msg.metrics.stats) {
261 // Store latest accumulated stats
262 debug('Storing stats for function %s', msg.metrics.stats.name);
263 accumulatedStats[msg.metrics.stats.name] = msg.metrics.stats;
264 }
265 }
266};
267
268// Determine the health of the app based on the accumulated metrics
269var healthy = function healthy(threshold) {
270
271 // Go through each function call metrics
272 return find(all(Date.now(), false), function (stat) {
273
274 // Go through its health status and calculate total requests & errors
275 var total = reduce(stat.health, function (a, c) {
276 return {
277 requests: a.requests + a.errors + c.ok + c.errors,
278 errors: a.errors + c.errors
279 };
280 }, {
281 requests: 0,
282 errors: 0
283 });
284
285 var percent = 100 * (total.errors / total.requests || 1);
286 debug('%s has %d% failure rate', stat.name, percent);
287
288 // If one function call is not healthy, conclude that the app is
289 // not healthy.
290 return percent > (threshold || 5);
291 }) ? false : true;
292};
293
294calls.on('message', onMessage);
295
296// Export our public functions
297module.exports.report = report;
298module.exports.stats = stats;
299module.exports.reset = reset;
300module.exports.healthy = healthy;
301module.exports.all = all;
302module.exports.onMessage = onMessage;
303module.exports.on = on;
304//# sourceMappingURL=data:application/json;base64,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QUlyRCxXQUFPLE9BQU8sSUFBSSxTQUFTLElBQUksQ0FBQyxDQUFBLEFBQUMsQ0FBQztHQUVuQyxDQUFDLEdBQUcsS0FBSyxHQUFHLElBQUksQ0FBQztDQUNuQixDQUFBOztBQUVELEtBQUssQ0FBQyxFQUFFLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQyxDQUFDOzs7QUFHL0IsTUFBTSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO0FBQy9CLE1BQU0sQ0FBQyxPQUFPLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQztBQUM3QixNQUFNLENBQUMsT0FBTyxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7QUFDN0IsTUFBTSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO0FBQ2pDLE1BQU0sQ0FBQyxPQUFPLENBQUMsR0FBRyxHQUFHLEdBQUcsQ0FBQztBQUN6QixNQUFNLENBQUMsT0FBTyxDQUFDLFNBQVMsR0FBRyxTQUFTLENBQUM7QUFDckMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDIiwiZmlsZSI6ImluZGV4LmpzIiwic291cmNlc0NvbnRlbnQiOlsiJ3VzZSBzdHJpY3QnO1xuXG4vLyBVdGlsaXR5IHRoYXQgY29sbGVjdHMgcmVhbCB0aW1lIGZ1bmN0aW9uIGNhbGwgcGVyZm9ybWFuY2UgYW5kIHJlbGlhYmlsaXR5XG4vLyBtZXRyaWNzLiBUaGUgc3RhdHMgYXJlIGNvbXB1dGVkIHVzaW5nIHJvbGxpbmcgd2luZG93cyBvZiBjYWxsIHN1Y2Nlc3Nlcyxcbi8vIGZhaWx1cmVzLCB0aW1lb3V0cywgY2lyY3VpdCBicmVha2VyIHJlamVjdGlvbnMgYW5kIGxhdGVuY2llcy5cblxuY29uc3QgXyA9IHJlcXVpcmUoJ3VuZGVyc2NvcmUnKTtcbmNvbnN0IGV2ZW50cyA9IHJlcXVpcmUoJ2FiYWN1cy1ldmVudHMnKTtcblxuY29uc3Qga2V5cyA9IF8ua2V5cztcbmNvbnN0IGZpbHRlciA9IF8uZmlsdGVyO1xuY29uc3QgbGFzdCA9IF8ubGFzdDtcbmNvbnN0IGV4dGVuZCA9IF8uZXh0ZW5kO1xuY29uc3QgcmVkdWNlID0gXy5yZWR1Y2U7XG5jb25zdCBtYXAgPSBfLm1hcDtcbmNvbnN0IGZpbmQgPSBfLmZpbmQ7XG5cbi8vIFNldHVwIGRlYnVnIGxvZ1xuY29uc3QgZGVidWcgPSByZXF1aXJlKCdhYmFjdXMtZGVidWcnKSgnYWJhY3VzLXBlcmYnKTtcblxuLy8gU2V0IHVwIGFuIGV2ZW50IGVtaXR0ZXIgYWxsb3dpbmcgb3RoZXIgbW9kdWxlcyB0byBsaXN0ZW4gdG8gYWNjdW11bGF0ZWQgY2FsbFxuLy8gc3RhdHMgZXZlbnRzXG5jb25zdCBlbWl0dGVyID0gZXZlbnRzLmVtaXR0ZXIoJ2NhbGwtbWV0cmljcy9lbWl0dGVyJyk7XG5jb25zdCBvbiA9IChlLCBsKSA9PiB7XG4gIGVtaXR0ZXIub24oZSwgbCk7XG59O1xuXG4vLyBTZXQgdXAgYW4gZXZlbnQgZW1pdHRlciB1c2VkIHRvIHJlcG9ydCBmdW5jdGlvbiBjYWxsIG1ldHJpY3NcbmNvbnN0IGNhbGxzID0gZXZlbnRzLmVtaXR0ZXIoJ2FiYWN1cy1wZXJmL2NhbGxzJyk7XG5cbi8vIFJlcG9ydCBmdW5jdGlvbiBjYWxsIG1ldHJpY3NcbmNvbnN0IHJlcG9ydCA9IChuYW1lLCB0aW1lLCBsYXRlbmN5LCBlcnIsIHRpbWVvdXQsIHJlamVjdCwgY2lyY3VpdCkgPT4ge1xuICBjb25zdCB0ID0gdGltZSB8fCBEYXRlLm5vdygpO1xuICBjb25zdCBjYWxsID0gKCkgPT4gKHtcbiAgICBuYW1lOiBuYW1lLFxuICAgIHRpbWU6IHQsXG4gICAgbGF0ZW5jeTogbGF0ZW5jeSA9PT0gdW5kZWZpbmVkID8gRGF0ZS5ub3coKSAtIHQgOiBsYXRlbmN5LFxuICAgIGVycm9yOiBlcnIgfHwgdW5kZWZpbmVkLFxuICAgIHRpbWVvdXQ6IHRpbWVvdXQgfHwgMCxcbiAgICByZWplY3Q6IHJlamVjdCB8fCBmYWxzZSxcbiAgICBjaXJjdWl0OiBjaXJjdWl0IHx8ICdjbG9zZWQnXG4gIH0pO1xuICBjYWxscy5lbWl0KCdtZXNzYWdlJywge1xuICAgIG1ldHJpY3M6IHtcbiAgICAgIGNhbGw6IGNhbGwoKVxuICAgIH1cbiAgfSk7XG59O1xuXG4vLyBDb252ZXJ0IGEgbGlzdCBvZiBidWNrZXRzIHRvIGEgbGlzdCBvZiBidWNrZXRzIGluIGEgcm9sbGluZyB0aW1lIHdpbmRvdy5cbi8vIEZpbHRlciBvdXQgdGhlIGJ1Y2tldHMgdGhhdCBhcmUgb3V0IG9mIHRoZSB0aW1lIHdpbmRvdywgYW5kIGNyZWF0ZSBhIG5ld1xuLy8gYnVja2V0IGlmIG5lY2Vzc2FyeSBmb3IgdGhlIGdpdmVuIHRpbWUuXG5jb25zdCByb2xsID0gKGJ1Y2tldHMsIHRpbWUsIHdpbiwgbWF4LCBwcm90bykgPT4ge1xuICBjb25zdCBpID0gTWF0aC5jZWlsKHRpbWUgLyAod2luIC8gbWF4KSk7XG4gIGNvbnN0IGN1cnJlbnQgPSBmaWx0ZXIoYnVja2V0cywgKGIpID0+IGIuaSA+IGkgLSBtYXgpO1xuICBjb25zdCBhbGwgPSBjdXJyZW50Lmxlbmd0aCAhPT0gMCAmJiBsYXN0KGN1cnJlbnQpLmkgPT09IGkgPyBjdXJyZW50IDpcbiAgICBjdXJyZW50LmNvbmNhdChbZXh0ZW5kKHt9LCBwcm90bygpLCB7XG4gICAgICBpOiBpXG4gICAgfSldKTtcbiAgcmV0dXJuIGxhc3QoYWxsLCBtYXgpO1xufTtcblxuLy8gUmV0dXJuIGEgcm9sbGluZyB3aW5kb3cgb2YgMTAgc2VjcyBvZiBjb3VudHNcbmNvbnN0IHJvbGxDb3VudHMgPSAoYnVja2V0cywgdGltZSkgPT4ge1xuICByZXR1cm4gcm9sbChidWNrZXRzLCB0aW1lLCAxMDAwMCwgMTAsICgpID0+ICh7XG4gICAgaTogMCxcbiAgICBvazogMCxcbiAgICBlcnJvcnM6IDAsXG4gICAgdGltZW91dHM6IDAsXG4gICAgcmVqZWN0czogMFxuICB9KSk7XG59O1xuXG4vLyBSZXR1cm4gYSByb2xsaW5nIHdpbmRvdyBvZiA2MCBzZWNzIG9mIGxhdGVuY2llc1xuY29uc3Qgcm9sbExhdGVuY2llcyA9IChidWNrZXRzLCB0aW1lKSA9PiB7XG4gIHJldHVybiByb2xsKGJ1Y2tldHMsIHRpbWUsIDYwMDAwLCA2LCAoKSA9PiAoe1xuICAgIGk6IDAsXG4gICAgbGF0ZW5jaWVzOiBbXVxuICB9KSk7XG59O1xuXG4vLyBSZXR1cm4gYSByb2xsaW5nIHdpbmRvdyBvZiAyIHNlY3Mgb2YgaGVhbHRoIHJlcG9ydHNcbmNvbnN0IHJvbGxIZWFsdGggPSAoYnVja2V0cywgdGltZSwgY291bnRzKSA9PiB7XG4gIC8vIENvbXB1dGUgaGVhbHRoIGZyb20gdGhlIGdpdmVuIGNvdW50c1xuICBjb25zdCBiID0gcmVkdWNlKGNvdW50cywgKGEsIGMpID0+ICh7XG4gICAgb2s6IGEub2sgKyBjLm9rLFxuICAgIGVycm9yczogYS5lcnJvcnMgKyBjLmVycm9ycyArIGMudGltZW91dHMgKyBjLnJlamVjdHNcbiAgfSksIHtcbiAgICBvazogMCxcbiAgICBlcnJvcnM6IDBcbiAgfSk7XG5cbiAgLy8gUm9sbCB0aGUgd2luZG93IGFuZCBzdG9yZSB0aGUgY29tcHV0ZWQgaGVhbHRoIGluIHRoZSBsYXN0IGJ1Y2tldFxuICBjb25zdCBoZWFsdGggPSByb2xsKGJ1Y2tldHMsIHRpbWUsIDIwMDAsIDQsICgpID0+ICh7XG4gICAgaTogMCxcbiAgICBvazogMCxcbiAgICBlcnJvcnM6IDBcbiAgfSkpO1xuICBleHRlbmQobGFzdChoZWFsdGgpLCBiKTtcbiAgcmV0dXJuIGhlYWx0aDtcbn07XG5cbi8vIFN0b3JlIGFjY3VtdWxhdGVkIGZ1bmN0aW9uIGNhbGwgc3RhdHMgcGVyIGZ1bmN0aW9uIG5hbWVcbi8vIFdhcm5pbmc6IGFjY3VtdWxhdGVkU3RhdHMgaXMgYSBtdXRhYmxlIHZhcmlhYmxlXG5sZXQgYWNjdW11bGF0ZWRTdGF0cyA9IHt9O1xuXG4vLyBSZXR1cm4gdGhlIGFjY3VtdWxhdGVkIHN0YXRzIGZvciBhIGZ1bmN0aW9uXG5jb25zdCBzdGF0cyA9IChuYW1lLCB0aW1lLCByb2xsKSA9PiB7XG4gIGNvbnN0IHQgPSB0aW1lIHx8IERhdGUubm93KCk7XG4gIGNvbnN0IHMgPSBhY2N1bXVsYXRlZFN0YXRzW25hbWVdIHx8IHtcbiAgICBuYW1lOiBuYW1lLFxuICAgIHRpbWU6IHQsXG4gICAgY291bnRzOiBbXSxcbiAgICBsYXRlbmNpZXM6IFtdLFxuICAgIGhlYWx0aDogW10sXG4gICAgY2lyY3VpdDogJ2Nsb3NlZCdcbiAgfTtcbiAgLyogZXNsaW50IG5vLWV4dHJhLXBhcmVuczogMSAqL1xuICByZXR1cm4gcm9sbCAhPT0gZmFsc2UgPyAoKCkgPT4ge1xuICAgIGNvbnN0IGNvdW50cyA9IHJvbGxDb3VudHMocy5jb3VudHMsIHQpO1xuICAgIHJldHVybiB7XG4gICAgICBuYW1lOiBuYW1lLFxuICAgICAgdGltZTogdCxcbiAgICAgIGNvdW50czogY291bnRzLFxuICAgICAgbGF0ZW5jaWVzOiByb2xsTGF0ZW5jaWVzKHMubGF0ZW5jaWVzLCB0KSxcbiAgICAgIGhlYWx0aDogcm9sbEhlYWx0aChzLmhlYWx0aCwgdCwgY291bnRzKSxcbiAgICAgIGNpcmN1aXQ6IHMuY2lyY3VpdFxuICAgIH07XG4gIH0pKCkgOiB7XG4gICAgbmFtZTogbmFtZSxcbiAgICB0aW1lOiB0LFxuICAgIGNvdW50czogcy5jb3VudHMsXG4gICAgbGF0ZW5jaWVzOiBzLmxhdGVuY2llcyxcbiAgICBoZWFsdGg6IHMuaGVhbHRoLFxuICAgIGNpcmN1aXQ6IHMuY2lyY3VpdFxuICB9O1xufTtcblxuLy8gUmVzZXQgdGhlIGFjY3VtdWxhdGVkIHJlbGlhYmlsaXR5IHN0YXRzIGZvciBhIGZ1bmN0aW9uIChidXQga2VlcCB0aGVcbi8vIGFjY3VtdWxhdGVkIGxhdGVuY2llcylcbmNvbnN0IHJlc2V0ID0gKG5hbWUsIHRpbWUpID0+IHtcbiAgY29uc3QgdCA9IHRpbWUgfHwgRGF0ZS5ub3coKTtcblxuICAvLyBXYXJuaW5nOiBtdXRhdGluZyB2YXJpYWJsZSBhY2N1bXVsYXRlZFN0YXRzXG4gIC8qIGVzbGludCBuby1leHRyYS1wYXJlbnM6IDEgKi9cbiAgY29uc3QgYXN0YXRzID0gKChzKSA9PiB7XG4gICAgcmV0dXJuIHMgPyB7XG4gICAgICBuYW1lOiBuYW1lLFxuICAgICAgdGltZTogdCxcbiAgICAgIGNvdW50czogW10sXG4gICAgICBsYXRlbmNpZXM6IHMubGF0ZW5jaWVzLFxuICAgICAgaGVhbHRoOiBbXSxcbiAgICAgIGNpcmN1aXQ6ICdjbG9zZWQnXG4gICAgfSA6XG4gICAgICB7XG4gICAgICAgIG5hbWU6IG5hbWUsXG4gICAgICAgIHRpbWU6IHQsXG4gICAgICAgIGNvdW50czogW10sXG4gICAgICAgIGxhdGVuY2llczogW10sXG4gICAgICAgIGhlYWx0aDogW10sXG4gICAgICAgIGNpcmN1aXQ6ICdjbG9zZWQnXG4gICAgICB9O1xuICB9KShhY2N1bXVsYXRlZFN0YXRzW25hbWVdKTtcbiAgYWNjdW11bGF0ZWRTdGF0c1tuYW1lXSA9IGFzdGF0cztcblxuICAvLyBQcm9wYWdhdGUgbmV3IHN0YXRzIHRvIGFsbCB0aGUgbGlzdGVuZXJzXG4gIGRlYnVnKCdFbWl0dGluZyBzdGF0cyBmb3IgZnVuY3Rpb24gJXMnLCBuYW1lKTtcbiAgZW1pdHRlci5lbWl0KCdtZXNzYWdlJywge1xuICAgIG1ldHJpY3M6IHtcbiAgICAgIHN0YXRzOiBhc3RhdHNcbiAgICB9XG4gIH0pO1xuICByZXR1cm4gYXN0YXRzO1xufTtcblxuLy8gUmV0dXJuIGFsbCB0aGUgYWNjdW11bGF0ZWQgc3RhdHNcbmNvbnN0IGFsbCA9ICh0aW1lLCByb2xsKSA9PiB7XG4gIGNvbnN0IHQgPSB0aW1lIHx8IERhdGUubm93KCk7XG4gIHJldHVybiBtYXAoa2V5cyhhY2N1bXVsYXRlZFN0YXRzKSwgKGspID0+IHN0YXRzKGssIHQsIHJvbGwpKTtcbn07XG5cbi8vIFByb2Nlc3MgZnVuY3Rpb24gY2FsbCBtZXRyaWNzIGFuZCB1cGRhdGUgYWNjdW11bGF0ZWQgY2FsbCBzdGF0c1xuY29uc3QgYWNjdW11bGF0ZVN0YXRzID0gKG5hbWUsIHRpbWUsIGxhdGVuY3ksIGVyciwgdGltZW91dCxcbiAgcmVqZWN0LCBjaXJjdWl0KSA9PiB7XG4gIGRlYnVnKCdBY2N1bXVsYXRpbmcgc3RhdHMgZm9yIGZ1bmN0aW9uICVzJywgbmFtZSk7XG4gIGRlYnVnKCdsYXRlbmN5ICVkLCBlcnIgJXMsIHRpbWVvdXQgJWQsIHJlamVjdCAlcywgY2lyY3VpdCAlcycsXG4gICAgICBsYXRlbmN5LCBlcnIsIHRpbWVvdXQsIHJlamVjdCwgY2lyY3VpdCk7XG5cbiAgICAvLyBSZXRyaWV2ZSB0aGUgY3VycmVudCBjYWxsIHN0YXRzIGZvciB0aGUgZ2l2ZW4gZnVuY3Rpb25cbiAgY29uc3QgcyA9IHN0YXRzKG5hbWUsIHRpbWUsIGZhbHNlKTtcblxuICAgIC8vIENvbXB1dGUgdXAgdG8gZGF0ZSBjb3VudHMgd2luZG93IGFuZCBpbmNyZW1lbnQgY291bnRzIGluIHRoZSBsYXN0IGJ1Y2tldFxuICBjb25zdCBjb3VudHMgPSByb2xsQ291bnRzKHMgPyBzLmNvdW50cyA6IFtdLCB0aW1lKTtcbiAgY29uc3QgdXBkYXRlQ291bnQgPSAoYykgPT4ge1xuICAgIGMub2sgPSBjLm9rICsgKCFlcnIgJiYgIXRpbWVvdXQgJiYgIXJlamVjdCA/IDEgOiAwKTtcbiAgICBjLmVycm9ycyA9IGMuZXJyb3JzICsgKGVyciA/IDEgOiAwKTtcbiAgICBjLnRpbWVvdXRzID0gYy50aW1lb3V0cyArICh0aW1lb3V0ID8gMSA6IDApO1xuICAgIGMucmVqZWN0cyA9IGMucmVqZWN0cyArIChyZWplY3QgPyAxIDogMCk7XG4gICAgZGVidWcoJyVkIG9rLCAlZCBlcnJvcnMsICVkIHRpbWVvdXRzLCAlZCByZWplY3RzLCAlZCBjb3VudCBidWNrZXRzJyxcbiAgICAgICAgYy5vaywgYy5lcnJvcnMsIGMudGltZW91dHMsIGMucmVqZWN0cywgY291bnRzLmxlbmd0aCk7XG4gIH07XG4gIHVwZGF0ZUNvdW50KGxhc3QoY291bnRzKSk7XG5cbiAgICAvLyBDb21wdXRlIHVwIHRvIGRhdGUgbGF0ZW5jaWVzIHdpbmRvdyBhbmQgYWRkIGxhdGVuY3kgdG8gdGhlIGxhc3QgYnVja2V0LFxuICAgIC8vIHVwIHRvIHRoZSBtYXggYnVja2V0IHNpemVcbiAgY29uc3QgbGF0ZW5jaWVzID0gcm9sbExhdGVuY2llcyhzID8gcy5sYXRlbmNpZXMgOiBbXSwgdGltZSk7XG4gIGlmKCFlcnIgJiYgIXRpbWVvdXQgJiYgIXJlamVjdCkge1xuICAgIGNvbnN0IHVwZGF0ZUxhdGVuY3kgPSAobCkgPT4ge1xuICAgICAgbC5sYXRlbmNpZXMgPSBsLmxhdGVuY2llcy5sZW5ndGggPCAxMDAgP1xuICAgICAgICAgIGwubGF0ZW5jaWVzLmNvbmNhdChbbGF0ZW5jeV0pIDogbC5sYXRlbmNpZXM7XG4gICAgICBkZWJ1ZygnJWQgbGF0ZW5jaWVzLCAlZCBsYXRlbmNpZXMgYnVja2V0cycsXG4gICAgICAgICAgbC5sYXRlbmNpZXMubGVuZ3RoLCBsYXRlbmNpZXMgLmxlbmd0aCk7XG4gICAgfTtcbiAgICB1cGRhdGVMYXRlbmN5KGxhc3QobGF0ZW5jaWVzKSk7XG4gIH1cblxuICAgIC8vIENvbXB1dGUgdXAgdG8gZGF0ZSBoZWFsdGggcmVwb3J0IHdpbmRvd1xuICBjb25zdCBoZWFsdGggPSByb2xsSGVhbHRoKHMgPyBzLmhlYWx0aCA6IFtdLCB0aW1lLCBjb3VudHMpO1xuICBjb25zdCBoID0gbGFzdChoZWFsdGgpO1xuICBkZWJ1ZygnJWQgb2ssICVkIGVycm9ycywgJWQgaGVhbHRoIGJ1Y2tldHMnLCBoLm9rLCBoLmVycm9ycywgaGVhbHRoLmxlbmd0aCk7XG5cbiAgICAvLyBTdG9yZSBhbmQgcmV0dXJuIHRoZSBuZXcgYWNjdW11bGF0ZWQgZnVuY3Rpb24gY2FsbCBzdGF0c1xuICAgIC8vIFdhcm5pbmc6IG11dGF0aW5nIHZhcmlhYmxlIGFjY3VtdWxhdGVkU3RhdHNcbiAgY29uc3QgYXN0YXRzID0ge1xuICAgIG5hbWU6IG5hbWUsXG4gICAgdGltZTogdGltZSxcbiAgICBjb3VudHM6IGNvdW50cyxcbiAgICBsYXRlbmNpZXM6IGxhdGVuY2llcyxcbiAgICBoZWFsdGg6IGhlYWx0aCxcbiAgICBjaXJjdWl0OiBjaXJjdWl0XG4gIH07XG4gIGFjY3VtdWxhdGVkU3RhdHNbbmFtZV0gPSBhc3RhdHM7XG5cbiAgICAvLyBQcm9wYWdhdGUgbmV3IHN0YXRzIHRvIGFsbCB0aGUgbGlzdGVuZXJzXG4gIGRlYnVnKCdFbWl0dGluZyBzdGF0cyBmb3IgZnVuY3Rpb24gJXMnLCBuYW1lKTtcbiAgZW1pdHRlci5lbWl0KCdtZXNzYWdlJywge1xuICAgIG1ldHJpY3M6IHtcbiAgICAgIHN0YXRzOiBhc3RhdHNcbiAgICB9XG4gIH0pO1xuICByZXR1cm4gYXN0YXRzO1xufTtcblxuLy8gUHJvY2VzcyBmdW5jdGlvbiBjYWxsIG1ldHJpY3MgbWVzc2FnZXMgYW5kIGZ1bmN0aW9uIGNhbGwgc3RhdHMgbWVzc2FnZXNcbmNvbnN0IG9uTWVzc2FnZSA9IChtc2cpID0+IHtcbiAgaWYobXNnLm1ldHJpY3MpIHtcbiAgICBkZWJ1ZygnUmVjZWl2ZWQgbWVzc2FnZSAlbycsIGtleXMobXNnKS5jb25jYXQoa2V5cyhtc2cubWV0cmljcykpKTtcbiAgICBpZihtc2cubWV0cmljcy5jYWxsKSB7XG4gICAgICAvLyBQcm9jZXNzIGNhbGwgbWV0cmljcyBhbmQgZW1pdCB1cGRhdGVkIGFjY3VtdWxhdGVkIGNhbGwgc3RhdHNcbiAgICAgIGNvbnN0IGMgPSBtc2cubWV0cmljcy5jYWxsO1xuICAgICAgYWNjdW11bGF0ZVN0YXRzKFxuICAgICAgICBjLm5hbWUsIGMudGltZSwgYy5sYXRlbmN5LCBjLmVycm9yLCBjLnRpbWVvdXQsIGMucmVqZWN0LCBjLmNpcmN1aXQpO1xuICAgIH1cbiAgICBpZihtc2cubWV0cmljcy5zdGF0cykge1xuICAgICAgLy8gU3RvcmUgbGF0ZXN0IGFjY3VtdWxhdGVkIHN0YXRzXG4gICAgICBkZWJ1ZygnU3RvcmluZyBzdGF0cyBmb3IgZnVuY3Rpb24gJXMnLCBtc2cubWV0cmljcy5zdGF0cy5uYW1lKTtcbiAgICAgIGFjY3VtdWxhdGVkU3RhdHNbbXNnLm1ldHJpY3Muc3RhdHMubmFtZV0gPSBtc2cubWV0cmljcy5zdGF0cztcbiAgICB9XG4gIH1cbn07XG5cbi8vIERldGVybWluZSB0aGUgaGVhbHRoIG9mIHRoZSBhcHAgYmFzZWQgb24gdGhlIGFjY3VtdWxhdGVkIG1ldHJpY3NcbmNvbnN0IGhlYWx0aHkgPSAodGhyZXNob2xkKSA9PiB7XG5cbiAgLy8gR28gdGhyb3VnaCBlYWNoIGZ1bmN0aW9uIGNhbGwgbWV0cmljc1xuICByZXR1cm4gZmluZChhbGwoRGF0ZS5ub3coKSwgZmFsc2UpLCAoc3RhdCkgPT4ge1xuXG4gICAgLy8gR28gdGhyb3VnaCBpdHMgaGVhbHRoIHN0YXR1cyBhbmQgY2FsY3VsYXRlIHRvdGFsIHJlcXVlc3RzICYgZXJyb3JzXG4gICAgY29uc3QgdG90YWwgPSByZWR1Y2Uoc3RhdC5oZWFsdGgsIChhLCBjKSA9PiAoe1xuICAgICAgcmVxdWVzdHM6IGEucmVxdWVzdHMgKyBhLmVycm9ycyArIGMub2sgKyBjLmVycm9ycyxcbiAgICAgIGVycm9yczogYS5lcnJvcnMgKyBjLmVycm9yc1xuICAgIH0pLCB7XG4gICAgICByZXF1ZXN0czogMCxcbiAgICAgIGVycm9yczogMFxuICAgIH0pO1xuXG4gICAgY29uc3QgcGVyY2VudCA9IDEwMCAqICh0b3RhbC5lcnJvcnMgLyB0b3RhbC5yZXF1ZXN0cyB8fCAxKTtcbiAgICBkZWJ1ZygnJXMgaGFzICVkJSBmYWlsdXJlIHJhdGUnLCBzdGF0Lm5hbWUsIHBlcmNlbnQpO1xuXG4gICAgLy8gSWYgb25lIGZ1bmN0aW9uIGNhbGwgaXMgbm90IGhlYWx0aHksIGNvbmNsdWRlIHRoYXQgdGhlIGFwcCBpc1xuICAgIC8vIG5vdCBoZWFsdGh5LlxuICAgIHJldHVybiBwZXJjZW50ID4gKHRocmVzaG9sZCB8fCA1KTtcblxuICB9KSA/IGZhbHNlIDogdHJ1ZTtcbn1cblxuY2FsbHMub24oJ21lc3NhZ2UnLCBvbk1lc3NhZ2UpO1xuXG4vLyBFeHBvcnQgb3VyIHB1YmxpYyBmdW5jdGlvbnNcbm1vZHVsZS5leHBvcnRzLnJlcG9ydCA9IHJlcG9ydDtcbm1vZHVsZS5leHBvcnRzLnN0YXRzID0gc3RhdHM7XG5tb2R1bGUuZXhwb3J0cy5yZXNldCA9IHJlc2V0O1xubW9kdWxlLmV4cG9ydHMuaGVhbHRoeSA9IGhlYWx0aHk7XG5tb2R1bGUuZXhwb3J0cy5hbGwgPSBhbGw7XG5tb2R1bGUuZXhwb3J0cy5vbk1lc3NhZ2UgPSBvbk1lc3NhZ2U7XG5tb2R1bGUuZXhwb3J0cy5vbiA9IG9uO1xuIl19
\No newline at end of file