Skip to content

Commit

Permalink
Merge pull request #11 from nishgowda/responseTimes-updates
Browse files Browse the repository at this point in the history
adding response times and updating readme
  • Loading branch information
nishgowda authored Sep 22, 2020
2 parents 0b8cbe7 + 0c34868 commit 0ca61e5
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 36 deletions.
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ Fast and light weight api and endpoint monitoring backed by Redis and carefully
* An object is made in redis that is defined as an **event**. An **event** is a collection of the route, the method, the status code, the number of requests, and date and hour (depending on what key is specified).

* Redis stores the data per request into three sepreate keys:
- "total" - the total number of requests per event (date and hour are not included here)
- "daily" - the total number of requests per event per day
- "hourly" - the total number of requests per event per hour of every day.
1. "total" - the total number of requests per event (date and hour are not included here)
2. "daily" - the total number of requests per event per day
3. "hourly" - the total number of requests per event per hour of every day.
4. "response-times" - the time in milliseconds each event takes per request.


## Install
```
npm install aegis-net
$ npm install aegis-net
```

## Usage:
Expand All @@ -40,9 +42,9 @@ app.use((req, res, next) => {
```javascript
app.get('/api/users', (_, res) => {
// note: Redis will store the data as a JSON string
// so it's important you parse to work with it.
// so it's important you parse after you retrueve itto work with it.
client.get('total', (err, stats) => {
res.status(200).send(JSON.parse(stats));
res.status(200).send(stats);
});
});

Expand All @@ -59,9 +61,12 @@ app.get('/api/users', (_, res) => {

```
``` JSON
[ { "method": "PUT", "route": "/api/users", "statusCode": 200, "requests": 2, "date": "9/20/2020", "hour": "12" }]
[ { "method": "GET", "route": "/api/users", "statusCode": 304, "requests": 2, "date": "9/20/2020", "hour": "12" }]

```
#### Side Notes:
- It is very important you initialize the listen middleware before any of your routes or custom middleware. If you don't do this, you may find some unkown error. Further testing with it is required.
- Any unkown request sent to the server will send the event with route of "unkown route".



Expand Down
15 changes: 11 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "aegis-net",
"version": "1.0.3",
"description": "",
"main": "dist/index.js",
"main": "index.js",
"scripts": {
"test": "jest --forceExit --config jestconfig.json",
"build": "tsc",
Expand All @@ -28,7 +28,9 @@
"url": "git+https://github.com/nishgowda/Aegis.git"
},
"keywords": [
"AegisNet"
"AegisNet",
"Api Monitoring",
"Api protection"
],
"author": "Nish Gowda",
"license": "MIT",
Expand All @@ -53,8 +55,6 @@
"typescript": "^4.0.3"
},
"dependencies": {
"@types/redis-mock": "^0.17.0",
"async-redis": "^1.1.7",
"ioredis-mock": "^4.21.3"
"async-redis": "^1.1.7"
}
}
11 changes: 5 additions & 6 deletions src/__tests__/app.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import fs from 'fs'
import path from 'path'
const dir = path.resolve(__dirname, '.././mocks/data.json')
import fs from 'fs';
import path from 'path';
const dir = path.resolve(__dirname, '.././mocks/data.json');
var obj = JSON.parse(fs.readFileSync(dir, 'utf8'));
// Check redis before running. Update on change
// Check redis before running. Update on change

