Skip to content

Instantly share code, notes, and snippets.

@matteofigus
Created October 11, 2015 15:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matteofigus/61cb3ac662467b9f6076 to your computer and use it in GitHub Desktop.
Save matteofigus/61cb3ac662467b9f6076 to your computer and use it in GitHub Desktop.
grunt-api-benchmark output
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Api Benchmark :: report</title>
<!--[if lt IE 9]><script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/excanvas.min.js"></script><![endif]-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/jquery.jqplot.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/plugins/jqplot.canvasTextRenderer.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/plugins/jqplot.canvasAxisLabelRenderer.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/plugins/jqplot.highlighter.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.0/highlight.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/jquery.jqplot.min.css" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.0/styles/dark.min.css" type="text/css" />
<style type="text/css">
.jqplot-target {
color: #F1F1F1;
}
table.jqplot-table-legend, table.jqplot-cursor-legend {
background-color: rgba(101, 0, 253, 0.89)
}
body {
background: #3D3D3D;
color: #F1F1F1;
}
a {
color: #6699FF;
}
#report-details, #charts {
width: 100%;
float: left;
font-family: "Trebuchet MS",Arial,Helvetica,sans-serif;
}
#report-details {
margin-bottom: 20px;
text-align: center;
}
.buttons {
width: 100%;
border-bottom: 1px solid rgb(75, 72, 72);
padding-bottom: 10px;
margin-bottom: 10px;
text-align: right;
}
.button {
margin-right: 10px;
padding: 6px;
text-decoration: none;
}
.button.selected {
background-color: rgb(56, 56, 56);
}
.chart {
width: 60%;
height: 450px;
float: left;
}
.distchart {
width: 96%;
height: 450px;
float: left;
}
.panel {
height: 450px;
overflow-y: auto;
}
.route {
width: 90%;
margin: 3%;
padding: 2%;
float: left;
background-color: #000000;
border-radius: 15px;
}
.stats-details {
font-family: "Trebuchet MS",Arial,Helvetica,sans-serif;
font-size: 15px;
float: left;
width: 36%;
padding: 2%;
}
.green {
color: green;
}
.red {
color: red;
}
.hide {
display:none;
}
pre code {
overflow-x: auto;
}
</style>
</head>
<body>
<script><!--
var data={"benchmark":{"My api":{"simpleRoute":{"name":"My api/simpleRoute","href":"http://localhost:3006/getJson","stats":{"sample":[0.01357674,0.004451465,0.002704374,0.002977995,0.002877574,0.002325863,0.002939445,0.002130357,0.002745725,0.002537905,0.003596319,0.002679005,0.003646311,0.002892941,0.002814363,0.002955197,0.003061557,0.003011819,0.003146771,0.002653854,0.002262629,0.003445287,0.00242548,0.003503066,0.001852882,0.002635448,0.003164103,0.00287955,0.002953393,0.003300628,0.002467521,0.003496141,0.005711787,0.002572354,0.00274642,0.002592796,0.00405076,0.003749407,0.003154614,0.003250697,0.003303471,0.003078609,0.003487908,0.00347683,0.002904609,0.002312779,0.002572475,0.003625578,0.003314829,0.004698727],"mean":0.003294327159999999,"singleMean":0.003294327159999999,"variance":0.0000026503800015192793,"deviation":0.0016279987719649174,"sem":0.00023023379428395297,"moe":0.0004512582367965478,"rme":13.698039535227824,"p75":0.0034795995,"p95":0.005154603999999996,"p99":0.01357674,"p999":0.01357674},"errors":{},"options":{"method":"get","concurrencyLevel":1,"start":"2015-10-11T15:13:42.005Z","end":"2015-10-11T15:13:42.170Z"},"request":{},"response":{"header":{"x-powered-by":"Express","content-type":"application/json; charset=utf-8","content-length":"27","etag":"\"1930243982\"","date":"Sun, 11 Oct 2015 15:13:42 GMT","connection":"close"},"statusCode":200,"body":"{\n \"message\": \"/getJson\"\n}","type":"application/json"},"hz":303.5521220060002},"secondaryRoute":{"name":"My api/secondaryRoute","href":"http://localhost:3006/getJson2","stats":{"sample":[0.002654189,0.002381642,0.002134532,0.002947698,0.003579008,0.002960868,0.002839665,0.002377134,0.002472979,0.005438477,0.003340719,0.003008837,0.003313665,0.002683624,0.004567821,0.010302359,0.004997022,0.003113254,0.002814524,0.002619174,0.002300552,0.00258412,0.003411082,0.00313257,0.002734786,0.003108364,0.002293127,0.002632554,0.003800198,0.003442057,0.002560258,0.002615407,0.002617405,0.003512938,0.002971256,0.002658991,0.00244818,0.003369597,0.001337099,0.002636872,0.002960341,0.002927998,0.002976053,0.003787202,0.002946055,0.002908672,0.002680191,0.003344324,0.003011174,0.003083977],"mean":0.0031468118199999996,"singleMean":0.0031468118199999996,"variance":0.0000015286028098581915,"deviation":0.001236366778046948,"sem":0.000174848666558152,"moe":0.00034270338645397795,"rme":10.890495080636185,"p75":0.00334162025,"p95":0.0051956767499999985,"p99":0.010302359,"p999":0.010302359},"errors":{},"options":{"method":"get","concurrencyLevel":1,"start":"2015-10-11T15:13:42.173Z","end":"2015-10-11T15:13:42.331Z"},"request":{},"response":{"header":{"x-powered-by":"Express","content-type":"application/json; charset=utf-8","content-length":"28","etag":"\"-251859214\"","date":"Sun, 11 Oct 2015 15:13:42 GMT","connection":"close"},"statusCode":200,"body":"{\n \"message\": \"/getJson2\"\n}","type":"application/json"},"hz":317.781951130462},"postRoute":{"name":"My api/postRoute","href":"http://localhost:3006/postJson","stats":{"sample":[0.004925044,0.002757273,0.002829501,0.002946599,0.00288121,0.002922298,0.001276902,0.002682766,0.001308097,0.002660585,0.002935274,0.002914031,0.002581371,0.003142122,0.002888957,0.003818671,0.002865263,0.002998599,0.002770339,0.002315193,0.002590548,0.002691801,0.002796524,0.00293225,0.005992326,0.003039912,0.004061229,0.003393566,0.003007595,0.002770579,0.003997125,0.009785281,0.003336936,0.002927775,0.003217157,0.002875874,0.002935301,0.003578158,0.00368908,0.002980481,0.002971544,0.002925199,0.002963889,0.003007356,0.003008785,0.002898165,0.002903681,0.002985745,0.002941816,0.004044152],"mean":0.0031933984999999994,"singleMean":0.0031933984999999994,"variance":0.0000014116764484460514,"deviation":0.0011881399111409612,"sem":0.00016802835763323113,"moe":0.00032933558096113303,"rme":10.313012327184756,"p75":0.00316088075,"p95":0.0054053208999999955,"p99":0.009785281,"p999":0.009785281},"errors":{},"options":{"method":"post","concurrencyLevel":1,"start":"2015-10-11T15:13:42.332Z","end":"2015-10-11T15:13:42.493Z"},"request":{"data":{"test":true,"someData":"someStrings"}},"response":{"header":{"x-powered-by":"Express","content-type":"application/json; charset=utf-8","content-length":"28","date":"Sun, 11 Oct 2015 15:13:42 GMT","connection":"close"},"statusCode":200,"body":"{\n \"message\": \"/postJson\"\n}","type":"application/json"},"hz":313.146010433712},"deleteRoute":{"name":"My api/deleteRoute","href":"http://localhost:3006/deleteMe?test=true","stats":{"sample":[0.005121029,0.003111974,0.003223593,0.002957041,0.003105988,0.003047021,0.002998718,0.002291656,0.00258632,0.002789987,0.003061476,0.002633973,0.002867664,0.002704424,0.004195614,0.002841241,0.002816914,0.002984814,0.00306308,0.002988983,0.002994484,0.003017741,0.002997348,0.003002317,0.002942033,0.002925172,0.003279665,0.002967981,0.008361879,0.00273283,0.002807183,0.002975581,0.00309248,0.002989029,0.003031334,0.003246972,0.002994813,0.002954762,0.003490526,0.003793165,0.002747762,0.002666588,0.00152271,0.003380057,0.003179591,0.003566917,0.004632012,0.004006566,0.003297438,0.002669178],"mean":0.00319315248,"singleMean":0.00319315248,"variance":8.527159978142545e-7,"deviation":0.0009234262275971235,"sem":0.00013059218949188763,"moe":0.0002559606914040998,"rme":8.015924482381742,"p75":0.00322943775,"p95":0.004852069649999998,"p99":0.008361879,"p999":0.008361879},"errors":{},"options":{"method":"delete","concurrencyLevel":1,"start":"2015-10-11T15:13:42.494Z","end":"2015-10-11T15:13:42.654Z"},"request":{},"response":{"header":{"x-powered-by":"Express","content-type":"application/json; charset=utf-8","content-length":"28","date":"Sun, 11 Oct 2015 15:13:42 GMT","connection":"close"},"statusCode":200,"body":"{\n \"message\": \"/deleteMe\"\n}","type":"application/json"},"hz":313.17013711791174}}},"info":{"date":"2015-10-11T15:13:42.658Z","apiName":"My api"}};
// --></script>
<div id="report">
<div id="report-details"><h1>Api Benchmark report</h1></div>
<div id="charts"></div>
</div>
<div style="text-align: center">
Generated by <a href="https://github.com/matteofigus/api-benchmark" target="_blank">api-benchmark</a><br /><br />
<iframe src="http://ghbtns.com/github-btn.html?user=matteofigus&repo=api-benchmark&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="110" height="20"></iframe>
</div>
<script>
var selectors = {
charts: '#charts',
reportDetails: '#report-details',
panel: '.panel',
panels: {
stats: '.stats',
distribution: '.distribution',
response: '.response',
request: '.request'
},
button: '.button',
buttons: {
stats: '.button-stats',
distribution: '.button-distribution',
response: '.button-response',
request: '.button-request'
}
};
var formatNumber = function(number) {
number = String(number).split('.');
return number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') +
(number[1] ? '.' + number[1] : '');
};
var formatDate = function(date){
return date.replace(/T/g, ' ').replace(/Z/g, ' ');
};
var fixNumber = function(number, digits){
return formatNumber(number.toFixed(digits));
};
var decodeHtml = function(text){
return $('<div/>').text(text).html();
};
var templates = {
reportDetails: function(info){
var formattedDate = formatDate(info.date);
return 'Benchmarked ' + info.apiName + ' on ' + formattedDate;
},
route: function(routeData, i){
var errorsCount = 0,
errorsDesc = '(',
duration = (new Date(routeData.options.end) - new Date(routeData.options.start))/1e3;
for(var errorType in routeData.errors){
errorsCount += routeData.errors[errorType].length;
errorsDesc += routeData.errors[errorType].length + ' ' + errorType + ',';
}
if(errorsCount == 0)
errorsDesc = '';
else
errorsDesc = errorsDesc.substr(0, errorsDesc.length - 1) + ')';
var template = '<div class="route"><div class="buttons">' +
'<a href="#stats" class="button button-stats selected">Stats</a>' +
'<a href="#distribution" class="button button-distribution">Distribution</a>' +
'<a href="#request" class="button button-request">Request details</a>' +
'<a href="#response" class="button button-response">Response details</a>' +
'</div><div class="stats panel"><div class="chart" id="chart' + i + '"></div>' +
'<div class="stats-details"><ul><li>' + routeData.stats.sample.length + ' run' +
(routeData.stats.sample.length > 1 ? 's' : '') + ' sampled with concurrency: ' +
routeData.options.concurrencyLevel + '</li>';
if(routeData.options.expectedStatusCode)
template += '<li>Expected status code: ' + routeData.options.expectedStatusCode + '</li>';
if(routeData.options.maxMean)
template += '<li>Expected max mean: ' + routeData.options.maxMean + '</li>';
if(routeData.options.maxSingleMean)
template += '<li>(*) Expected max mean across all concurrent requests: ' + routeData.options.maxSingleMean + '</li>';
template += '</ul><ul><li>Started: ' + formatDate(routeData.options.start) + '</li>' +
'<li>Ended: ' + formatDate(routeData.options.end) + ' (' + duration + ' seconds)</li>' +
'<li>' + fixNumber(routeData.hz, 2) + ' ops/sec \xb1 ' + fixNumber(routeData.stats.rme, 2) + '%</li>' +
'<li class="' + (!errorsCount ? 'green' : 'red') + '">' + errorsCount + ' error' +
(errorsCount == 1 ? '' : 's') + ' ' + errorsDesc + '</li>' +
'<li>Sample arithmetic mean: ' + fixNumber(routeData.stats.mean, 6) + '</li>';
if(routeData.options.concurrencyLevel > 1)
template += '<li>Mean across all the concurrent requests: ' + fixNumber(routeData.stats.singleMean, 6) + '</li>';
template += '<li>Sample standard deviation: ' + fixNumber(routeData.stats.deviation, 6) + '</li>' +
'<li>Margin of error: ' + fixNumber(routeData.stats.moe, 6) + '</li>' +
'<li>The standard error of the mean: ' + fixNumber(routeData.stats.sem, 6) + '</li>' +
'<li>The sample variance: ' + fixNumber(routeData.stats.variance, 6) + '</li>' +
'</ul><ul><li>75% Percentile: ' + fixNumber(routeData.stats.p75, 6) + '</li>' +
'<li>95% Percentile: ' + fixNumber(routeData.stats.p95, 6) + '</li>' +
'<li>99% Percentile: ' + fixNumber(routeData.stats.p99, 6) + '</li>' +
'<li>99.9% Percentile: ' + fixNumber(routeData.stats.p999, 6) + '</li>' +
'</ul></div></div><div class="distribution panel"><div class="distchart" id="distchart' + i + '">' +
'</div></div><div class="request panel hide">Request: ' + routeData.options.method.toUpperCase() +
' <a href="' + routeData.href + '" target="_blank">' +
routeData.href + '</a><br />Concurrency level: ' + routeData.options.concurrencyLevel + '<br />';
if(routeData.options.delay)
template += 'Delay between bench cycles: ' + routeData.options.delay + '<br />';
var requestHeaders = 'none',
requestData = 'none',
requestQuery = 'none';
if(!!routeData.request.headers)
requestHeaders = '<br /><pre><code>' + JSON.stringify(routeData.request.headers, undefined, 2) + '</pre></code>';
if(!!routeData.request.data)
requestData = '<br /><pre><code>' + JSON.stringify(routeData.request.data, undefined, 2) + '</pre></code>';
if(!!routeData.request.query)
requestQuery = '<br /><pre><code>' + JSON.stringify(routeData.request.query, undefined, 2) + '</pre></code>';
template += '<br />Headers sent: ' + requestHeaders + '<br />Data sent: ' + requestData + '<br />Query sent: ' + requestQuery + '<br /></div><div class="response panel hide">Data here refers to the first sampled response.<br /><br />';
var responseHeader = JSON.stringify(routeData.response.header, undefined, 2),
responseBody = '';
try {
responseBody = JSON.stringify(JSON.parse(decodeHtml(routeData.response.body)), undefined, 2);
} catch(e){
responseBody = decodeHtml(routeData.response.body);
}
template += 'Response status: ' + routeData.response.statusCode + '<br />Response headers:<br />' +
'<pre><code>' + responseHeader + '</code></pre>' +
'<br />Response body:<br /><pre><code>' + responseBody + '</code></pre><br /></div></div>';
return template;
}
};
var bindButtons = function(){
var bindButton = function(panelName){
$(selectors.buttons[panelName]).click(function(){
var $sender = $(this),
$panelsContainer = $sender.parent().parent();
$panelsContainer.find(selectors.button).removeClass('selected');
$sender.addClass('selected');
$panelsContainer.find(selectors.panel).addClass('hide');
$panelsContainer.find(selectors.panels[panelName]).removeClass('hide');
return false;
});
};
bindButton('stats');
bindButton('distribution');
bindButton('response');
bindButton('request');
};
$(function(){
$(selectors.reportDetails).append(templates.reportDetails(data.info));
var counter = 0;
for(serviceName in data.benchmark){
for(routeName in data.benchmark[serviceName]){
$(selectors.charts).append(templates.route(data.benchmark[serviceName][routeName], counter));
var samples = data.benchmark[serviceName][routeName]['stats']['sample'],
meanArray = [],
mean = data.benchmark[serviceName][routeName]['stats']['mean'],
singleMeanArray = [],
singleMean = data.benchmark[serviceName][routeName]['stats']['singleMean'],
maxMeanArray = [],
maxMean = data.benchmark[serviceName][routeName].options.maxMean !== undefined,
maxSingleMeanArray = [],
maxSingleMean = data.benchmark[serviceName][routeName].options.maxSingleMean !== undefined,
errorsArray = [],
areThereSomeErrors = false;
for(var i = 0; i < samples.length; i++){
meanArray.push(mean);
singleMeanArray.push(singleMean);
errorsArray.push(null);
if(maxMean)
maxMeanArray.push(data.benchmark[serviceName][routeName].options.maxMean);
if(maxSingleMean)
maxSingleMeanArray.push(data.benchmark[serviceName][routeName].options.maxSingleMean);
}
for(var errorType in data.benchmark[serviceName][routeName].errors){
var errorsOfType = data.benchmark[serviceName][routeName]['errors'][errorType];
for(var i = 0; i < errorsOfType.length; i++){
if(errorsOfType[i].pos !== undefined){
errorsArray[errorsOfType[i].pos] = data.benchmark[serviceName][routeName]['stats']['sample'][errorsOfType[i].pos];
areThereSomeErrors = true;
}
}
}
var getSeriesOptions = function(label, color){
return {
showMarker: false,
pointLabels: { show: true },
label: label,
color: color
};
};
var lines = [samples],
series = [getSeriesOptions('Samples', '#6699FF')];
if(maxMean){
lines.push(maxMeanArray);
series.push(getSeriesOptions('Max Mean', '#F90'));
}
lines.push(meanArray);
series.push(getSeriesOptions('Mean', '#66FF00'));
if(maxSingleMean){
lines.push(maxSingleMeanArray);
series.push(getSeriesOptions('Max Single Mean (*)', '#EBFF00'));
lines.push(singleMeanArray);
series.push(getSeriesOptions('Single mean', '#FC6333'));
}
if(areThereSomeErrors > 0){
lines.push(errorsArray);
series.push({
showLine:false,
markerOptions: { size: 7, style:'x' },
label: 'Errors',
color: '#FF0000'
});
}
var options = {
title: {
text: data.benchmark[serviceName][routeName]['name'],
color: '#F1F1F1'
},
animate: true,
grid: {
background: '#000000', textColor: '#F1F1F1'
},
seriesDefaults:{
rendererOptions: {
showDataLabels: true
}
},
highlighter: {
show: true,
sizeAdjust: 7.5
},
legend: {
show:true,
location: 'se',
background: 'rgba(101, 0, 253, 0.89)'
},
axesDefaults: {
labelRenderer: $.jqplot.CanvasAxisLabelRenderer
},
axes: {
xaxis: {
label: 'Samples',
pad: 0
},
yaxis: {
label: 'Response time (seconds)',
tickOptions: {
formatString:'%.6f'
}
}
},
series: series
};
$.jqplot('chart' + counter, lines, options);
counter++;
}
}
hljs.initHighlightingOnLoad();
bindButtons();
});
$(function(){
var counter = 0;
for(serviceName in data.benchmark){
for(routeName in data.benchmark[serviceName]){
var samples = data.benchmark[serviceName][routeName]['stats']['sample'];
var buckets = [[0.001, 0], [0.005, 0], [0.01, 0], [0.015, 0], [0.025, 0], [0.05, 0], [0.1, 0], [0.25, 0], [0.333, 0], [0.5, 0], [0.750, 0], [1.5, 0], [3, 0], [6, 0], [12, 0], [50, 0], [100, 0], [300, 0], [1000, 0]];
for(var i = 0; i<samples.length; i++) {
for (var j =0; j<buckets.length; j++) {
if(buckets[j][0]>= samples[i]){
buckets[j][1] += 100/samples.length;
break;
}
};
}
var distribution = [];
for(i =0; i<buckets.length; i++) {
if(buckets[i][1]){
distribution.push(buckets[i]);
}
}
var lines = [distribution],
series = [{
showMarker: false,
pointLabels: { show: true },
label: 'Samples',
color: '#6699FF'
}];
var options = {
title: {
text: data.benchmark[serviceName][routeName]['name'],
color: '#F1F1F1'
},
animate: true,
grid: {
background: '#000000', textColor: '#F1F1F1'
},
seriesDefaults:{
rendererOptions: {
showDataLabels: true
}
},
highlighter: {
show: true,
sizeAdjust: 7.5
},
legend: {
show:true,
location: 'se',
background: 'rgba(101, 0, 253, 0.89)'
},
axesDefaults: {
labelRenderer: $.jqplot.CanvasAxisLabelRenderer
},
axes: {
xaxis: {
label: 'Response time (seconds)',
tickOptions: {
formatString:'%.3f'
}
},
yaxis: {
label: 'Percent of responses',
tickOptions: {
formatString: function(val){
console.log(val);
return '%.1f%';}()
}
}
},
series: series
};
$.jqplot('distchart' + counter, lines, options);
counter++;
}
}
$(".distribution.panel").addClass('hide');
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment