Measuring And Reporting The Response Time Of An ASP.NET Core API

Introduction

Performance is a buzzword for APIs. One of the most important and measurable parameters of the API performance is the response time. In this article, we will understand how to add code to measure the response time of an API and then return the response time data to the end client.

Measuring and Reporting the Response time of an Asp.Net Core API

What is the need for this?

So, let's take a moment to think why we would ever need such a feature to measure the Response time of an API. Following are some of the points that have been the inspiration for writing code to Capture response time.

  1. You need to define the SLA (Service Level Agreements) for your API with your clients. The clients need to understand how much time  the API takes to respond back. The response time data over time can help us decide on an SLA for our API.

  2. Management is interested in reports as to how fast or slow the application is. You need to have data to corroborate your claims. It is worth it to have reports on the performance of the application and to share it with Stakeholders.

  3. The client needs to have the information of the Response time of the API so that they can track how much time is spent on the client and the Server.

You might also have encountered similar requests in your project and it is worthwhile looking at an approach to capture the response time for the API.

Where to add the code?

Let's explore a couple of approaches to capture the response time of our API focusing mostly on capturing the time spent in our API. Our objective is to calculate the time elapsed in milliseconds from the time the request is received by the Asp.net core runtime to the time the response is processed and sent back from the Server.

What factors are we ignoring?

It's important to understand that this discussion doesn't include the time spent in N/W, Time spent in IIS and Application Pool Startup. If the Application Pool wasn't up and running, then the first request can affect the overall response time of the API. There is an Application Initialization Module which we can make use of but that is out of scope for this article.

First Attempt

One very naive approach to capturing the response time of an API would be to add code to every API method at the start and end and then measure the delta to calculate the response time as shown below.

  1. // GET api/values/5   
  2. [HttpGet]  
  3. public IActionResult Get() {  
  4.     // Start the watch   
  5.     var watch = new Stopwatch();  
  6.     watch.Start();  
  7.     // Your actual Business logic   
  8.     // End the watch  
  9.     watch.Stop();  
  10.     var responseTimeForCompleteRequest = watch.ElapsedMilliseconds;  
  11. }  

This code should be able to calculate the time spent in an operation. But this doesn't seem to be the right approach for the following reasons.

  • If an API has a lot of operations, then we need to add this code to multiple places which are not good for maintainability.
  • This code measures the time spent in the method only, it doesn't measure the time spent on other activities like middleware, filters, Controller selection, action method selection, Model binding etc.
Second Attempt

Let's try to improve the above code by centralizing the code in one place so that it is easier to maintain. We need to execute the response time calculation code before and after a method is getting executed. If you have worked with earlier versions of Asp.net Web API, you would be familiar with concepts of Filter. Filters allow you to run code before or after specific stages in the request processing pipeline.

We will implement a filter for calculating the Response time as shown below. We will create a Filter and use the OnActionExecuting to start the timer and then stop the timer in method OnActionExecuted, thus calculating the response time of the API.

  1. public class ResponseTimeActionFilter: IActionFilter {  
  2.     private const string ResponseTimeKey = "ResponseTimeKey";  
  3.     public void OnActionExecuting(ActionExecutingContext context) {  
  4.         // Start the timer   
  5.         context.HttpContext.Items[ResponseTimeKey] = Stopwatch.StartNew();  
  6.     }  
  7.     public void OnActionExecuted(ActionExecutedContext context) {  
  8.         Stopwatch stopwatch = (Stopwatch) context.HttpContext.Items[ResponseTimeKey];  
  9.         // Calculate the time elapsed   
  10.         var timeElapsed = stopwatch.Elapsed;  
  11.     }  
  12. }  

This code is not a reliable technique for calculating the response time as it doesn't address the issue of calculating the time spent in execution of middleware, controller selection, action method selection, model binding etc. The filter pipeline runs after the MVC selects the action to execute. So, it effectively doesn't instrument the time spent in the Other Asp.net pipeline.

Third Attempt

We will use the Asp.net Core Middleware to Calculate the Response time of the API.

So, what is Middleware?

Basically, Middleware are software components which handle the Request/Response. Middleware is assembled into an application pipeline and serves in the incoming request. Each component does the following operations.

  • Chooses whether to pass the request to the next component in the pipeline. 
  • Can perform work before and after the next component in the pipeline is invoked.

 

If you have worked with HTTPModules or HTTPHandlers in ASP.NET, then you can think of Middleware as a replacement in ASP.NET Core. Some of the examples of middleware are -

  • MVC Middleware
  • Authentication
  • Static File Serving
  • Caching
  • CORS

Measuring and Reporting the Response time of an Asp.Net Core API

We want to add code to start the timer once the request enters the ASP.NET Core pipeline and stop the timer once the response is processed by the Pipeline. Custom Middleware at the start of the request pipeline seems to be the best approach for getting the access to the request as early as possible and access until the last step is executed in the pipeline.

We will build a Response Time Middleware which we will add as the first Middleware to the request Pipeline so that we can start the timer as soon the request enters the Asp.net core pipeline.

What to do with the Response time data?

Once we capture the response time data we can process data in the following ways.

  1. Add the Response time data to a Reporting database or an analytics solution.
  2. Write the Response time data to a log file.
  3. Pass the response time data to a message queue which can further be processed by another application for reporting and analytics.
  4. Send the Response time information to the client applications consuming our Rest API using the Response headers.

There may be other useful ways of using the response time data. Please leave a comment and tell me how you process the response time data in your application.

Let's write the code

We will write the code considering the following points.

  1. Calculating the response time data for the API
  2. Reporting the data back to client applications by passing the data in the Response headers.

Full code snippet for the ResponseTimeMiddleware is shown below.

  1. public class ResponseTimeMiddleware {  
  2.     // Name of the Response Header, Custom Headers starts with "X-"  
  3.     private const string RESPONSE_HEADER_RESPONSE_TIME = "X-Response-Time-ms";  
  4.     // Handle to the next Middleware in the pipeline  
  5.     private readonly RequestDelegate _next;  
  6.     public ResponseTimeMiddleware(RequestDelegate next) {  
  7.         _next = next;  
  8.     }  
  9.     public Task InvokeAsync(HttpContext context) {  
  10.         // Start the Timer using Stopwatch  
  11.         var watch = new Stopwatch();  
  12.         watch.Start();  
  13.         context.Response.OnStarting(() => {  
  14.             // Stop the timer information and calculate the time   
  15.             watch.Stop();  
  16.             var responseTimeForCompleteRequest = watch.ElapsedMilliseconds;  
  17.             // Add the Response time information in the Response headers.   
  18.             context.Response.Headers[RESPONSE_HEADER_RESPONSE_TIME] = responseTimeForCompleteRequest.ToString();  
  19.             return Task.CompletedTask;  
  20.         });  
  21.         // Call the next delegate/middleware in the pipeline   
  22.         return this._next(context);  
  23.     }  
  24. }  
Explanation of the code

The interesting part happens in the InvokeAsync method, We use Stopwatch class to start the stopwatch once the requests enter into the first middleware of the request and then stop the stopwatch once the request has been processed and the response is ready to be sent back to the client. OnStarting method provides an opportunity to write a custom code to add a delegate to be invoked just before response headers will be sent to the client.

Lastly, we add the Response time information in a Custom Header. We use the X-Response-Time-ms header as a Response Header. As a convention, the Custom Header starts with an X.

Conclusion

In this article, we understood how to leverage ASP.NET middleware to manage cross-cutting concerns like measuring the response time of the APIs. There are various other useful use cases of using middleware which can help to reuse code and improve the maintainability of the application.