Thank you for reading my blog posts, I am no longer publishing new content on this platform. You can find my latest content on either mainawycliffe.dev or All Things Typescript Newsletter (✉️)
Most API and Backend Services have a metric service to measure to measure request response times. The one downside of this is, it doesn’t consider network latency, or how the device capability is affecting the performance of your web app. This is important, since a lot of the people access your web apps using low end mobile devices.
Collecting such telemetry data is vital, as it can help you fine tune the performance of your web app. In this post, we will look at how you can measure the time it takes to send and receive HTTP responses, from the frontend. Once you have access to the data, you can then send it to a logging server.
To make this information useful for analyses in future, we will also be collecting browser, operating system and any device information we can get. Remember to update your apps terms of use and privacy policy accordingly.
We are going to be using a HTTP Interceptor to intercept both outgoing requests and incoming responses. Then, we will be adding a timestamp for outgoing requests and then read it on responses. And then, we are going to find a difference between the current timestamp and the one for outgoing request.
We will then take that information, together with device information and send it to a logging server. To collect device data, we are going to be using ngx-device-detector library. This will give us information such as OS, Browser, User Agent etc. So, without further ado.
First, we need to create a new angular application:
$ ng new ng-measure-http-response-times
Next, we will install our app dependency, using your favorite package manager:
$ yarn add ngx-device-detector
$ npm install ngx-device-detector
And then, import both DeviceDetectorModule
and HttpClientModule
in your application module.
...
import { HttpClientModule } from '@angular/common/http';
import { DeviceDetectorModule } from 'ngx-device-detector';
...
@NgModule({
...
imports: [
...
HttpClientModule,
DeviceDetectorModule.forRoot()
...
],
...
})
export class AppModule {}
Next up, let’s create a HTTP Interceptor. You can do this by generating a class using angular cli and then adding support for dependency injection.
$ ng g class http-response-time-logger
Then implement http interceptor interface, add dependency injection support and an intercept method for our class:
...
@Injectable()
export class HttpResponseTimeLogger implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { ... }
}
Next, we need to inject DeviceDetectorService
to our class, so we can use it within our intercept method:
constructor(private deviceService: DeviceDetectorService) {}
And then register our interceptor class in the app module, list of providers:
@NgModule({
...
providers: [
...
{
provide: HTTP_INTERCEPTORS,
useClass: HttpResponseTimeLogger,
multi: true
}
],
...
})
Now, we need to get the current timestamp header when the request is being sent. Our timestamp will be in millisecond for some level of accuracy.
const startTimestamp = new Date().getTime();
Then, we need to clone our request and add the timestamp header:
const newReq = req.clone({
headers: req.headers.set('startTimestamp', startTimestamp.toString())
});
The idea here is, you will get the header from the request on the server and return it with the response. You can use a middleware to automatically retrieve the header and attach it to the response. Next, return the newly cloned request and tap to the response.
return next.handle(newReq).pipe(
tap((res: Response) => { });
Then, inside our tap method, we need to read the timestamp from the response header and get the current timestamp in millisecond, then finally get the difference.
// another timestamp
const endTimestamp: number = new Date().getTime();
const startTimestamp2: number = Number(res.headers.get('startTimestamp'));
// get the difference
const responseTimes = endTimestamp - startTimestamp2;
NB: You don’t have to get the difference inside your http interceptor. You can also log the start and end timestamps and let the server handle the calculation for you.
Next up, we need to get the device information using the DeviceDetectorService
method.
const deviceInfo = this.deviceService.getDeviceInfo();
Now we have everything we need to log the data. One option would be to send the data over to the logging server directly.
this.http
.post('URL HERE', {
startTime: startTimestamp2,
endTime: endTimestamp,
difference: difference,
deviceInfo: JSON.stringify(deviceInfo) // decode the data on the server
})
.subscribe();
NB: You can also store the logs locally and collect them periodically on the background.
You can also decide instead of logging all requests, to log some of the requests. One way you can achieve this, is by generating a random number between 1 and 6. And then check if the number is greater than 3. If the number is greater than 3, don’t log the request, otherwise log the request. At the beginning of the intercept method, we need to add the following code:
const random = Math.floor(Math.random() * 6) + 1;
// if number is less than 3 skip logging
if (random > 3) {
return next.handle(req);
}
If everything works perfectly, you will be logging three in every six requests, about 50 % of the requests at random. So, our final class should look like this:
@Injectable()
export class HttpResponseTimeLogger implements HttpInterceptor {
constructor(
private deviceService: DeviceDetectorService,
private http: HttpClient
) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const random = Math.floor(Math.random() * 6) + 1;
// if number is less 3 skip logging
if (random > 3) {
return next.handle(req);
}
// get timestamp
const startTimestamp = new Date().getTime();
const newReq = req.clone({
headers: req.headers.set('startTimestamp', startTimestamp.toString())
});
return next.handle(newReq).pipe(
tap((res: Response) => {
// another timestamp
const endTimestamp: number = new Date().getTime();
const startTimestamp2: number = Number(
res.headers.get('startTimestamp')
);
// get the difference
const responseTimes = endTimestamp - startTimestamp2;
// get browser information
const deviceInfo = this.deviceService.getDeviceInfo();
// send the data to the server
this.http
.post('URL HERE', {
startTime: startTimestamp2,
endTime: endTimestamp,
deviceInfo: JSON.stringify(deviceInfo)
})
.subscribe();
})
);
}
}
One of the least talked about features of Angular 6 is Angular CLI Workspaces. Workspaces or Angular CLI Workspaces give angular developers …
Read MorengFor is directive that iterates through a list of data and instantiates a template for each item in the list. It does an amazing job when …
Read MoreBasically, a service worker intercepts all outgoing http requests sent by your application on the browser. You can choose which request to …
Read MoreAngular Component Development Kit (CDK) is a set of un-opinionated developers’ tools to add common interaction behavior to your angular UI …
Read MoreIn this post, I am going to cover Angular CLI Budgets in details. So, what are CLI Budgets? CLI Budgets is a way of way of ensuring that …
Read MoreIn web development or any job in general, tools are very vital for you to do your job right. Tools help improve productivity, reduce …
Read More