Skip to content

Instantly share code, notes, and snippets.

@hadaytullah
Last active October 25, 2019 09:59
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 hadaytullah/7c5f007239c562dab91010b3152b974b to your computer and use it in GitHub Desktop.
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.
/*
* 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