Among the most common - and important - needs during software development certainly is carrying out each task while achieving the best possible performance. In order to do this is essential to adopt a proper tool that will allow us to measure the execution time of each method, function or code block in a suitable time unit (be it milliseconds, microseconds or nanoseconds) with a decent amount of accuracy.
Ideally, such tool should also let the developer execute several measurements within the same execution context, allowing him to set multiple time laps and giving the chance to compare them with each other: to cut things short, we are talking about something that provides the core functionality of a common stopwatch.
With this goal in mind, a few years ago I developed a simple C# class that basically puts this concept into life. Its name is TimeTracker and it can be used to set multiple time laps during the execution of any program: each one of them, once created, can be compared with any other lap to calculate the time lapse between them using one the available time formats: nanoseconds, microseconds, milliseconds, seconds, minutes or hours.
This is a basic usage example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public ActionResult MyActionMethod() { TimeTracker tt = new TimeTracker("start"); // todo: insert some time-intensive piece of code here tt.AddLap("myLap1"); // todo: insert another time-intensive piece of code here tt.AddLap("myLap2"); // todo: insert yet another time-intensive piece of code here tt.AddLap("myLap3"); // store the timings to ViewBag ViewBag.myLap1Result = "myLap1 completed after " + GetLapTimeDiff("start", "myLap1") + " ms"; ViewBag.myLap2Result = "myLap2 completed after " + GetLapTimeDiff("start", "myLap2") + " ms"; ViewBag.myLap3Result = "myLap3 completed after " + GetLapTimeDiff("start", "myLap3") + " ms"; return View(); } |
The sample is taken from a standard ASP.NET MVC application, yet the class can be effectively used within any other possible C# context: ASP.NET MVC, Windows Forms, WFC, ASP.NET Core and the likes. It's also entirely possible to run global-scope measurements: we just need to instantiate a single TimeTracker object globally - for example in the Startup class of a standard ASP.NET 4 / ASP.NET Core project - and add multiple time laps from different points and / or along the entire application life cycle.
This is the class full source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
using System; using System.Collections.Generic; namespace Ryadel.Components.Diagnostics { /// <summary> /// A small object to measure the time between two or more operations. /// </summary> public class TimeTracker { #region Nested Types public enum TimeFormat { Nanoseconds, Microseconds, Milliseconds, Seconds, Minutes, Hours } #endregion Nested Types #region Private Fields private Dictionary<string, long> m_TrackList; #endregion Private Fields #region Constructor public TimeTracker() : this(null) { } public TimeTracker(string lapName) { if (TrackList == null) m_TrackList = new Dictionary<string, long>(); if (!string.IsNullOrEmpty(lapName)) AddLap(lapName); } #endregion Constructor #region Methods /// <summary> /// Adds a lap to the TimeTracker /// </summary> /// <param name="lapName"></param> public void AddLap(string lapName) { TrackList[lapName] = GetCurrentTime(); } /// <summary> /// Gets the TimeDiff between the two given laps in time units specified by the given [TimeTracker.TimeFormat] /// </summary> /// <param name="startLap">name of the starting lap</param> /// <param name="endLap">name of the ending lap</param> /// <param name="format">a valid [TimeTracker.TimeFormat] enum value</param> /// <returns>a decimal representing the TimeDiff in time units specified by the given [TimeTracker.TimeFormat].</returns> public decimal GetLapTimeDiff(string startLap, string endLap, TimeFormat format = TimeFormat.Milliseconds) { if (!TrackList.ContainsKey(startLap)) throw new Exception(String.Format("Starting Lap '{0}' cannot be found.", startLap)); if (!TrackList.ContainsKey(endLap)) throw new Exception(String.Format("Ending Lap '{0}' cannot be found.", endLap)); return CalculateTime(TrackList[endLap] - TrackList[startLap], format); } protected virtual decimal CalculateTime(long tDiff, TimeFormat format = TimeFormat.Milliseconds) { switch (format) { case TimeFormat.Nanoseconds: return (decimal)(tDiff * 100); case TimeFormat.Microseconds: return (decimal)(tDiff / (long)10); case TimeFormat.Milliseconds: default: return (decimal)(tDiff / (long)10000); case TimeFormat.Seconds: return (decimal)(tDiff / (long)10000000); case TimeFormat.Minutes: return (decimal)(tDiff / (long)600000000); case TimeFormat.Hours: return (decimal)(tDiff / (long)36000000000); } } /// <summary> /// Protected (overridable) method to get the current lap time. /// </summary> /// <returns></returns> protected virtual long GetCurrentTime() { return DateTime.UtcNow.ToFileTimeUtc(); } /// <summary> /// Protected method to check if given lap name exists. /// </summary> /// <param name="lapNames"></param> protected void CheckLapNames(params string[] lapNames) { foreach (string n in lapNames) if (!TrackList.ContainsKey(n)) throw new Exception(String.Format("Lap '{0}' cannot be found.", n)); } #endregion Methods #region Properties /// <summary> /// Returns TRUE if the TimeTracker's TrackList is Empty, FALSE otherwise. /// </summary> public bool IsEmpty { get { return (TrackList == null || TrackList.Count == 0); } } /// <summary> /// Dictionary containing the laps (if any) /// </summary> protected Dictionary<string,long> TrackList { get { return m_TrackList; } } #endregion Properties } } |
The code itself is quite documented and filled with comments, so it should need no further explanations. Despite being written several years ago, the class can still prove its usefulness to properly analyze the performances of almost any kind of C# source code block in a rather accurate way.
That's it for now: happy Time Tracking!