-
-
Save MarkPflug/55173728458020c6d335cc099c891c0b to your computer and use it in GitHub Desktop.
using BenchmarkDotNet.Analysers; | |
using BenchmarkDotNet.Columns; | |
using BenchmarkDotNet.Configs; | |
using BenchmarkDotNet.Diagnosers; | |
using BenchmarkDotNet.Engines; | |
using BenchmarkDotNet.Exporters; | |
using BenchmarkDotNet.Loggers; | |
using BenchmarkDotNet.Reports; | |
using BenchmarkDotNet.Running; | |
using BenchmarkDotNet.Validators; | |
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
namespace Benchmarks | |
{ | |
public class CpuDiagnoserAttribute : Attribute, IConfigSource | |
{ | |
public IConfig Config { get; } | |
public CpuDiagnoserAttribute() | |
{ | |
Config = ManualConfig.CreateEmpty().AddDiagnoser(new CpuDiagnoser()); | |
} | |
} | |
public class CpuDiagnoser : IDiagnoser | |
{ | |
Process proc; | |
public CpuDiagnoser() | |
{ | |
this.proc = Process.GetCurrentProcess(); | |
} | |
public IEnumerable<string> Ids => new[] { "CPU" }; | |
public IEnumerable<IExporter> Exporters => Array.Empty<IExporter>(); | |
public IEnumerable<IAnalyser> Analysers => Array.Empty<IAnalyser>(); | |
public void DisplayResults(ILogger logger) | |
{ | |
} | |
public RunMode GetRunMode(BenchmarkCase benchmarkCase) | |
{ | |
return RunMode.NoOverhead; | |
} | |
long userStart, userEnd; | |
long privStart, privEnd; | |
public void Handle(HostSignal signal, DiagnoserActionParameters parameters) | |
{ | |
if(signal == HostSignal.BeforeActualRun) | |
{ | |
userStart = proc.UserProcessorTime.Ticks; | |
privStart = proc.PrivilegedProcessorTime.Ticks; | |
} | |
if(signal == HostSignal.AfterActualRun) | |
{ | |
userEnd = proc.UserProcessorTime.Ticks; | |
privEnd = proc.PrivilegedProcessorTime.Ticks; | |
} | |
} | |
public IEnumerable<Metric> ProcessResults(DiagnoserResults results) | |
{ | |
yield return new Metric(CpuUserMetricDescriptor.Instance, (userEnd - userStart) * 100d / results.TotalOperations); | |
yield return new Metric(CpuPrivilegedMetricDescriptor.Instance, (privEnd - privStart) * 100d / results.TotalOperations); | |
} | |
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters) | |
{ | |
yield break; | |
} | |
class CpuUserMetricDescriptor : IMetricDescriptor | |
{ | |
internal static readonly IMetricDescriptor Instance = new CpuUserMetricDescriptor(); | |
public string Id => "CPU User Time"; | |
public string DisplayName => Id; | |
public string Legend => Id; | |
public string NumberFormat => "0.##"; | |
public UnitType UnitType => UnitType.Time; | |
public string Unit => "ns"; | |
public bool TheGreaterTheBetter => false; | |
public int PriorityInCategory => 1; | |
} | |
class CpuPrivilegedMetricDescriptor : IMetricDescriptor | |
{ | |
internal static readonly IMetricDescriptor Instance = new CpuPrivilegedMetricDescriptor(); | |
public string Id => "CPU Privileged Time"; | |
public string DisplayName => Id; | |
public string Legend => Id; | |
public string NumberFormat => "0.##"; | |
public UnitType UnitType => UnitType.Time; | |
public string Unit => "ns"; | |
public bool TheGreaterTheBetter => false; | |
public int PriorityInCategory => 1; | |
} | |
} | |
} |
@trontronicent Feel free. You might also add a comment to dotnet/BenchmarkDotNet#1666 in the hopes that an official implementation gets added. The limitation of my implementation is that the benchmark needs to be run "InProc", which is not the default behavior.
that suits me well, as i run 'unsupported' configs, I need InProc anyways ;)
Thx a lot, what credits shall I include on the codefile doc header ? If nothing custom desired, I just add the URL to this gist.
Hey Mark,
sorry for the ultra late reply. Thanks a lot for the script. i tried adding it to one of my projects, but sadly I don't get meaningful results. I added the diagnoser like so: https://github.com/JohannesDeml/MicroBenchmarksDotNet/blob/test/cpu-diagnoser/MicroBenchmarks.Extensions/DefaultBenchmarkConfig.cs#L39
And here are example results I get:
Method | TimeoutDuration | Mean | Error | StdDev | CPU User Time | CPU Privileged Time |
---|---|---|---|---|---|---|
ThreadSpinWait | 2 | 2.001 ms | 0.0003 ms | 0.0003 ms | - | - |
ThreadSleep0 | 2 | 2.001 ms | 0.0002 ms | 0.0002 ms | - | - |
ThreadSleep | 2 | 15.653 ms | 0.1952 ms | 0.1825 ms | - | - |
ThreadSleepEnhanced | 2 | 2.967 ms | 0.0228 ms | 0.0213 ms | - | - |
TaskDelay | 2 | 15.803 ms | 0.2703 ms | 0.2257 ms | - | - |
TimerWait | 2 | 15.643 ms | 0.1521 ms | 0.1348 ms | - | - |
AutoResetEvent | 2 | 15.669 ms | 0.1561 ms | 0.1461 ms | - | - |
ThreadSpinWait | 5 | 5.001 ms | 0.0011 ms | 0.0009 ms | - | - |
ThreadSleep0 | 5 | 5.001 ms | 0.0002 ms | 0.0002 ms | - | - |
ThreadSleep | 5 | 15.645 ms | 0.1543 ms | 0.1443 ms | - | - |
ThreadSleepEnhanced | 5 | 5.956 ms | 0.0278 ms | 0.0260 ms | - | - |
TaskDelay | 5 | 15.720 ms | 0.1555 ms | 0.1378 ms | - | - |
TimerWait | 5 | 15.646 ms | 0.2161 ms | 0.1916 ms | - | - |
AutoResetEvent | 5 | 15.694 ms | 0.2464 ms | 0.2305 ms | - | 29592.8 |
ThreadSpinWait | 20 | 20.001 ms | 0.0003 ms | 0.0002 ms | - | - |
ThreadSleep0 | 20 | 20.001 ms | 0.0005 ms | 0.0005 ms | - | - |
ThreadSleep | 20 | 31.509 ms | 0.1074 ms | 0.1004 ms | - | - |
ThreadSleepEnhanced | 20 | 20.892 ms | 0.0599 ms | 0.0560 ms | - | - |
TaskDelay | 20 | 31.492 ms | 0.2259 ms | 0.2003 ms | - | - |
TimerWait | 20 | 31.582 ms | 0.3439 ms | 0.2871 ms | - | - |
AutoResetEvent | 20 | 31.572 ms | 0.1158 ms | 0.1083 ms | - | 29592.8 |
Something seems to be not working here. Do you have an idea what the problem is?
No idea, sorry. Seems that something in .net 7 broke it. I noticed this too and stopped using it for my benchmarks.
Thanks for the swift response! Okay, got it, very strange. And fyi, this seems to also affect .NET 6, so maybe it is more because of a change in how benchmarkdotnet spins up the processes or something like that? 🤷
Got it working under .NET 8:
- Add [InProcess] on the class with the bechmarks.
- You might have to run it with Administrator - sorry, too lazy to restart and check if it works without.
- Start Without Debugger to get best results.
Hey Mark, i would like to include your diagnoser into a project (strictly internal use , no redistribution), and would like to ask if youre OK with that - cheers for the good work