Skip to content

Instantly share code, notes, and snippets.

@MarkPflug
Created December 6, 2021 04:21
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save MarkPflug/55173728458020c6d335cc099c891c0b to your computer and use it in GitHub Desktop.
Save MarkPflug/55173728458020c6d335cc099c891c0b to your computer and use it in GitHub Desktop.
BenchmarkDotNet CPU utilization
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
Copy link

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

@MarkPflug
Copy link
Author

@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.

@trontronicent
Copy link

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.

@JohannesDeml
Copy link

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?

@MarkPflug
Copy link
Author

No idea, sorry. Seems that something in .net 7 broke it. I noticed this too and stopped using it for my benchmarks.

@JohannesDeml
Copy link

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? 🤷

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment