136 lines
4.0 KiB
C#
136 lines
4.0 KiB
C#
using BenchmarkDotNet.Attributes;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using ZB.MOM.WW.CBDDC.Core.Network;
|
|
using ZB.MOM.WW.CBDDC.Sample.Console;
|
|
|
|
namespace ZB.MOM.WW.CDBBC.E2E.Benchmark.Tests;
|
|
|
|
[MemoryDiagnoser]
|
|
[SimpleJob(launchCount: 1, warmupCount: 1, iterationCount: 3)]
|
|
public class E2EThroughputBenchmarks
|
|
{
|
|
private const int BatchSize = 50;
|
|
private BenchmarkPeerNode _nodeA = null!;
|
|
private BenchmarkPeerNode _nodeB = null!;
|
|
private int _sequence;
|
|
|
|
[GlobalSetup]
|
|
public async Task GlobalSetupAsync()
|
|
{
|
|
int nodeAPort = GetAvailableTcpPort();
|
|
int nodeBPort = GetAvailableTcpPort();
|
|
while (nodeBPort == nodeAPort)
|
|
nodeBPort = GetAvailableTcpPort();
|
|
|
|
string clusterToken = Guid.NewGuid().ToString("N");
|
|
|
|
_nodeA = BenchmarkPeerNode.Create(
|
|
"benchmark-node-a",
|
|
nodeAPort,
|
|
clusterToken,
|
|
[
|
|
new KnownPeerConfiguration
|
|
{
|
|
NodeId = "benchmark-node-b",
|
|
Host = "127.0.0.1",
|
|
Port = nodeBPort
|
|
}
|
|
]);
|
|
|
|
_nodeB = BenchmarkPeerNode.Create(
|
|
"benchmark-node-b",
|
|
nodeBPort,
|
|
clusterToken,
|
|
[
|
|
new KnownPeerConfiguration
|
|
{
|
|
NodeId = "benchmark-node-a",
|
|
Host = "127.0.0.1",
|
|
Port = nodeAPort
|
|
}
|
|
]);
|
|
|
|
await _nodeA.StartAsync();
|
|
await _nodeB.StartAsync();
|
|
|
|
// Allow initial network loop to settle before measurements.
|
|
await Task.Delay(500);
|
|
}
|
|
|
|
[GlobalCleanup]
|
|
public Task GlobalCleanupAsync()
|
|
{
|
|
// Explicit Surreal embedded disposal can race native callbacks in benchmark child processes.
|
|
// Benchmarks run out-of-process, so process teardown is used for cleanup stability.
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
[Benchmark(Description = "Local write throughput", OperationsPerInvoke = BatchSize)]
|
|
public async Task LocalWriteThroughput()
|
|
{
|
|
IReadOnlyList<string> userIds = NextUserIds("local");
|
|
foreach (string userId in userIds)
|
|
await _nodeA.UpsertUserAsync(CreateUser(userId));
|
|
}
|
|
|
|
[Benchmark(Description = "Cross-node replicated throughput", OperationsPerInvoke = BatchSize)]
|
|
public async Task ReplicatedWriteThroughput()
|
|
{
|
|
IReadOnlyList<string> userIds = NextUserIds("replicated");
|
|
foreach (string userId in userIds)
|
|
await _nodeA.UpsertUserAsync(CreateUser(userId));
|
|
|
|
await WaitForReplicationAsync(userIds, TimeSpan.FromSeconds(30));
|
|
}
|
|
|
|
private IReadOnlyList<string> NextUserIds(string prefix)
|
|
{
|
|
int start = Interlocked.Add(ref _sequence, BatchSize) - BatchSize;
|
|
string[] ids = new string[BatchSize];
|
|
for (var i = 0; i < BatchSize; i++)
|
|
ids[i] = $"{prefix}-{start + i:D8}";
|
|
|
|
return ids;
|
|
}
|
|
|
|
private static User CreateUser(string userId)
|
|
{
|
|
return new User
|
|
{
|
|
Id = userId,
|
|
Name = $"user-{userId}",
|
|
Age = 30,
|
|
Address = new Address { City = "BenchmarkCity" }
|
|
};
|
|
}
|
|
|
|
private async Task WaitForReplicationAsync(IReadOnlyList<string> userIds, TimeSpan timeout)
|
|
{
|
|
DateTime deadline = DateTime.UtcNow.Add(timeout);
|
|
while (DateTime.UtcNow < deadline)
|
|
{
|
|
bool allPresent = true;
|
|
foreach (string userId in userIds)
|
|
if (!_nodeB.ContainsUser(userId))
|
|
{
|
|
allPresent = false;
|
|
break;
|
|
}
|
|
|
|
if (allPresent) return;
|
|
|
|
await Task.Delay(25);
|
|
}
|
|
|
|
throw new TimeoutException($"Timed out waiting for replication of {userIds.Count} users.");
|
|
}
|
|
|
|
private static int GetAvailableTcpPort()
|
|
{
|
|
using var listener = new TcpListener(IPAddress.Loopback, 0);
|
|
listener.Start();
|
|
return ((IPEndPoint)listener.LocalEndpoint).Port;
|
|
}
|
|
}
|