UNPKG

5.5 kBPlain TextView Raw
1#!/usr/bin/env node
2// -*- mode: js -*-
3
4var fs = require('fs');
5var path = require('path');
6var spawn = require('child_process').spawn;
7var readline = require('readline');
8var sprintf = require('util').format;
9
10var nopt = require('nopt');
11
12
13///--- Globals
14
15var BUCKETS = {};
16var REQUEST_IDS = {};
17
18var OPTS = {
19 'average': Boolean,
20 'help': Boolean,
21 'end': Date,
22 'max-latency': Number,
23 'max-requests': Number,
24 'output': String,
25 'percentile': [Number, Array],
26 'period': Number,
27 'requests': Boolean,
28 'start': Date
29};
30
31var SHORT_OPTS = {
32 'a': ['--average'],
33 'h': ['--help'],
34 'i': ['--period'],
35 'e': ['--end'],
36 'l': ['--max-latency'],
37 'n': ['--max-requests'],
38 'o': ['--output'],
39 'p': ['--percentile'],
40 'r': ['--requests'],
41 's': ['--start']
42};
43
44
45///--- Functions
46
47function percentile(p, vals) {
48 p = parseInt(p, 10);
49 return vals[(Math.round(((p / 100) * vals.length) + 1 / 2) - 1)].latency;
50}
51
52
53function report(buckets, output) {
54 Object.keys(buckets).sort(function (a, b) {
55 return parseInt(a, 10) - parseInt(b, 10);
56 }).forEach(function (k) {
57 var avg = 0;
58 var perc = [];
59 var req = buckets[k].length;
60 var sum = 0;
61 var t = Math.round(buckets[k]._time);
62
63 buckets[k] = buckets[k].sort(function (a, b) {
64 return a.latency - b.latency;
65 });
66
67 buckets[k].forEach(function (v) {
68 sum += v.latency;
69 });
70
71 if (sum > 0 && req > 0) {
72 if (output.average)
73 output.average.push([t, Math.round(sum / req)]);
74 if (output.requests)
75 output.requests.push([t, buckets[k].length]);
76 Object.keys(output.percentile).forEach(function (p) {
77 var _p = percentile(p, buckets[k]);
78 output.percentile[p].push([t, _p]);
79 });
80 }
81 });
82
83 return output;
84}
85
86
87function usage(code, message) {
88 var str = '';
89 Object.keys(SHORT_OPTS).forEach(function (k) {
90 if (!Array.isArray(SHORT_OPTS[k]))
91 return;
92
93 var opt = SHORT_OPTS[k][0].replace('--', '');
94 var type = OPTS[opt].name || 'string';
95 if (type && type === 'boolean')
96 type = '';
97 type = type.toLowerCase();
98
99 str += ' [--' + opt + ' ' + type + ']';
100 });
101 str += ' [file ...]';
102
103 if (message)
104 console.error(message);
105
106 console.error('usage: ' + path.basename(process.argv[1]) + str);
107 process.exit(code);
108}
109
110
111///--- Mainline
112
113var parsed;
114
115try {
116 parsed = nopt(OPTS, SHORT_OPTS, process.argv, 2);
117} catch (e) {
118 usage(1, e.toString());
119}
120
121if (parsed.help)
122 usage(0);
123if (!parsed.average && !parsed.percentile)
124 usage(1, '--average or --percentile required');
125if (parsed.argv.remain.length < 1)
126 usage(1, 'log file required');
127
128var config = {
129 average: parsed.average || false,
130 maxLatency: parsed['max-latency'] || 1000,
131 maxRequests: parsed['max-requests'] || 10000,
132 percentile: parsed.percentile || [],
133 period: parsed.period || 60,
134 requests: parsed.requests || false,
135 start: parsed.start ? (parsed.start.getTime() / 1000) : 0,
136 end: parsed.end ? (parsed.end.getTime() / 1000) : Number.MAX_VALUE
137};
138
139var buckets = {};
140
141var done = 0;
142parsed.argv.remain.forEach(function (f) {
143 var stream = readline.createInterface({
144 input: fs.createReadStream(f),
145 output: null
146 })
147 stream.on('line', function (l) {
148 var record;
149 var t = -1;
150
151 try {
152 record = JSON.parse(l);
153 } catch (e) {
154 }
155
156 if (!record)
157 return;
158
159 var t = -1;
160 if (record.time)
161 t = (new Date(record.time).getTime() / 1000);
162
163 if (record._audit !== true ||
164 REQUEST_IDS[record.req_id] ||
165 t < config.start ||
166 t > config.end) {
167
168 console.error('Skipping %s', l);
169 }
170
171 REQUEST_IDS[record.req_id] = true;
172 record.time = t;
173
174 var b = Math.round(record.time / config.period) + '';
175 if (!buckets[b])
176 buckets[b] = [];
177
178 buckets[b].push(record);
179 buckets[b]._time = record.time // good enough
180 });
181
182 stream.on('end', function () {
183 if (++done === parsed.argv.remain.length) {
184 console.error('Generating report...');
185
186 var output = {
187 average: config.average ? [] : false,
188 requests: config.requests ? [] : false,
189 percentile: {}
190 };
191 config.percentile.forEach(function (p) {
192 output.percentile[p] = [];
193 });
194
195 output = report(buckets, output);
196 var finalOutput = [];
197 if (output.average) {
198 finalOutput.push({
199 name: 'avg',
200 values: output.average
201 });
202 }
203 if (output.requests) {
204 finalOutput.push({
205 name: 'n',
206 values: output.requests
207 });
208 }
209 Object.keys(output.percentile).forEach(function (k) {
210 finalOutput.push({
211 name: 'p' + k,
212 values: output.percentile[k]
213 });
214 });
215
216 console.log(JSON.stringify(finalOutput));
217 }
218 });
219});