Skip to content

Commit

Permalink
Merge pull request #15 from zlatko-ms/12-performance-monitoring
Browse files Browse the repository at this point in the history
12 performance monitoring
  • Loading branch information
zlatko-ms authored Sep 1, 2022
2 parents 29c66d9 + fa3d66f commit be8d3df
Show file tree
Hide file tree
Showing 11 changed files with 344 additions and 19 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ Most of the available infrastructures will require healthchecks, being it on the

In order to integrate with those infrastructures you can use the **'/health'** endpoint of the workload that will return a small payload with a 200 HTTP status, which is more then sufficient to deploy the workload in those conditions

### Performance Tracking

The application integrates very basic perfromance tracking that simply exposes the aggregated RPS (request per second) metric. It can come handy in order to check quota limits or basic network connectivity performance, usefull when benching different Load Balancer solutions.

## Code

The code is pretty staightfoward and easy to update :
Expand Down Expand Up @@ -86,6 +90,7 @@ The following table lists the used environnement variables :
| /connectivity/public | translates calls to github api |
| /connectivity/spoke | translates calls to other spoke http server (you can use a dedicated helloer as well) |
| /connectivity/onprem | translates calls to on prem http server (uou can use a dedicated helloer as well) |
| /perfs | exposition of basic aggregated Request Per Second metric as well as breakdown per service |

### HTTP Responses

Expand Down
18 changes: 11 additions & 7 deletions app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,24 @@ var express = require('express');
const { Logger } = require('@util/logger.js');
var { healthProbeHandler } = require('@handlers/health.js');
var { helloHandler } = require('@handlers//hello.js');
var { spokeForwarder, onpremForwarder } = require('@handlers/privateForwarders.js');
var { forwardCallHandler } = require('@handlers/privateForwarders.js');
var { githubForwarder } = require('@handlers/githubForwarder.js');
//var { onpremForwarder } = require('@handlers/onpremForwarder.js');
var { perfHandler } = require('@handlers/perfs.js')

// configuration
var { appPort } = require('@util/configuration.js');
var { appPort , forwardSpokeUrl, forwardOnPremUrl } = require('@util/configuration.js');

// app
Logger.info("starting backend");
var app = express();

// exposed services
app.get('/health', healthProbeHandler)
app.get('/connectivity/local', helloHandler);
app.get('/connectivity/spoke', spokeForwarder );
app.get('/connectivity/public', githubForwarder);
app.get('/connectivity/onprem', onpremForwarder);
app.get('/perfs',perfHandler)
app.get('/connectivity/local', (req,res) => helloHandler(req,res,"connectivity/local") );
app.get('/connectivity/spoke', (req,res) => forwardCallHandler(req,res,"connectivity/spoke",forwardSpokeUrl,"spoke"));
app.get('/connectivity/onprem', (req,res) => forwardCallHandler(req,res,"connectivity/onprem",forwardOnPremUrl,"onprem"));
app.get('/connectivity/public', (req,res) => githubForwarder(req,res,"connectivity/public"));

app.listen(appPort);
Logger.info("backend started, listening on port="+appPort);
6 changes: 4 additions & 2 deletions app/handlers/githubForwarder.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ const { Logger } = require('@util/logger.js');
var { responseBuilder } = require('@util/response.js');
const url= require('url');
const axios = require('axios');

const { BackendPerformanceTracker } = require('@util/perf.js');
const githubUsername = process.env.HELLOER_FORWARDER_GITHUB_USERNAME || "funkomatic";

