Extending MVC Framework: Adding Performance Metrics

Today, with the whole world relying on web services and with so much data and load passing through production servers, performance metrics can provide developers with crucial information. Tracking your application’s performance in the production environment has many benefits:

  1. You get real data regarding how your application behaves in production.

  2. If you have performance problems in production, you will be able to see exactly where your application failed and pinpoint the problem.

  3. You can see exactly which functions and services are most frequently used by your users, and at which times of day.

In choosing a solution for tracking performance metrics, I had two criteria:

  1. That it be easily applied across a project, without changes to controllers or functions.

  2. That it be easily extendable, letting us gather a hit count and avarage execution time data, write it to different systems, and add other statistical data in the future.

The Solution

The ASP.NET MVC framework has a feature called ActionFilters, which can be used to decorate actions easily with any kind of functionality and serve as MVC’s AOP framework. Using ActionFilters is perfect for our solution, since it supplies us a point of extensibility both before and after an action is invoked, while providing all of the essential information about the action.

The plan is to have an action filter applied across the controllers, which will execute all performance trackers for every call to the controller (we will talk about this shortly).

We will implement our own ActionFilter:

public class PerformanceTrackerActionFilter : ActionFilterAttribute  
{
    private readonly ILogger _logger;
    Private readonly IEnumerable<IPerformanceTracker> _trackers;

    public PerformanceTrackerActionFilter(IEnumerable<IPerformanceTracker> trackers,ILogger logger)
    {
       Guard.AgainstNull(() => trackers);
       Guard.AgainstNull(() => logger);

      _trackers = trackers;
      _logger = logger;
    }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        try
        {
            _trackers.ForEach(pt => pt.OnActionStart(actionContext));
        }
        catch (Exception ex)
        {
            _logger.Warn(ex);
        }
    }

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        try
        {
            _trackers.ForEach(pt => pt.AfterActionExecuted(actionExecutedContext));
        }
        catch (Exception ex)
        {
            _logger.Warn(ex);
        }
    }
}

The filter itself does a very simple job: When applied, it forwards the action context to all the performance trackers that it received upon initialization. This is where extensibility comes in. We resolve all instances of IPerformanceTracker that are registered in our container (Castle Windsor, in this case) and pass them on when initiating our actionFilter implementation. These trackers can also use any registered service/component from the container, since they are resolved by it. We can implement any kind of tracker that writes to any kind of system, and the only requirement is to register it (via XML or an installer).
And there you have it: a new tracking method automatically added across all tracked controllers/methods in your service.

Initialization code for registering the action filter to be applied across all controllers looks like this in our case:

var counters = _container.ResolveAll<IPerformanceTracker>();  
GlobalConfiguration.Configuration.Filters.Add(new PerformanceTrackerActionFilter(counters));  

An example of a performance tracker would be:

public class TimerTracker : IPerformanceTracker  
{
    private const string TIMER_KEY = "__action_timer__";

    private readonly StatsdActivityWriter.StatsdActivityWriter _writer;

    public TimerTracker(StatsdActivityWriter.StatsdActivityWriter writer)
    {
        _writer = writer;
    }

    public void OnActionStart(HttpActionContext actionContext)
    {
        var stopWatch = new Stopwatch();
        stopWatch.Start();
        actionContext.Request.Properties[TIMER_KEY] = stopWatch;
    }

    public void AfterActionExecuted(HttpActionExecutedContext actionContext)
    {
        var stopWatch = actionContext.Request.Properties[TIMER_KEY] as Stopwatch;
        if (stopWatch != null)
        {
            stopWatch.Stop();
            var info = actionContext.ActionContext.GetActionInfo();
            _writer.WriteActivity(new TimerActivity
            {
                ActivityName = PerformanceTrackerConfig.ServiceName,
                TimerName = info.GetActionName(),
                Value = stopWatch.Elapsed
            });   
        }
    }
}

This tracker measures the execution time of each method on a controller using a stopwatch and then reports a formatted method name and the time it took to execute it to Statsd.

Summary

In this article, we have demonstrated a solution that enabled us to easily add various performance trackers across our Web API services.

We can visualize our Statsd performance metrics later on in Graphite:
Statsd Example

This solution is lightweight, utilizes common framework components, and is extendable. I hope it helps you in your own project!