1 | ;
|
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 |
|
7 | var _ = require('underscore');
|
8 | var events = require('abacus-events');
|
9 |
|
10 | var keys = _.keys;
|
11 | var filter = _.filter;
|
12 | var last = _.last;
|
13 | var extend = _.extend;
|
14 | var reduce = _.reduce;
|
15 | var map = _.map;
|
16 | var find = _.find;
|
17 |
|
18 | // Setup debug log
|
19 | var debug = require('abacus-debug')('abacus-perf');
|
20 |
|
21 | // Set up an event emitter allowing other modules to listen to accumulated call
|
22 | // stats events
|
23 | var emitter = events.emitter('call-metrics/emitter');
|
24 | var on = function on(e, l) {
|
25 | emitter.on(e, l);
|
26 | };
|
27 |
|
28 | // Set up an event emitter used to report function call metrics
|
29 | var calls = events.emitter('abacus-perf/calls');
|
30 |
|
31 | // Report function call metrics
|
32 | var 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.
|
55 | var 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
|
67 | var 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
|
80 | var 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
|
90 | var 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
|
116 | var accumulatedStats = {};
|
117 |
|
118 | // Return the accumulated stats for a function
|
119 | var 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)
|
152 | var 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
|
187 | var 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
|
195 | var 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
|
252 | var 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
|
269 | var 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 |
|
294 | calls.on('message', onMessage);
|
295 |
|
296 | // Export our public functions
|
297 | module.exports.report = report;
|
298 | module.exports.stats = stats;
|
299 | module.exports.reset = reset;
|
300 | module.exports.healthy = healthy;
|
301 | module.exports.all = all;
|
302 | module.exports.onMessage = onMessage;
|
303 | module.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 |