using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; namespace Unosquare.Swan.Components { /// /// A simple benchmarking class. /// /// /// The following code demonstrates how to create a simple benchmark. /// /// namespace Examples.Benchmark.Simple /// { /// using Unosquare.Swan.Components; /// /// public class SimpleBenchmark /// { /// public static void Main() /// { /// using (Benchmark.Start("Test")) /// { /// // do some logic in here /// } /// /// // dump results into a string /// var results = Benchmark.Dump(); /// } /// } /// /// } /// /// public static class Benchmark { private static readonly Object SyncLock = new Object(); private static readonly Dictionary> Measures = new Dictionary>(); /// /// Starts measuring with the given identifier. /// /// The identifier. /// A disposable object that when disposed, adds a benchmark result. public static IDisposable Start(String identifier) => new BenchmarkUnit(identifier); /// /// Outputs the benchmark statistics. /// /// A string containing human-readable statistics. public static String Dump() { StringBuilder builder = new StringBuilder(); lock(SyncLock) { foreach(KeyValuePair> kvp in Measures) { _ = builder.Append($"BID: {kvp.Key,-30} | ") .Append($"CNT: {kvp.Value.Count,6} | ") .Append($"AVG: {kvp.Value.Average(t => t.TotalMilliseconds),8:0.000} ms. | ") .Append($"MAX: {kvp.Value.Max(t => t.TotalMilliseconds),8:0.000} ms. | ") .Append($"MIN: {kvp.Value.Min(t => t.TotalMilliseconds),8:0.000} ms. | ") .Append(Environment.NewLine); } } return builder.ToString().TrimEnd(); } /// /// Adds the specified result to the given identifier. /// /// The identifier. /// The elapsed. private static void Add(String identifier, TimeSpan elapsed) { lock(SyncLock) { if(Measures.ContainsKey(identifier) == false) { Measures[identifier] = new List(1024 * 1024); } Measures[identifier].Add(elapsed); } } /// /// Represents a disposable benchmark unit. /// /// private sealed class BenchmarkUnit : IDisposable { private readonly String _identifier; private Boolean _isDisposed; // To detect redundant calls private Stopwatch _stopwatch = new Stopwatch(); /// /// Initializes a new instance of the class. /// /// The identifier. public BenchmarkUnit(String identifier) { this._identifier = identifier; this._stopwatch.Start(); } /// public void Dispose() => this.Dispose(true); /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. private void Dispose(Boolean alsoManaged) { if(this._isDisposed) { return; } if(alsoManaged) { Add(this._identifier, this._stopwatch.Elapsed); this._stopwatch?.Stop(); } this._stopwatch = null; this._isDisposed = true; } } } }