UNPKG

grandma

Version:
288 lines (224 loc) 7.57 kB
var _ = require('lodash'); var byline = require('byline'); var through = require('through2'); var stats = require('./stats.js'); var estimate = { duration: require('./estimate-duration.js'), rate: require('./estimate-rate.js'), concurrent: require('./estimate-concurrent.js') }; var FULL = 'fullTest'; function insertInArray(hash, name, value) { if (hash[name]) { hash[name].push(value); } else { hash[name] = [value]; } } function mergeToNumericArray(header, data, name) { if (header[name] !== undefined && !_.isArray(header[name])) { header[name] = [header[name]]; } if (_.isNumber(data[name])) { header[name] = header[name] ? header[name].concat([data[name]]) : [data[name]]; } } function mergeHeaderJson(header, data) { if (!header.epoch) { header.epoch = data.epoch; } if (!header.duration) { header.duration = data.duration; } if (header.epoch !== data.epoch || header.duration !== data.duration) { // figure out the earliest epoch and longest possible runtime var absStart = Math.min(header.epoch, data.epoch); var absEnd = Math.max((header.epoch + header.duration), (data.epoch + data.duration)); header.epoch = absStart; header.duration = absEnd - absStart; } mergeToNumericArray(header, data, 'rate'); mergeToNumericArray(header, data, 'concurrent'); header.targetCount = header.targetCount || 0; header.targetCount += data.targetCount; header.name = header.name || data.name || null; return header; } function readJsonLines(stream, onLine, done) { var readLines = false; stream .on('error', done) .pipe(byline.createStream()) .pipe(through(function(line, enc, cb) { var jsonLine; try { jsonLine = JSON.parse(line); } catch(e) { // skip this line return cb(); } readLines = true; onLine(jsonLine); cb(); })) // remember to flish the stream .on('data', _.noop) .on('end', function() { if (!readLines) { return done(new Error('no data provided')); } done(); }); } function jsonStats(data) { var reports = data.reports; var metrics = data.metrics; var header = data.header; var categories = data.categories; function buildSummaryFromReports(subset) { var groupedTimes = _.reduce(subset, function(seed, rep) { _.forEach(rep, function(val, key) { insertInArray(seed, key, val); }); return seed; }, {}); var groupedSortedTimes = _.mapValues(groupedTimes, function(rep) { return rep.sort(function(a, b) { return a - b; }); }); return _.mapValues(groupedSortedTimes, function(sortedTimes) { var iqr = stats.iqr(sortedTimes); return { mean: stats.mean(sortedTimes), median: iqr.q2, '25': iqr.q1, '50': stats.percentile(sortedTimes, 50), '75': iqr.q3, '95': stats.percentile(sortedTimes, 95), '99': stats.percentile(sortedTimes, 99), min: iqr.min, max: iqr.max }; }); } var statsJson = { info: { count: reports.length, targetCount: header.targetCount, duration: header.duration, rate: header.rate || null, concurrent: header.concurrent || null, name: header.name || null }, breakdown: data.breakdown }; statsJson.estimates = data.estimates; statsJson.latencies = buildSummaryFromReports(reports); statsJson.metrics = buildSummaryFromReports(metrics); statsJson.categories = _.reduce(categories.latencies, function(memo, arr, name) { var group = { info: { count: arr.length } }; group.latencies = buildSummaryFromReports(arr); memo[name] = group; return memo; }, {}); statsJson.categories = _.reduce(categories.metrics, function(memo, arr, name) { var group = memo[name] || { info: { count: arr.length } }; group.metrics = buildSummaryFromReports(arr); memo[name] = group; return memo; }, statsJson.categories); return statsJson; } function readJsonStats(input, done) { var latencies = []; var metrics = []; var header = {}; var breakdown = { successes: 0 }; var categories = { latencies: {}, metrics: {} }; var estimateRate = estimate.rate(); var estimateConcurrent = estimate.concurrent(); var estimateDuration = estimate.duration(); var jsonHandlers = { header: function(data) { header = mergeHeaderJson(header, data); }, report: function(data) { var latencySummary = _.reduce(data.report, function(seed, rep, name) { seed[name] = rep.duration; return seed; }, {}); latencies.push(latencySummary); if (data.metrics && Object.keys(data.metrics).length) { metrics.push(data.metrics); } if (data.categories && data.categories.length) { data.categories.forEach(function(name) { insertInArray(categories.latencies, name, latencySummary); if (data.metrics && Object.keys(data.metrics).length) { insertInArray(categories.metrics, name, data.metrics); } }); } if (data.report[FULL] && data.report[FULL].status === 'failure') { breakdown.failures = breakdown.failures || {}; var errCode = data.report[FULL].errorCode || 0; breakdown.failures[errCode] = breakdown.failures[errCode] || 0; breakdown.failures[errCode] += 1; } else { breakdown.successes += 1; } } }; function onJson(data) { estimateRate.include(data); estimateConcurrent.include(data); estimateDuration.include(data); jsonHandlers[data.type](data); } function averageValue(value) { if (_.isArray(value) && value.length) { return stats.mean(value); } return null; } readJsonLines(input, onJson, function(err) { if (err) { return done(err); } header.rate = averageValue(header.rate); header.concurrent = averageValue(header.concurrent); var combinedData = { header: header, reports: latencies, metrics: metrics, categories: categories, breakdown: breakdown, estimates: { rate: estimateRate.result, concurrent: estimateConcurrent.result, duration: estimateDuration.result } }; var jsonSummary = jsonStats(combinedData); done(undefined, jsonSummary); }); } module.exports = readJsonStats; module.exports.readLines = readJsonLines; module.exports._mergeHeaderJson = mergeHeaderJson;