March 28, 2017

653 words 4 mins read

Performance Timing Made Simple

Performance Timing Made Simple

Recently I was working on an application that utilizes Azure Service Fabric and I was doing load testing before going live and I wasn't getting the performance I was expecting. Since this application can only run under the service fabric runtime, profiling is a big challenge. Tools like ANTS Performance Profiler or dotTrace can't plug in since service fabric is hiding the application so I needed to turn to a regular C# StopWatch to track down the problem. I didn't want to go into every method, create a stopwatch, start it, stop it, and log the value so I started looking for a better way to do it. I did a quick search and came across this answer from John Skeet who was accused of either not being John Skeet or voting for his own answer. I don't know John Skeet but I doubt he needs to up-vote himself as he has mad (digital) street cred. Here is what John suggested:

public static TimeSpan Time(Action action)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    action();
    stopwatch.Stop();
    return stopwatch.Elapsed;
}

This is a very simple way to wrap a code block to get a TimeSpan back. Very concise and I never would have figured it out, but I wanted to take it further to make my life easier. I wanted to be able to drop wrap some methods in have it

  • Write the timing to a log (since this was in Azure)
  • Have the log include the class and method that was being tracked automatically.

I wanted to be able to drop this into my code and not have to customize every log instance in order to know where is was coming, from so I tweaked Mr Skeet's code to be the following:

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace PerformanceDiagnotics
{
    public class PerformanceTimer
    {
        private readonly bool _enabled;
        private readonly ILogger _logger;

        public PerformanceTimer(bool enabled, ILogger logger)
        {
            _enabled = enabled;
            _logger = logger;
        }

        public void LogTiming(Action action, string customAction = "", [CallerFilePath] string file = "", [CallerMemberName] string memberName = "")
        {
            var stopwatch = new Stopwatch();
            if (_enabled)
            {
                stopwatch = Stopwatch.StartNew();
            }
            action();

            if (!_enabled) return;

            stopwatch.Stop();

            Log(fileName, memberName, customAction, stopwatch.Elapsed.TotalMilliseconds);
        }

        public async Task LogTimingAsync(Func action, string customAction = "", [CallerFilePath] string file = "", [CallerMemberName] string memberName = "")
        {
            var stopwatch = new Stopwatch();

            if (_enabled)
            {
                stopwatch = Stopwatch.StartNew();
            }

            await action();

            if (_enabled)
            {
                stopwatch.Stop();

                Log(file, memberName, customAction, stopwatch.Elapsed.TotalMilliseconds);
            }
        }

        private void Log(string file, string memberName, string customAction, double totalMilliseconds)
        {
            var split = file.Split('\\');
            var fileName = split[split.Length - 1].Replace(".cs", "");

            if (!string.IsNullOrEmpty(customAction))
            {
                _logger.LogDebug("Class: {Class}, Method: {Method}, Action: {Action}, Timing {Timing}", fileName,
                    memberName, customAction, totalMilliseconds);
            }
            else
            {
                _logger.LogDebug("Class: {Class}, Method: {Method}, Timing {Timing}", fileName, memberName,
                    totalMilliseconds);
            }
        }
    }
}

There are a couple things of note here:

  • There is one method for synchronous and one for asynchronous
  • CallerFilePath – By using the attribute it gives us the file path to use. In my case it is a 1:1 to a class which is very convenient
  • CallerMemberName – This one give us the method that the call came from
  • I included a custom action so if i wanted to have multiple timers I could add extra metadata to know what is going on
  • I added an Enabled propery to turn on/off the timing to save CPU cycles. A simple application parameter canturn it on or off
  • I used an ILogger since this was in ASP.NET Core, but any logging mechanism could be swapped in

I was happy how this turned out as I was able to drop in timing all over the application fairly quickly. Using it is pretty simple too.

Synchronous

_timing.LogTiming(() =>
{
    DeleteLoginInfo(id);
});

Asynchronous

await _timing.LogTimingAsync(async () =>
{
    await DeleteLoginInfo(id);
});

There is likely room for improvement here but this got me out of a bind and hopefully it helps someone else too!