test('Aegis endpoint tests', async () => {
try {
expect(obj).toEqual([ { "method": "GET", "route": "/api/users", "statusCode": 200, "requests": 10 }])
expect(obj).toEqual([{ method: 'GET', route: '/api/users', statusCode: 200, requests: 10 }]);
} catch (error) {
throw error;
}

});
62 changes: 49 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { NextFunction, Response, Request } from 'express';
export class AegisNet {
private connectionString;
protected client: any;
private responseTime: number;
constructor(connectionString: string) {
this.connectionString = connectionString;
this.client = redis.createClient(this.connectionString);
}

/*
Fetches the endpoint of url in custom form of:
Fetches the endpoint url in custom form of:
/{endpoint}/
*/
private fetchRoute = (req: Request) => {
Expand Down Expand Up @@ -45,6 +46,9 @@ export class AegisNet {
case 'hourly':
this.client.set(key, JSON.stringify(stats));
break;
case 'response-times':
this.client.set(key, JSON.stringify(stats));
break;
default:
break;
}
Expand All @@ -63,23 +67,59 @@ export class AegisNet {
return newDate;
};

// Helper to retrieve the hour in local time of each request
private returnHour = () => {
const dateObj = new Date();
const hour = dateObj.getHours();
return `${hour}`;
}
};
// Helper to retrieve the response time in milliseconds for each request sent to express server.
private getResponseTime = (start: any) => {
const NS_PER_SEC = 1e9;
const NS_TO_MS = 1e6;
const diff = process.hrtime(start);
return (diff[0] * NS_PER_SEC + diff[1]) / NS_TO_MS;
};
/*
Active listener to be used as middleware with express
every time an endpoint is hit we update the key with called endpoints and stats
*/
listen = async (req: Request, res: Response, next: NextFunction) => {
const start = process.hrtime();
res.on('finish', () => {
this.responseTime = this.getResponseTime(start);
this.fetchDailyStats(req, res);
this.fetchHourlyStats(req, res);
this.fetchTotalStats(req, res);
this.fetchResponseTimes(req, res);
});
next();
};
// Fetch the event and the the response time of each event.
private fetchResponseTimes = async (req: Request, res: Response) => {
try {
this.getStats('response-times')
.then((response) => {
let myStats: Stats[];
myStats = (response as Stats[]) || []; // If repsonse is null create an empty object
const event: Event = {
method: req.method,
route: this.fetchRoute(req),
statusCode: res.statusCode,
date: this.returnDateFull(),
hour: this.returnHour(),
responseTime: this.responseTime,
};
myStats.push(event);
this.dumpStats(myStats, 'response-times');
})
.catch((err) => {
throw err;
});
} catch (error) {
throw error;
}
};

// Fetches the number of events hit per day
private fetchDailyStats = async (req: Request, res: Response) => {
Expand All @@ -88,12 +128,11 @@ export class AegisNet {
.then((response) => {
let myStats: Stats[];
myStats = (response as Stats[]) || []; // If repsonse is null create an empty object
const newDate = this.returnDateFull();
const event: Event = {
method: req.method,
route: this.fetchRoute(req),
statusCode: res.statusCode,
date: newDate,
date: this.returnDateFull(),
};
if (response) {
if (
Expand Down Expand Up @@ -136,20 +175,18 @@ export class AegisNet {
throw error;
}
};
private fetchHourlyStats = async (req: Request, res: Response) => {
private fetchHourlyStats = async (req: Request, res: Response) => {
try {
this.getStats('hourly')
.then((response) => {
let myStats: Stats[];
const newDate = this.returnDateFull();
const hour = this.returnHour();
myStats = (response as Stats[]) || []; // If repsonse is null create an empty object
const event: Event = {
method: req.method,
route: this.fetchRoute(req),
statusCode: res.statusCode,
date: newDate,
hour: hour
date: this.returnDateFull(),
hour: this.returnHour(),
};
if (response) {
if (
Expand Down Expand Up @@ -190,11 +227,10 @@ export class AegisNet {
.catch((err) => {
throw err;
});

}catch(error) {
} catch (error) {
throw error;
}
}
}
};

// Fethces the total number of requests for each event hit
private fetchTotalStats = async (req: Request, res: Response) => {
Expand Down
23 changes: 23 additions & 0 deletions src/mocks/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import express from 'express';
import { AegisNet } from '../index';
import redis from 'redis';
const app = express();
app.use(express.json());

const connectionString = 'redis://127.0.0.1:6379';
const aegis = new AegisNet(connectionString);
const client = redis.createClient(connectionString);
app.use((req, res, next) => {
aegis.listen(req, res, next);
});

app.get('/api/users', (_, res) => {
// note: Redis will store the data as a JSON string
// so it's important you parse to work with it.
client.get('response-times', (err, stats) => {
if (err) throw err;
res.status(200).send(stats);
});
});

app.listen(5000, () => 'Listening');
1 change: 1 addition & 0 deletions src/types/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export type Event = {
date?: string;
hour?: string;
requests?: number;
responseTime?: number;
};
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@
"baseUrl": ".",
"allowJs": true
},
"exclude": ["node_modules", "**/__tests__/*"],
"exclude": ["node_modules", "**/__tests__/*", "**/mocks/*"],
"include": ["src"],
}

0 comments on commit 0ca61e5

Please sign in to comment.