-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add CPU benchmark project * Fix typos * Add README.md Co-authored-by: Aaron Stannard <aaron@petabridge.com>
- Loading branch information
1 parent
f703526
commit 405dfc5
Showing
5 changed files
with
337 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
src/benchmark/Akka.Cluster.Cpu.Benchmark/Akka.Cluster.Cpu.Benchmark.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>netcoreapp3.1</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="NDesk.Options.Core" Version="1.2.5" /> | ||
<PackageReference Include="Tmds.ExecFunction" Version="0.5.0" /> | ||
<PackageReference Include="Universe.CpuUsage" Version="2.2.497.598" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\core\Akka.Cluster\Akka.Cluster.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// //----------------------------------------------------------------------- | ||
// // <copyright file="Seed.cs" company="Akka.NET Project"> | ||
// // Copyright (C) 2009-2022 Lightbend Inc. <http://www.lightbend.com> | ||
// // Copyright (C) 2013-2022 .NET Foundation <https://github.com/akkadotnet/akka.net> | ||
// // </copyright> | ||
// //----------------------------------------------------------------------- | ||
|
||
using System; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using Akka.Actor; | ||
using Akka.Configuration; | ||
|
||
namespace Akka.Cluster.Cpu.Benchmark | ||
{ | ||
public class BenchmarkNode | ||
{ | ||
private const string Address = "127.0.0.1"; | ||
private const int BasePort = 15225; | ||
|
||
public static async Task<int> EntryPoint(string[] args) | ||
{ | ||
var node = new BenchmarkNode(int.Parse(args[1])); | ||
node.Start(); | ||
|
||
// wait forever until we get killed | ||
await Task.Delay(TimeSpan.FromDays(1)); | ||
|
||
return 0; | ||
} | ||
|
||
private readonly Config _config; | ||
private ActorSystem _actorSystem; | ||
|
||
public BenchmarkNode(int nodeOffset) | ||
{ | ||
_config = ConfigurationFactory.ParseString($@" | ||
akka {{ | ||
log-dead-letters = off | ||
log-dead-letters-during-shutdown = off | ||
actor.provider = cluster | ||
remote {{ | ||
# log-remote-lifecycle-events = DEBUG | ||
dot-netty.tcp {{ | ||
transport-class = ""Akka.Remote.Transport.DotNetty.TcpTransport, Akka.Remote"" | ||
applied-adapters = [] | ||
transport-protocol = tcp | ||
hostname = ""0.0.0.0"" | ||
public-hostname = {Address} | ||
port = {BasePort + nodeOffset} | ||
}} | ||
}} | ||
cluster {{ | ||
seed-nodes = [""{new Address("akka.tcp", nameof(BenchmarkNode), Address, BasePort)}""] | ||
roles = [benchmark-node] | ||
}} | ||
}}"); | ||
} | ||
|
||
public void Start() | ||
{ | ||
_actorSystem = ActorSystem.Create(nameof(BenchmarkNode), _config); | ||
} | ||
|
||
public async Task StopAsync() | ||
=> await CoordinatedShutdown.Get(_actorSystem).Run(CoordinatedShutdown.ClrExitReason.Instance); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using NDesk.Options; | ||
using Tmds.Utils; | ||
using Universe.CpuUsage; | ||
|
||
namespace Akka.Cluster.Cpu.Benchmark | ||
{ | ||
public static class Program | ||
{ | ||
private const int DefaultSampleDuration = 5; // in seconds | ||
private const int DefaultDelay = 5; // in seconds | ||
private const int DefaultRepeat = 60; | ||
private const int DefaultClusterSize = 9; | ||
|
||
private const int DefaultWarmUpRepeat = 5; | ||
|
||
private static readonly List<CpuUsage> Usages = new List<CpuUsage>(); | ||
private static readonly List<Process> Processes = new List<Process>(); | ||
|
||
public static async Task<int> Main(string[] args) | ||
{ | ||
// ExecFunction hook | ||
if (ExecFunction.IsExecFunctionCommand(args)) | ||
return ExecFunction.Program.Main(args); | ||
|
||
// Argument parsing | ||
var sampleDuration = DefaultSampleDuration; | ||
var delay = DefaultDelay; | ||
var repeat = DefaultRepeat; | ||
var clusterSize = DefaultClusterSize; | ||
var warmUp = DefaultWarmUpRepeat; | ||
var showHelp = false; | ||
|
||
var optionSet = new OptionSet() | ||
.Add( | ||
"d|sample-duration=", | ||
"Sets the sample point duration in seconds. Default: 5 seconds", | ||
s => { sampleDuration = int.Parse(s); }) | ||
.Add( | ||
"delay=", | ||
"Sets the initial delay to wait for cluster to stabilize before benchmark starts in seconds. Default: 5 seconds", | ||
s => { delay = int.Parse(s); }) | ||
.Add( | ||
"s|samples=", | ||
"Sets how many samples are taken during the benchmark. Default: 60 samples", | ||
s => { repeat = int.Parse(s); }) | ||
.Add( | ||
"c|cluster-size=", | ||
"Sets how many nodes to be added to the cluster in addition to the tested node. Default: 9 nodes", | ||
s => { clusterSize = int.Parse(s); }) | ||
.Add( | ||
"w|warm-up-count=", | ||
"Sets how many blank samples to be performed as benchmark warm-up before benchmark starts. Default: 5 samples", | ||
s => { warmUp = int.Parse(s); }) | ||
.Add( | ||
"h|?|help", | ||
"Shows help", | ||
s => { showHelp = s != null; }); | ||
|
||
optionSet.Parse(args); | ||
|
||
if (showHelp) | ||
{ | ||
Console.WriteLine("Usage: dotnet run -c Release"); | ||
Console.WriteLine("Usage: dotnet run -c Release -- [OPTIONS]"); | ||
Console.WriteLine("Usage: Akka.Cluster.Cpu.Benchmark [OPTIONS]"); | ||
Console.WriteLine("Options:"); | ||
optionSet.WriteOptionDescriptions(Console.Out); | ||
return 0; | ||
} | ||
|
||
// Start the benchmark node | ||
var node = new BenchmarkNode(0); | ||
node.Start(); | ||
|
||
var executor = new FunctionExecutor(o => | ||
{ | ||
o.StartInfo.RedirectStandardError = true; | ||
o.OnExit = p => | ||
{ | ||
if (p.ExitCode != 0) | ||
{ | ||
var message = | ||
"Function execution failed with exit code: " + | ||
$"{p.ExitCode}{Environment.NewLine}{p.StandardError.ReadToEnd()}"; | ||
throw new Exception(message); | ||
} | ||
}; | ||
}); | ||
|
||
// Spin up cluster nodes | ||
foreach (var portOffset in Enumerable.Range(1, clusterSize)) | ||
{ | ||
Processes.Add(executor.Start(BenchmarkNode.EntryPoint, new [] | ||
{ | ||
portOffset.ToString() | ||
})); | ||
} | ||
|
||
// Wait until things settles down | ||
await Task.Delay(TimeSpan.FromSeconds(delay)); | ||
|
||
// Warm up | ||
foreach (var i in Enumerable.Range(1, warmUp)) | ||
{ | ||
var start = CpuUsage.GetByProcess(); | ||
await Task.Delay(TimeSpan.FromSeconds(sampleDuration)); | ||
var end = CpuUsage.GetByProcess(); | ||
var final = end - start; | ||
|
||
Console.WriteLine($"{i}. [Warmup] {final}"); | ||
} | ||
Console.WriteLine(); | ||
|
||
// Start benchmark | ||
foreach (var i in Enumerable.Range(1, repeat)) | ||
{ | ||
var start = CpuUsage.GetByProcess(); | ||
await Task.Delay(TimeSpan.FromSeconds(sampleDuration)); | ||
var end = CpuUsage.GetByProcess(); | ||
var final = end - start; | ||
|
||
Console.WriteLine($"{i}. Cpu Usage: {final}"); | ||
Usages.Add(final.Value); | ||
} | ||
|
||
// Kill cluster node processes | ||
foreach (var process in Processes) | ||
{ | ||
process.Kill(); | ||
process.Dispose(); | ||
} | ||
|
||
// Stop benchmark node | ||
await node.StopAsync(); | ||
|
||
// Generate csv report | ||
var now = DateTime.Now; | ||
var sb = new StringBuilder(); | ||
sb.AppendLine($"CPU Benchmark {now}"); | ||
sb.AppendLine($"Sample Time,{sampleDuration},second(s)"); | ||
sb.AppendLine($"Sample points,{repeat}"); | ||
sb.AppendLine($"Cluster size,{clusterSize},node(s)"); | ||
sb.AppendLine("Sample time,User usage,User percent,Kernel usage,Kernel percent,Total usage,Total percent"); | ||
foreach (var iter in Enumerable.Range(1, repeat)) | ||
{ | ||
var usage = Usages[iter - 1]; | ||
var user = usage.UserUsage.TotalSeconds; | ||
var kernel = usage.KernelUsage.TotalSeconds; | ||
var total = usage.TotalMicroSeconds / 1000000.0; | ||
sb.AppendLine($"{iter * sampleDuration},{user},{(user/sampleDuration)*100},{kernel},{(kernel/sampleDuration)*100},{total},{(total/sampleDuration)*100}"); | ||
} | ||
|
||
await File.WriteAllTextAsync($"CpuBenchmark_{now.ToFileTime()}.csv", sb.ToString()); | ||
|
||
// Generate console report | ||
sb.Clear(); | ||
sb.AppendLine("CPU Benchmark complete."); | ||
sb.AppendLine(); | ||
|
||
sb.AppendLine() | ||
.AppendLine(" CPU Usage Mode | Mean | StdErr | StdDev | Median | Maximum |") | ||
.AppendLine("--------------- |----- |------- |------- |------- |-------- |") | ||
.AppendLine(CalculateResult(Usages.Select(u => u.UserUsage.TotalMicroSeconds), "User")) | ||
.AppendLine(CalculateResult(Usages.Select(u => u.KernelUsage.TotalMicroSeconds), "Kernel")) | ||
.AppendLine(CalculateResult(Usages.Select(u => u.TotalMicroSeconds), "Total")); | ||
|
||
Console.WriteLine(sb.ToString()); | ||
|
||
return 0; | ||
} | ||
|
||
private static string CalculateResult(IEnumerable<long> values, string name) | ||
{ | ||
var times = values.OrderBy(i => i).ToArray(); | ||
var medianIndex = times.Length / 2; | ||
|
||
var mean = times.Average(); | ||
var stdDev = Math.Sqrt(times.Average(v => Math.Pow(v - mean, 2))); | ||
var stdErr = stdDev / Math.Sqrt(times.Length); | ||
double median; | ||
if (times.Length % 2 == 0) | ||
median = (times[medianIndex - 1] + times[medianIndex]) / 2.0; | ||
else | ||
median = times[medianIndex]; | ||
|
||
return $" {name} | {(mean / 1000.0):N3} ms | {(stdErr / 1000.0):N3} ms | {(stdDev / 1000.0):N3} ms | {(median / 1000.0):N3} ms | {(times.Last() / 1000.0):N3} ms |"; | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Akka.Cluster.Cpu.Benchmark | ||
|
||
This project is a standalone console CPU benchmark to measure Akka.Cluster actors CPU usage. It will: | ||
|
||
* Spin up a single bare minimum cluster node as a seed node, | ||
* Spins up a cluster with a predetermined size that uses that node as seed, and then | ||
* Collect CPU usage at regular interval and save the result in a comma separated value (.csv) file that can be imported into a spreadsheet application. | ||
|
||
## Usage | ||
|
||
To run this project directly using .NET CLI, use one of the following commands: | ||
|
||
```powershell | ||
dotnet run -c Release | ||
dotnet run -c Release -- [OPTIONS] | ||
``` | ||
|
||
To run this as a standalone compiled executable, use one of the following commands: | ||
|
||
```powershell | ||
./Akka.Cluster.Cpu.Benchmark.exe [OPTIONS] | ||
dotnet run Akka.Cluster.Cpu.Benchmark.dll | ||
dotnet run Akka.Cluster.Cpu.Benchmark.dll -- [OPTIONS] | ||
``` | ||
|
||
## Options | ||
|
||
| Option | Description | | ||
|-----------------------------|--------------------------------------------------------------------------------------------------------------------| | ||
| -d, --sample-duration=VALUE | Sets the sample point duration in seconds.<br/>Default: 5 seconds | | ||
| --delay=VALUE | Sets the initial delay to wait for cluster to stabilize before benchmark starts in seconds.<br/>Default: 5 seconds | | ||
| -s, --samples=VALUE | Sets how many samples are taken during the benchmark.<br/>Default: 60 samples | | ||
| -c, --cluster-size=VALUE | Sets how many nodes to be added to the cluster in addition to the tested node.<br/>Default: 9 nodes | | ||
| -w, --warm-up-count=VALUE | Sets how many blank samples to be performed as benchmark warm-up before benchmark starts.<br/>Default: 5 samples | | ||
| -h, -?, --help | Shows help | | ||
|