220 lines
5.4 KiB
JavaScript
Executable File
220 lines
5.4 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
// -*- mode: js -*-
|
|
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
var spawn = require('child_process').spawn;
|
|
var readline = require('readline');
|
|
var sprintf = require('util').format;
|
|
|
|
var nopt = require('nopt');
|
|
|
|
|
|
///--- Globals
|
|
|
|
var BUCKETS = {};
|
|
var REQUEST_IDS = {};
|
|
|
|
var OPTS = {
|
|
'average': Boolean,
|
|
'help': Boolean,
|
|
'end': Date,
|
|
'max-latency': Number,
|
|
'max-requests': Number,
|
|
'output': String,
|
|
'percentile': [Number, Array],
|
|
'period': Number,
|
|
'requests': Boolean,
|
|
'start': Date
|
|
};
|
|
|
|
var SHORT_OPTS = {
|
|
'a': ['--average'],
|
|
'h': ['--help'],
|
|
'i': ['--period'],
|
|
'e': ['--end'],
|
|
'l': ['--max-latency'],
|
|
'n': ['--max-requests'],
|
|
'o': ['--output'],
|
|
'p': ['--percentile'],
|
|
'r': ['--requests'],
|
|
's': ['--start']
|
|
};
|
|
|
|
|
|
///--- Functions
|
|
|
|
function percentile(p, vals) {
|
|
p = parseInt(p, 10);
|
|
return vals[(Math.round(((p / 100) * vals.length) + 1 / 2) - 1)].latency;
|
|
}
|
|
|
|
|
|
function report(buckets, output) {
|
|
Object.keys(buckets).sort(function (a, b) {
|
|
return parseInt(a, 10) - parseInt(b, 10);
|
|
}).forEach(function (k) {
|
|
var avg = 0;
|
|
var perc = [];
|
|
var req = buckets[k].length;
|
|
var sum = 0;
|
|
var t = Math.round(buckets[k]._time);
|
|
|
|
buckets[k] = buckets[k].sort(function (a, b) {
|
|
return a.latency - b.latency;
|
|
});
|
|
|
|
buckets[k].forEach(function (v) {
|
|
sum += v.latency;
|
|
});
|
|
|
|
if (sum > 0 && req > 0) {
|
|
if (output.average)
|
|
output.average.push([t, Math.round(sum / req)]);
|
|
if (output.requests)
|
|
output.requests.push([t, buckets[k].length]);
|
|
Object.keys(output.percentile).forEach(function (p) {
|
|
var _p = percentile(p, buckets[k]);
|
|
output.percentile[p].push([t, _p]);
|
|
});
|
|
}
|
|
});
|
|
|
|
return output;
|
|
}
|
|
|
|
|
|
function usage(code, message) {
|
|
var str = '';
|
|
Object.keys(SHORT_OPTS).forEach(function (k) {
|
|
if (!Array.isArray(SHORT_OPTS[k]))
|
|
return;
|
|
|
|
var opt = SHORT_OPTS[k][0].replace('--', '');
|
|
var type = OPTS[opt].name || 'string';
|
|
if (type && type === 'boolean')
|
|
type = '';
|
|
type = type.toLowerCase();
|
|
|
|
str += ' [--' + opt + ' ' + type + ']';
|
|
});
|
|
str += ' [file ...]';
|
|
|
|
if (message)
|
|
console.error(message);
|
|
|
|
console.error('usage: ' + path.basename(process.argv[1]) + str);
|
|
process.exit(code);
|
|
}
|
|
|
|
|
|
///--- Mainline
|
|
|
|
var parsed;
|
|
|
|
try {
|
|
parsed = nopt(OPTS, SHORT_OPTS, process.argv, 2);
|
|
} catch (e) {
|
|
usage(1, e.toString());
|
|
}
|
|
|
|
if (parsed.help)
|
|
usage(0);
|
|
if (!parsed.average && !parsed.percentile)
|
|
usage(1, '--average or --percentile required');
|
|
if (parsed.argv.remain.length < 1)
|
|
usage(1, 'log file required');
|
|
|
|
var config = {
|
|
average: parsed.average || false,
|
|
maxLatency: parsed['max-latency'] || 1000,
|
|
maxRequests: parsed['max-requests'] || 10000,
|
|
percentile: parsed.percentile || [],
|
|
period: parsed.period || 60,
|
|
requests: parsed.requests || false,
|
|
start: parsed.start ? (parsed.start.getTime() / 1000) : 0,
|
|
end: parsed.end ? (parsed.end.getTime() / 1000) : Number.MAX_VALUE
|
|
};
|
|
|
|
var buckets = {};
|
|
|
|
var done = 0;
|
|
parsed.argv.remain.forEach(function (f) {
|
|
var stream = readline.createInterface({
|
|
input: fs.createReadStream(f),
|
|
output: null
|
|
})
|
|
stream.on('line', function (l) {
|
|
var record;
|
|
var t = -1;
|
|
|
|
try {
|
|
record = JSON.parse(l);
|
|
} catch (e) {
|
|
}
|
|
|
|
if (!record)
|
|
return;
|
|
|
|
var t = -1;
|
|
if (record.time)
|
|
t = (new Date(record.time).getTime() / 1000);
|
|
|
|
if (record._audit !== true ||
|
|
REQUEST_IDS[record.req_id] ||
|
|
t < config.start ||
|
|
t > config.end) {
|
|
|
|
console.error('Skipping %s', l);
|
|
}
|
|
|
|
REQUEST_IDS[record.req_id] = true;
|
|
record.time = t;
|
|
|
|
var b = Math.round(record.time / config.period) + '';
|
|
if (!buckets[b])
|
|
buckets[b] = [];
|
|
|
|
buckets[b].push(record);
|
|
buckets[b]._time = record.time // good enough
|
|
});
|
|
|
|
stream.on('end', function () {
|
|
if (++done === parsed.argv.remain.length) {
|
|
console.error('Generating report...');
|
|
|
|
var output = {
|
|
average: config.average ? [] : false,
|
|
requests: config.requests ? [] : false,
|
|
percentile: {}
|
|
};
|
|
config.percentile.forEach(function (p) {
|
|
output.percentile[p] = [];
|
|
});
|
|
|
|
output = report(buckets, output);
|
|
var finalOutput = [];
|
|
if (output.average) {
|
|
finalOutput.push({
|
|
name: 'avg',
|
|
values: output.average
|
|
});
|
|
}
|
|
if (output.requests) {
|
|
finalOutput.push({
|
|
name: 'n',
|
|
values: output.requests
|
|
});
|
|
}
|
|
Object.keys(output.percentile).forEach(function (k) {
|
|
finalOutput.push({
|
|
name: 'p' + k,
|
|
values: output.percentile[k]
|
|
});
|
|
});
|
|
|
|
console.log(JSON.stringify(finalOutput));
|
|
}
|
|
});
|
|
});
|