module.exports.githubForwarder = function (req,res) {
module.exports.githubForwarder = function (req,res,serviceName) {

Logger.info("recv public forwarding request, retrieving "+githubUsername+" repos from github");

var callUrl = "https://api.github.com/users/"+githubUsername+"/repos";
var responsePayload=responseBuilder(req);

BackendPerformanceTracker.addHit(serviceName)

axios.get(callUrl).then(resAxios => {
var repoCount = resAxios.data.length;
Expand Down
1 change: 1 addition & 0 deletions app/handlers/health.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const { Logger } = require('@util/logger.js');
var { responseBuilder } = require('@util/response.js');
const { logProbeCalls } = require('@util/configuration.js')
const { BackendPerformanceTracker } = require('@util/perf.js');

module.exports.healthProbeHandler = function(req,res) {
if (logProbeCalls) {
Expand Down
6 changes: 4 additions & 2 deletions app/handlers/hello.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@

const { Logger } = require('@util/logger.js');
var { responseBuilder } = require('@util/response.js');
const { BackendPerformanceTracker } = require('@util/perf.js');

module.exports.helloHandler = function(req,res) {
module.exports.helloHandler = function(req,res,serviceName) {
Logger.info("recv hello request");
BackendPerformanceTracker.addHit(serviceName)
var responsePayload = responseBuilder(req);
responsePayload['message']="hello";
res.json(responsePayload);
Logger.info("sent hello response to client from "+responsePayload.request_source_ip);
Logger.info("sent "+serviceName+" response to client from "+responsePayload.request_source_ip);
}

18 changes: 18 additions & 0 deletions app/handlers/perfs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const { Logger } = require('@util/logger.js');
const { forwardSpokeUrl, forwardOnPremUrl} = require('@util/configuration.js')
var { responseBuilder } = require('@util/response.js');
const { BackendPerformanceTracker } = require('@util/perf.js');
const axios = require('axios');

module.exports.perfHandler= function(req,res) {
Logger.info("recv hello request");
var responsePayload = responseBuilder(req);


responsePayload['perfs']= {
'aggRps' : BackendPerformanceTracker.getAggRps(),
'services' : BackendPerformanceTracker.getServiceBreakdown()
}
res.json(responsePayload);
Logger.info("sent perf stats to client from "+responsePayload.request_source_ip);
}
10 changes: 6 additions & 4 deletions app/handlers/privateForwarders.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
const { Logger } = require('@util/logger.js');
const { forwardSpokeUrl, forwardOnPremUrl} = require('@util/configuration.js')
var { responseBuilder } = require('@util/response.js');
const axios = require('axios');
const { BackendPerformanceTracker } = require('@util/perf.js');

function forwardCall(req,res,forwardUrl,label) {
module.exports.forwardCallHandler = function(req,res,serviceName,forwardUrl,label) {

Logger.info("recv private forwarding request, using private endpoint "+forwardUrl);

var responsePayload=responseBuilder(req);
BackendPerformanceTracker.addHit(serviceName);

axios.get(forwardUrl).then(resAxios => {
responsePayload[label+"_status"]="success";
Expand All @@ -20,7 +22,7 @@ function forwardCall(req,res,forwardUrl,label) {
responsePayload[label+"_response"]=err;
res.status(500).json(responsePayload);
});

};

module.exports.onpremForwarder = function (req,res) { return forwardCall(req,res,forwardOnPremUrl,"onprem") }
module.exports.spokeForwarder = function (req,res) { return forwardCall(req,res,forwardSpokeUrl,"spoke") }

139 changes: 139 additions & 0 deletions app/util/perf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@


const { Logger } = require('@util/logger.js');
var format = require('date-format');
const { loggers } = require('winston');

const dateFormat = 'yyyyMMddhhmm'

let ServicePerfRecord = class {

constructor(start, end, count) {
this.startDate = start
this.endDate = end
this.avgRps = count
}

set startDate(date) { this._startDate = date }
get startDate() { return this._startDate }

get endDate() { return this._endDate }
set endDate(date) { this._endDate = date }

get avgRps() { return this._avgHitsPerSecond }
set avgRps(val) { this._avgHitsPerSecond = val }
}



let ServicePerfTracker = class {

constructor(serviceName) {
this._serviceName = serviceName
this._records = new Map()
}

addHit(date = new Date()) {
let key = format.asString(dateFormat, date);
var hits = this._records.has(key) ? this._records.get(key) : 0
hits++
this._records.set(key, hits)
this._purgeOldRecords()
}

_purgeOldRecords(hours = 2) {
var keysToRemove = new Set()
var lastDateToKeep = new Date()
lastDateToKeep.setHours(lastDateToKeep.getHours() - hours)
let lastDateToKeepString = format.asString(dateFormat, lastDateToKeep);
let lastDateToKeepAsInt = parseInt(lastDateToKeepString)
this._records.forEach((value, key) => {
if (parseInt(key) <= lastDateToKeepAsInt) {
keysToRemove.add(key)
}
})
keysToRemove.forEach(k => {
this._records.delete(k)
});
}

getServicePerfRecord() {

var datesArray = []
var hits = 0;
this._records.forEach((v, k) => {
var keyDate = format.parse(dateFormat, k)
datesArray.push(keyDate)
hits += v
})

let minutesCount = datesArray.length
if (minutesCount>0) {

datesArray.sort((a, b) => {return a - b})
var avgHits = hits / (minutesCount * 60)

var startDate = datesArray[0]
startDate.setSeconds(0)
startDate.setMilliseconds(0)

var endDate = datesArray.pop()
endDate.setSeconds(0)
endDate.setMilliseconds(0)

return new ServicePerfRecord(startDate, endDate, avgHits)
}

return new ServicePerfRecord(new Date, new Date, Number.NaN);
}

getIndicatorHistory() {
return new Map([...this._records].sort((a, b) => String(a[0]).localeCompare(b[0])))
}

}

let PerformanceTracker = class {

constructor() {
this._serviceTrackers = new Map()
}

addHit(serviceName, date = new Date) {
var svcTracker = this._serviceTrackers.has(serviceName) ? this._serviceTrackers.get(serviceName) : new ServicePerfTracker(serviceName)
svcTracker.addHit(date)
this._serviceTrackers.set(serviceName, svcTracker)
}

getServicePerfRecord(serviceName) {
var svcTracker = this._serviceTrackers.has(serviceName) ? this._serviceTrackers.get(serviceName) : new ServicePerfTracker(serviceName)
return svcTracker.getServicePerfRecord()
}

getTrackedServices() {
return new Set(this._serviceTrackers.keys())
}

getAggRps() {

var totalHits = 0
this._serviceTrackers.forEach( (v,k) => {
let serviceRecord = v.getServicePerfRecord()
totalHits+=serviceRecord.avgRps
})
return totalHits;
}

getServiceBreakdown() {
var ret = new Map()
this._serviceTrackers.forEach( (v,k) => { ret[k]=v.getServicePerfRecord() });
return ret;
}

}



var BackendPerformanceTracker = new PerformanceTracker()

module.exports = { ServicePerfRecord , ServicePerfTracker , PerformanceTracker, BackendPerformanceTracker }
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "helloer",
"version": "1.0.4",
"version": "1.0.5",
"private": true,
"scripts": {
"app:clean": "rm -rf node_modules/* && rm -f *.log && rm -f helloer-dist.*.gz",
Expand All @@ -12,6 +12,7 @@
"app:test": "mocha --reporter spec"
},
"dependencies": {
"date-format" : "^4.0.13",
"axios": "^0.27.2",
"cookie-parser": "~1.4.4",
"debug": "~4.3.2",
Expand Down
Loading

0 comments on commit be8d3df

Please sign in to comment.