Performance testing API’s with async/awaits

I’ve got this serverless cloud function, you can read about how I built it here. Today’s blog is how I’d go about performance testing this API using async/awaits in Typescript.

The API helper class

Here is the GET API function:

import request from 'supertest';

/**
 * Checks API is up.
 */
export async function checkMinSupportedVersionAPIReturns200(
) {
  const response = await request('https://australia-southeast1-buggybank.cloudfunctions.net/')
      .get(`min-app-version`)
      .expect(200);
  return response;
}

The performance test

Here’s the performance test function, it spins up 10 concurrent instances to hit this API over 5 seconds. And it’ll wait 30 seconds before timing out.

describe( 'performance test APIs', () => {
  it('should handle some load', async () => {
    await sendManyRequests(10, 5);
  }, 30000);

Helper function – send single request

There’s a send single request helper function, I really should have used another one with a shorter name but I’m a lazy dev.

const sendSingleRequest = async (): Promise<void> => {
    await checkMinSupportedVersionAPIReturns200();
  };

Helper function – send many requests

Next thing to add is a function that calls many requests and builds up from that single request. Inflight is used to increase the await time.

const sendManyRequests = async (maxInflightRequests:number,
      durationSeconds: number): Promise<void> => {
    let inFlight = 0;
    const startEpoch = Date.now();
    const endEpoch = startEpoch + (durationSeconds * 1000);
    while (Date.now() < endEpoch) {
      while (inFlight < maxInflightRequests) {
        inFlight++;
        sendSingleRequest()
            .then(() => {
              inFlight--;
            })
            .catch((err) => {
              inFlight--;
              console.error(`Request error: ${err}`);
              errorCount++;
            });
      }
      await delay(10);
    }
    while (inFlight > 0) {
      await delay(10);
    }
  };

Await Delay

await delay introduces a new promise and will add a wait of 10 milliseconds in my previous function.

export const delay = (ms: number): Promise<void> => {
  return new Promise((resolve) => setTimeout(resolve, ms));
};

Clean up and logging

I then add some logs and some output to report on my performance testing results.

    const duration = (Date.now() - startEpoch) / 1000.0;

    console.info(`Requests: ${requestCount}`);
    console.info(`Errors: ${errorCount}`);
    console.info(`Responses: ${responseCount}`);
    console.info(`Duration: ${duration}s`);
    console.info(`TPS: ${requestCount / duration}`);

Here is the output of my testing (TPS stands for transactions per second):

Requests: 1451
Errors: 0
Responses: 1451
Duration: 11.22s
TPS: 129.32263814616755

Checking out the code

You can check out this branch if you are interested, though please don’t run this against my own API. I’ll get an expensive cloud bill if you do and then I’ll have to switch off the cloud function.

There is more to performance testing but I’ve kept this brief. I really should pull out the performance test in its own folder seperate from smoke tests. And I can enhance the tests such that it would perform up to a required TPS.

This test is a building block and will fail once your API starts failing too.

3 comments

Leave a Reply