using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Text.Json; using Cysharp.Threading.Tasks; using ShrinkNetwork; public static class TcpBenchmarks { public static async Task Run(string output) { var rows = new List(); foreach (var connections in new[] { 1, 32, 256 }) { var probe = new TcpListener(IPAddress.Loopback, 0); probe.Start(); var port = ((IPEndPoint)probe.LocalEndpoint).Port; probe.Stop(); var transport = new ShrinkTcpServerTransport(IPAddress.Loopback, port); var server = Service(); server.RegisterRequestHandler((_, request) => UniTask.FromResult(new NetworkTransportTests.Response { Text = request.Text })); server.BindTransport(transport); var clients = new List<(ShrinkNetworkService service, ShrinkTcpClientTransport transport, ShrinkNetworkSession session)>(); long notifications = 0; try { for (var i = 0; i < connections; i++) { var client = Service(); var link = new ShrinkTcpClientTransport("127.0.0.1", port); var connected = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); client.OnSessionConnected += s => connected.TrySetResult(s); client.RegisterHandler((_, _) => { Interlocked.Increment(ref notifications); return UniTask.CompletedTask; }); client.BindTransport(link); clients.Add((client, link, await connected.Task.WaitAsync(TimeSpan.FromSeconds(10)))); } foreach (var bytes in new[] { 64, 1024, 32768 }) { var samples = new List(); var message = new NetworkTransportTests.Request { Text = new string('t', bytes) }; var allocated = GC.GetTotalAllocatedBytes(true); var elapsed = Stopwatch.StartNew(); for (var i = 0; i < 256; i++) { var client = clients[i % clients.Count]; var start = Stopwatch.GetTimestamp(); var response = await client.service.CallAsync(client.session, message).AsTask().WaitAsync(TimeSpan.FromSeconds(10)); if (response.Text.Length != bytes) throw new InvalidDataException("RPC content mismatch"); samples.Add(Stopwatch.GetElapsedTime(start).TotalMicroseconds); } samples.Sort(); rows.Add(new { scenario = "tcp/rpc", connections, bytes, operations = 256, throughputPerSecond = 256 / elapsed.Elapsed.TotalSeconds, p50Microseconds = samples[128], p95Microseconds = samples[243], p99Microseconds = samples[253], processAllocatedBytes = GC.GetTotalAllocatedBytes(true) - allocated }); var payload = new ShrinkJsonNetworkSerializer().Serialize(message); var before = Interlocked.Read(ref notifications); elapsed.Restart(); foreach (var session in server.Sessions.Values) await server.SendSerializedAsync(session, typeof(NetworkTransportTests.Request), payload); while (Interlocked.Read(ref notifications) - before < connections) { if (elapsed.Elapsed > TimeSpan.FromSeconds(10)) throw new TimeoutException("Broadcast drain"); await Task.Delay(1); } rows.Add(new { scenario = "tcp/broadcast", connections, bytes, elapsedMilliseconds = elapsed.Elapsed.TotalMilliseconds, delivered = Interlocked.Read(ref notifications) - before }); } using var queue = new ShrinkNetworkDispatchQueue(32, byteCapacity: 32768, perSessionCapacity: 2); server.DispatchScheduler = queue; var attempts = connections * 8; for (var i = 0; i < attempts; i++) { var c = clients[i % clients.Count]; if (c.service.Sessions.Count != 0) try { await c.service.SendAsync(c.session, new NetworkTransportTests.Request { Text = new string('s', 1024) }); } catch (IOException) { } } await Task.Delay(100); var beforePump = queue.CaptureDiagnostics(); await queue.PumpAsync(32, 8192, TimeSpan.FromMilliseconds(2)); rows.Add(new { scenario = "tcp/slow-consumer-burst", connections, attempts, beforePump, afterPump = queue.CaptureDiagnostics(), metrics = server.GetDiagnosticsSnapshot() }); } finally { foreach (var c in clients) c.transport.Stop(); transport.Stop(); } Console.WriteLine("TCP completed: " + connections); } await File.WriteAllTextAsync(output, JsonSerializer.Serialize(new { runtime = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription, host = "Windows loopback TCP; sequential round-trip samples, multiple real connections", rows }, new JsonSerializerOptions { WriteIndented = true })); } private static ShrinkNetworkService Service() { var service = new ShrinkNetworkService(); service.RegisterMessage(1); service.RegisterMessage(2); return service; } }