Skip to content

Simple performance stopwatch

Jon P Smith edited this page Dec 16, 2024 · 16 revisions

I often needed to know how long a process took. I do use BenchmarkDotNet, but often I want a simpler method, and My solution is TimeThings. TimeThings is very easy to use and very flexible (see next section), but its nowhere as good at BenchmarkDotNet, but it only adds ~0.3 ms to the performance test. I use TimeThings to get a "quick and dirty" performance while I'm building a new application, but if performance is important I use BenchmarkDotNet to get accurate timing, for instance I used BenchmarkDotNet in my Net.DistributedFileStoreCache.

TimeThings used in Xunit unit tests

I usually TimeThings in my Xunit unit tests, and the code below shows a basic use, but you be aware most unit tests run in parallel, which affect the time a process takes. Therefore you need to run the Xunit's test that contains a TimeThings on its own to get the correct performance timing (or if you have one unit test class that contains multiple TimeThingss, then you can run all of the unit tests in the class because each unit test is run sequentially in a test class). The code below shows a Xunit with the a TimeThings - the result performance time is sent to your Test Explorer's output panel.

public class PerfBookJsonContext
{
    private readonly ITestOutputHelper _output;
    public PerfBookJsonContext(ITestOutputHelper output) => _output = output;

    [Fact]
    public void TestWithTimeThings()
    {
        //SETUP                  
        //your test setup

        //ATTEMPT
        using (new TimeThings(output)) //The performance time it sent to your 
        {
            //your process you need to test and 
        }

        //VERIFY
        //your test checks
    }
}

TimeThings used in outside of Xunit

The TimeThings has a version which takes an Action<TimeThingResult> which is fills in the performance data when the TimeThings is disposed.

[Fact]
public void TestTimeThingResultReturn()
{
    //SETUP                  
    TimeThingResult result = null;

    //ATTEMPT
    using (new TimeThings(x => result = x))
    {
        Thread.Sleep(10);
    }

    //VERIFY
    //your test checks
}

The TimeThings's result data

  • new TimeThings(output) : returns a string showing the time, e.g., "took 1.64 ms."
  • new TimeThings(output, "Read book") : This message took

The TimeThings class has some optional parameters you can use, like message and numRuns - see below the method

/// <summary>
/// This will measure the time it took from this class being created to it being disposed and writes out to xUnit ITestOutputHelper
/// </summary>
/// <param name="output">On dispose it will write the result to the output</param>
/// <param name="message">Optional: a string to show in the result. Useful if you have multiple timing in one unit test.</param>
/// <param name="numRuns">Optional: if the timing covers multiple runs of something, then set numRuns to the number of runs and it will give you the average per run</param>
public TimeThings(ITestOutputHelper output, string message = "", int numRuns = 0)
{
   // rest of code left out

So, if you used the TimeThings class with new TimeThings(output, "read data", 500), you would get the string

500 x read data took 23.45 ms., ave. per run = 46.90 us.

The overall time is shown in ms., but if you set the numRuns parameter, then the "ave. per run" time will be "scaled" , i.e. it will look at the time and give you the time that is easier to understand. The private TimeScaled method does this.

private string TimeScaled(double timeMilliseconds)
{
    if (timeMilliseconds > 5 * 1000)
        return $"{timeMilliseconds / 1000:F3} sec.";      //Seconds
    if (timeMilliseconds > 5)
        return $"{timeMilliseconds:#,###.00} ms.";        //Milliseconds
    if (timeMilliseconds > 5 / 1000.0)
        return $"{timeMilliseconds * 1000:#,###.00} us."; //Microseconds
    return $"{timeMilliseconds * 1000_000:#,###.0} ns.";  //Nanoseconds
}

TimeThings used outside of Xunit unit tests

There is a version of the TimeThings which return a TimeThingResult which contains

Clone this wiki locally