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.

How it Works

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.

Initial App Setup

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 {}

Create a HTTP Interceptor

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
    }
  ],
  ...
})

Adding A Timestamp Header

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) => { });

Calculating and Logging HTTP Response Time

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.

Logging a Portion of all Requests

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();
      })
    );
  }
}