AyCode.Core/BenchmarkSuite1/SignalRRoundTripBenchmarks.cs

309 lines
11 KiB
C#

using System.Security.Claims;
using AyCode.Core.Extensions;
using AyCode.Core.Tests.TestModels;
using AyCode.Models.Server.DynamicMethods;
using AyCode.Services.Server.SignalRs;
using AyCode.Services.SignalRs;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Configuration;
using Microsoft.VSDiagnostics;
namespace AyCode.Core.Benchmarks;
/// <summary>
/// Benchmarks for SignalR round-trip communication using the same infrastructure as SignalRClientToHubTest.
/// Measures: Client -> Server -> Service -> Response -> Client
/// </summary>
[MemoryDiagnoser]
[CPUUsageDiagnoser]
public class SignalRRoundTripBenchmarks
{
private BenchmarkSignalRClient _client = null!;
private BenchmarkSignalRHub _hub = null!;
private BenchmarkSignalRService _service = null!;
// Pre-created test data
private TestOrderItem _testOrderItem = null!;
private TestOrder _testOrder = null!;
private SharedTag _sharedTag = null!;
private int[] _intArray = null!;
private List<string> _stringList = null!;
private Guid _testGuid;
[GlobalSetup]
public void Setup()
{
var logger = new TestLogger();
_hub = new BenchmarkSignalRHub(logger);
_service = new BenchmarkSignalRService();
_client = new BenchmarkSignalRClient(_hub, logger);
_hub.RegisterService(_service, _client);
// Pre-create test data
_testOrderItem = new TestOrderItem { Id = 1, ProductName = "Widget", Quantity = 5, UnitPrice = 10.50m };
_testOrder = TestDataFactory.CreateOrder(itemCount: 3);
_sharedTag = new SharedTag { Id = 1, Name = "Important", Color = "#FF0000" };
_intArray = [1, 2, 3, 4, 5];
_stringList = ["apple", "banana", "cherry"];
_testGuid = Guid.NewGuid();
}
#region Primitive Parameter Benchmarks
[Benchmark(Description = "RoundTrip: Single int")]
[BenchmarkCategory("Primitives")]
public string? RoundTrip_SingleInt()
{
return _client.PostDataSync<int, string>(BenchmarkSignalRTags.SingleIntParam, 42);
}
[Benchmark(Description = "RoundTrip: Two ints")]
[BenchmarkCategory("Primitives")]
public int RoundTrip_TwoInts()
{
return _client.PostSync<int>(BenchmarkSignalRTags.TwoIntParams, [10, 20]);
}
[Benchmark(Description = "RoundTrip: Bool")]
[BenchmarkCategory("Primitives")]
public bool RoundTrip_Bool()
{
return _client.PostDataSync<bool, bool>(BenchmarkSignalRTags.BoolParam, true);
}
[Benchmark(Description = "RoundTrip: String")]
[BenchmarkCategory("Primitives")]
public string? RoundTrip_String()
{
return _client.PostDataSync<string, string>(BenchmarkSignalRTags.StringParam, "Hello");
}
[Benchmark(Description = "RoundTrip: Guid")]
[BenchmarkCategory("Primitives")]
public Guid RoundTrip_Guid()
{
return _client.PostDataSync<Guid, Guid>(BenchmarkSignalRTags.GuidParam, _testGuid);
}
[Benchmark(Description = "RoundTrip: No params")]
[BenchmarkCategory("Primitives")]
public string? RoundTrip_NoParams()
{
return _client.GetAllSync<string>(BenchmarkSignalRTags.NoParams);
}
[Benchmark(Description = "RoundTrip: Multiple types (3 params)")]
[BenchmarkCategory("Primitives")]
public string? RoundTrip_MultipleTypes()
{
return _client.PostSync<string>(BenchmarkSignalRTags.MultipleTypesParams, [true, "test", 42]);
}
#endregion
#region Complex Object Benchmarks
[Benchmark(Description = "RoundTrip: TestOrderItem")]
[BenchmarkCategory("Complex")]
public TestOrderItem? RoundTrip_TestOrderItem()
{
return _client.PostDataSync<TestOrderItem, TestOrderItem>(BenchmarkSignalRTags.TestOrderItemParam, _testOrderItem);
}
[Benchmark(Description = "RoundTrip: TestOrder (3 items)")]
[BenchmarkCategory("Complex")]
public TestOrder? RoundTrip_TestOrder()
{
return _client.PostDataSync<TestOrder, TestOrder>(BenchmarkSignalRTags.TestOrderParam, _testOrder);
}
[Benchmark(Description = "RoundTrip: SharedTag")]
[BenchmarkCategory("Complex")]
public SharedTag? RoundTrip_SharedTag()
{
return _client.PostDataSync<SharedTag, SharedTag>(BenchmarkSignalRTags.SharedTagParam, _sharedTag);
}
#endregion
#region Collection Benchmarks
[Benchmark(Description = "RoundTrip: int[] (5 elements)")]
[BenchmarkCategory("Collections")]
public int[]? RoundTrip_IntArray()
{
return _client.PostDataSync<int[], int[]>(BenchmarkSignalRTags.IntArrayParam, _intArray);
}
[Benchmark(Description = "RoundTrip: List<string> (3 elements)")]
[BenchmarkCategory("Collections")]
public List<string>? RoundTrip_StringList()
{
return _client.PostDataSync<List<string>, List<string>>(BenchmarkSignalRTags.StringListParam, _stringList);
}
#endregion
#region Mixed Parameter Benchmarks
[Benchmark(Description = "RoundTrip: Int + DTO")]
[BenchmarkCategory("Mixed")]
public string? RoundTrip_IntAndDto()
{
return _client.PostSync<string>(BenchmarkSignalRTags.IntAndDtoParam, [42, _testOrderItem]);
}
[Benchmark(Description = "RoundTrip: 5 mixed params")]
[BenchmarkCategory("Mixed")]
public string? RoundTrip_FiveParams()
{
return _client.PostSync<string>(BenchmarkSignalRTags.FiveParams, [42, "hello", true, _testGuid, 99.99m]);
}
#endregion
}
#region Benchmark Infrastructure (minimal, reuses production code)
/// <summary>
/// SignalR tags for benchmarks - matches TestSignalRTags structure
/// </summary>
public abstract class BenchmarkSignalRTags : AcSignalRTags
{
public const int SingleIntParam = 100;
public const int TwoIntParams = 101;
public const int BoolParam = 102;
public const int StringParam = 103;
public const int GuidParam = 104;
public const int NoParams = 107;
public const int MultipleTypesParams = 109;
public const int TestOrderItemParam = 120;
public const int TestOrderParam = 121;
public const int SharedTagParam = 122;
public const int IntArrayParam = 130;
public const int StringListParam = 132;
public const int IntAndDtoParam = 160;
public const int FiveParams = 164;
}
/// <summary>
/// Benchmark-optimized SignalR client with synchronous methods for accurate timing
/// </summary>
public class BenchmarkSignalRClient : AcSignalRClientBase, IAcSignalRHubItemServer
{
private readonly BenchmarkSignalRHub _hub;
public BenchmarkSignalRClient(BenchmarkSignalRHub hub, TestLogger logger) : base(logger)
{
_hub = hub;
// Eliminate polling delay for benchmarks
MsDelay = 0;
MsFirstDelay = 0;
}
// Synchronous wrappers for benchmarking (avoids async overhead measurement)
public TResponse? PostDataSync<TPost, TResponse>(int tag, TPost data)
=> PostDataAsync<TPost, TResponse>(tag, data).GetAwaiter().GetResult();
public TResponse? PostSync<TResponse>(int tag, object[] parameters)
=> PostAsync<TResponse>(tag, parameters).GetAwaiter().GetResult();
public TResponse? GetAllSync<TResponse>(int tag)
=> GetAllAsync<TResponse>(tag).GetAwaiter().GetResult();
protected override Task MessageReceived(int messageTag, byte[] messageBytes) => Task.CompletedTask;
protected override HubConnectionState GetConnectionState() => HubConnectionState.Connected;
protected override bool IsConnected() => true;
protected override Task StartConnectionInternal() => Task.CompletedTask;
protected override Task StopConnectionInternal() => Task.CompletedTask;
protected override ValueTask DisposeConnectionInternal() => ValueTask.CompletedTask;
protected override async Task SendToHubAsync(int messageTag, byte[]? messageBytes, int? requestId)
{
await _hub.OnReceiveMessage(messageTag, messageBytes, requestId);
}
}
/// <summary>
/// Benchmark-optimized SignalR hub
/// </summary>
public class BenchmarkSignalRHub : AcWebSignalRHubBase<BenchmarkSignalRTags, TestLogger>
{
private IAcSignalRHubItemServer _callerClient = null!;
public BenchmarkSignalRHub(TestLogger logger) : base(new ConfigurationBuilder().Build(), logger)
{
}
public void RegisterService(object service, IAcSignalRHubItemServer client)
{
_callerClient = client;
DynamicMethodCallModels.Add(new AcDynamicMethodCallModel<SignalRAttribute>(service));
}
protected override string GetConnectionId() => "benchmark-connection";
protected override bool IsConnectionAborted() => false;
protected override string? GetUserIdentifier() => "benchmark-user";
protected override ClaimsPrincipal? GetUser() => null;
protected override Task ResponseToCaller(int messageTag, ISignalRMessage message, int? requestId)
=> SendMessageToClient(_callerClient, messageTag, message, requestId);
}
/// <summary>
/// Benchmark service handlers - same logic as TestSignalRService2
/// </summary>
public class BenchmarkSignalRService
{
[SignalR(BenchmarkSignalRTags.SingleIntParam)]
public string HandleSingleInt(int value) => $"{value}";
[SignalR(BenchmarkSignalRTags.TwoIntParams)]
public int HandleTwoInts(int a, int b) => a + b;
[SignalR(BenchmarkSignalRTags.BoolParam)]
public bool HandleBool(bool value) => value;
[SignalR(BenchmarkSignalRTags.StringParam)]
public string HandleString(string text) => $"Echo: {text}";
[SignalR(BenchmarkSignalRTags.GuidParam)]
public Guid HandleGuid(Guid id) => id;
[SignalR(BenchmarkSignalRTags.NoParams)]
public string HandleNoParams() => "OK";
[SignalR(BenchmarkSignalRTags.MultipleTypesParams)]
public string HandleMultipleTypes(bool flag, string text, int number) => $"{flag}-{text}-{number}";
[SignalR(BenchmarkSignalRTags.TestOrderItemParam)]
public TestOrderItem HandleTestOrderItem(TestOrderItem item) => new()
{
Id = item.Id,
ProductName = $"Processed: {item.ProductName}",
Quantity = item.Quantity * 2,
UnitPrice = item.UnitPrice * 2,
};
[SignalR(BenchmarkSignalRTags.TestOrderParam)]
public TestOrder HandleTestOrder(TestOrder order) => order;
[SignalR(BenchmarkSignalRTags.SharedTagParam)]
public SharedTag HandleSharedTag(SharedTag tag) => tag;
[SignalR(BenchmarkSignalRTags.IntArrayParam)]
public int[] HandleIntArray(int[] values) => values.Select(x => x * 2).ToArray();
[SignalR(BenchmarkSignalRTags.StringListParam)]
public List<string> HandleStringList(List<string> items) => items.Select(x => x.ToUpper()).ToList();
[SignalR(BenchmarkSignalRTags.IntAndDtoParam)]
public string HandleIntAndDto(int id, TestOrderItem item) => $"{id}-{item?.ProductName}";
[SignalR(BenchmarkSignalRTags.FiveParams)]
public string HandleFiveParams(int a, string b, bool c, Guid d, decimal e) => $"{a}-{b}-{c}-{d}-{e}";
}
#endregion