Last active
October 25, 2019 09:59
-
-
Save hadaytullah/7c5f007239c562dab91010b3152b974b to your computer and use it in GitHub Desktop.
Statistics collector that calculates the median and average request response times for a 7 days dataset.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Our application servers receive approximately 20 000 | |
* http requests per second. Response timeout is 19000ms. | |
* Implement a statistics collector that calculates the | |
* median and average request response times for a 7 day | |
* dataset. | |
*/ | |
'use strict'; | |
// Stats collector | |
class StatsCollector { | |
constructor(/*void*/) { | |
this.responseTimeMsList = []; | |
} | |
/* | |
* Pushes a value to the responseTimeMsList | |
* @param {Number} responseTimeMs A response time value | |
* @throw {Error} Will throw "invalid response time value" error | |
*/ | |
pushValue(responseTimeMs /*number*/) /*void*/ { | |
//checking if the value a valid response time | |
if(this.isInvalidResponseTime(responseTimeMs)){ | |
throw new Error("invalid response time value") | |
}else{ | |
//a valid value is pushed to the array | |
this.responseTimeMsList.push(parseFloat(responseTimeMs)); | |
} | |
} | |
/* | |
* Calculates median of the response time values | |
* Even number of entries: the meidan will be averge of the two mid entries | |
* Odd number of entires: the median will be the middle entry | |
* @returns {Number} The median of response time values | |
* @throws {Error} Will throw 'the response time list is empty' error | |
*/ | |
getMedian() /*number*/ { | |
var responseTimeMedian = -1; | |
if(this.responseTimeMsList.length == 0){ | |
throw new Error("the response time list is empty") | |
}else if(this.responseTimeMsList.length == 1){//if it has only one value then that is the median | |
responseTimeMedian = this.responseTimeMsList[0]; | |
}else if(this.responseTimeMsList.length == 2){ // incase of two values no need to do sorting etc. the mean of both is the median. | |
responseTimeMedian = (this.responseTimeMsList[0]+this.responseTimeMsList[1])/2; | |
}else{ | |
this.sort(); | |
//even or odd | |
if(this.isEvenNumber(this.responseTimeMsList.length)){ | |
var halfLength = this.responseTimeMsList.length/2; | |
responseTimeMedian = (this.responseTimeMsList[halfLength] + this.responseTimeMsList[halfLength-1])/2; | |
}else{ | |
var halfLength = Math.floor(this.responseTimeMsList.length/2); | |
responseTimeMedian = this.responseTimeMsList[halfLength]; | |
} | |
} | |
return responseTimeMedian; | |
} | |
/* | |
* Sorts the response times in an ascending order | |
*/ | |
sort(){ | |
var compareNumbers = function(a, b){ | |
return a - b; | |
} | |
this.responseTimeMsList.sort(compareNumbers); | |
} | |
/* | |
* Checks if a number is a Even number | |
* @param {Number} number The number | |
* @returns {Boolean} True when even number otherwise false | |
*/ | |
isEvenNumber(number) /* boolean */{ | |
// not meant for negative numbers | |
if (number < 0){ | |
return false; | |
} | |
var fraction = number/2; | |
if (fraction == Math.floor(fraction)){ | |
return true; | |
} | |
return false; | |
} | |
/* | |
* Checks if a value is a valid response time | |
* @param {Number} responseTime The response time value | |
* @returns {Boolean} True if a valid response time value otherwise false | |
*/ | |
isInvalidResponseTime(responseTime) { | |
if(responseTime<0 || responseTime==null || responseTime == undefined || isNaN(parseFloat(responseTime)) || !isFinite(responseTime)){ | |
return true; | |
} | |
return false; | |
} | |
/* | |
* Averages the response time values | |
* @returns {Number} The average of the response time values | |
* @throws {Error} Will throw "the response time list is empty" error | |
*/ | |
getAverage() { | |
var responseTimeSum=0, responseTimeAverage=0; | |
if(this.responseTimeMsList.length == 0){ | |
throw new Error("the response time list is empty") | |
}else if(this.responseTimeMsList.length == 1){ | |
responseTimeAverage = this.responseTimeMsList[0]; | |
}else{ | |
this.responseTimeMsList.forEach((function(responseTime){ | |
responseTimeSum +=responseTime; | |
}).bind(this)); | |
if(responseTimeSum > 0) { | |
responseTimeAverage = responseTimeSum / this.responseTimeMsList.length | |
} | |
} | |
return responseTimeAverage; | |
} | |
} | |
// Configure Mocha, telling both it and chai to use BDD-style tests. | |
mocha.setup("bdd"); | |
chai.should(); | |
describe('StatsCollector', function(){ | |
//------ Valid/invalid response time values ----------------- | |
it('Test001: Populating response time array with valid values.', function(){ | |
var statsCollector = new StatsCollector(); | |
statsCollector.pushValue(200); | |
statsCollector.pushValue('200'); | |
statsCollector.pushValue(200.123); | |
statsCollector.pushValue(0); | |
statsCollector.responseTimeMsList.should.have.length(4); | |
}); | |
it('Test002: Populating response time array with invalid values.', function(){ | |
var statsCollector = new StatsCollector(); | |
(function(){statsCollector.pushValue('ff');}).should.throw(Error,"invalid response time value"); | |
(function(){statsCollector.pushValue(null);}).should.throw(Error,"invalid response time value"); | |
(function(){statsCollector.pushValue(1/0);}).should.throw(Error,"invalid response time value"); | |
(function(){statsCollector.pushValue('');}).should.throw(Error,"invalid response time value"); | |
}); | |
//----------- AVERAGE TESTS ------------ | |
it('Test003: Average of an empty list.', function(){ | |
var statsCollector = new StatsCollector(); | |
(function(){statsCollector.getAverage();}).should.throw(Error, "the response time list is empty"); | |
}); | |
it('Test004: Average of single response time entry.', function(){ | |
var statsCollector = new StatsCollector(); | |
statsCollector.pushValue(200); | |
statsCollector.getAverage().should.equal(200); | |
}); | |
it('Test005: Average of two response time entries.', function(){ | |
var statsCollector = new StatsCollector(); | |
statsCollector.pushValue(140); | |
statsCollector.pushValue(160); | |
statsCollector.getAverage().should.equal(150); | |
}); | |
it('Test006: Averaging whole numbers.', function(){ | |
var statsCollector = new StatsCollector(); | |
statsCollector.pushValue(190); | |
statsCollector.pushValue(200); | |
statsCollector.pushValue(210); | |
statsCollector.pushValue(220); | |
statsCollector.getAverage().should.equal(205); | |
}); | |
it('Test007: Averaging with decimal point.', function(){ | |
var statsCollector = new StatsCollector(); | |
statsCollector.pushValue(190.145); | |
statsCollector.pushValue(190.145); | |
statsCollector.pushValue(190.145); | |
statsCollector.getAverage().should.equal(190.145); | |
}); | |
//----------- MEDIAN TESTS ------------ | |
it('Test008: Median of an empty list.', function(){ | |
var statsCollector = new StatsCollector(); | |
(function(){statsCollector.getMedian();}).should.throw(Error, "the response time list is empty"); | |
}); | |
it('Test009: Median of single response time entry.', function(){ | |
var statsCollector = new StatsCollector(); | |
statsCollector.pushValue(200); | |
statsCollector.getMedian().should.equal(200); | |
}); | |
it('Test010: Median of two response time entries.', function(){ | |
var statsCollector = new StatsCollector(); | |
statsCollector.pushValue(190); | |
statsCollector.pushValue(110); | |
statsCollector.getMedian().should.equal(150); | |
}); | |
it('Test011: Median of odd number of entries.', function(){ | |
var statsCollector = new StatsCollector(); | |
statsCollector.pushValue(200); | |
statsCollector.pushValue(150); | |
statsCollector.pushValue(180); | |
statsCollector.pushValue(170); | |
statsCollector.pushValue(140); | |
statsCollector.getMedian().should.equal(170); | |
}); | |
it('Test012: Median of even number of entries.', function(){ | |
var statsCollector = new StatsCollector(); | |
statsCollector.pushValue(200); | |
statsCollector.pushValue(150); | |
statsCollector.pushValue(180); | |
statsCollector.pushValue(170); | |
statsCollector.pushValue(140); | |
statsCollector.pushValue(120); | |
statsCollector.getMedian().should.equal(160); | |
}); | |
it('Test013: Median of even number of entries containing decimal point values.', function(){ | |
var statsCollector = new StatsCollector(); | |
statsCollector.pushValue(200); | |
statsCollector.pushValue(150.10); | |
statsCollector.pushValue(180); | |
statsCollector.pushValue(170.30); | |
statsCollector.pushValue(140); | |
statsCollector.pushValue(120); | |
statsCollector.getMedian().should.equal(160.20); | |
}); | |
it('Test014: Average & median of all zero value entries.', function(){ | |
var statsCollector = new StatsCollector(); | |
statsCollector.pushValue(0); | |
statsCollector.pushValue(0); | |
statsCollector.pushValue(0); | |
statsCollector.getAverage().should.equal(0); | |
statsCollector.getMedian().should.equal(0); | |
}); | |
it('Test015: Average & median of numbers pushed as strings.', function(){ | |
var statsCollector = new StatsCollector(); | |
statsCollector.pushValue('200'); | |
statsCollector.pushValue('150'); | |
statsCollector.pushValue('180'); | |
statsCollector.pushValue('170'); | |
statsCollector.pushValue('140'); | |
statsCollector.getAverage().should.equal(168); | |
statsCollector.getMedian().should.equal(170); | |
}); | |
//----------- Stress test --------------------- | |
//Can't run, browswer is crashing | |
/*it('Test016: Stress test, pushing 7 days data.', function(){ | |
var statsCollector = new StatsCollector(); | |
var requestsIn7Days=20000*60*60*24*7; | |
for(var i=1; i<=requestsIn7Days; i++){ | |
statsCollector.pushValue('200'); | |
} | |
statsCollector.responseTimeMsList.should.have.length(4); | |
});*/ | |
}); | |
// Run all our test suites. Only necessary in the browser. | |
mocha.run(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment