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; /// /// Benchmarks for SignalR round-trip communication using the same infrastructure as SignalRClientToHubTest. /// Measures: Client -> Server -> Service -> Response -> Client /// [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 _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(BenchmarkSignalRTags.SingleIntParam, 42); } [Benchmark(Description = "RoundTrip: Two ints")] [BenchmarkCategory("Primitives")] public int RoundTrip_TwoInts() { return _client.PostSync(BenchmarkSignalRTags.TwoIntParams, [10, 20]); } [Benchmark(Description = "RoundTrip: Bool")] [BenchmarkCategory("Primitives")] public bool RoundTrip_Bool() { return _client.PostDataSync(BenchmarkSignalRTags.BoolParam, true); } [Benchmark(Description = "RoundTrip: String")] [BenchmarkCategory("Primitives")] public string? RoundTrip_String() { return _client.PostDataSync(BenchmarkSignalRTags.StringParam, "Hello"); } [Benchmark(Description = "RoundTrip: Guid")] [BenchmarkCategory("Primitives")] public Guid RoundTrip_Guid() { return _client.PostDataSync(BenchmarkSignalRTags.GuidParam, _testGuid); } [Benchmark(Description = "RoundTrip: No params")] [BenchmarkCategory("Primitives")] public string? RoundTrip_NoParams() { return _client.GetAllSync(BenchmarkSignalRTags.NoParams); } [Benchmark(Description = "RoundTrip: Multiple types (3 params)")] [BenchmarkCategory("Primitives")] public string? RoundTrip_MultipleTypes() { return _client.PostSync(BenchmarkSignalRTags.MultipleTypesParams, [true, "test", 42]); } #endregion #region Complex Object Benchmarks [Benchmark(Description = "RoundTrip: TestOrderItem")] [BenchmarkCategory("Complex")] public TestOrderItem? RoundTrip_TestOrderItem() { return _client.PostDataSync(BenchmarkSignalRTags.TestOrderItemParam, _testOrderItem); } [Benchmark(Description = "RoundTrip: TestOrder (3 items)")] [BenchmarkCategory("Complex")] public TestOrder? RoundTrip_TestOrder() { return _client.PostDataSync(BenchmarkSignalRTags.TestOrderParam, _testOrder); } [Benchmark(Description = "RoundTrip: SharedTag")] [BenchmarkCategory("Complex")] public SharedTag? RoundTrip_SharedTag() { return _client.PostDataSync(BenchmarkSignalRTags.SharedTagParam, _sharedTag); } #endregion #region Collection Benchmarks [Benchmark(Description = "RoundTrip: int[] (5 elements)")] [BenchmarkCategory("Collections")] public int[]? RoundTrip_IntArray() { return _client.PostDataSync(BenchmarkSignalRTags.IntArrayParam, _intArray); } [Benchmark(Description = "RoundTrip: List (3 elements)")] [BenchmarkCategory("Collections")] public List? RoundTrip_StringList() { return _client.PostDataSync, List>(BenchmarkSignalRTags.StringListParam, _stringList); } #endregion #region Mixed Parameter Benchmarks [Benchmark(Description = "RoundTrip: Int + DTO")] [BenchmarkCategory("Mixed")] public string? RoundTrip_IntAndDto() { return _client.PostSync(BenchmarkSignalRTags.IntAndDtoParam, [42, _testOrderItem]); } [Benchmark(Description = "RoundTrip: 5 mixed params")] [BenchmarkCategory("Mixed")] public string? RoundTrip_FiveParams() { return _client.PostSync(BenchmarkSignalRTags.FiveParams, [42, "hello", true, _testGuid, 99.99m]); } #endregion } #region Benchmark Infrastructure (minimal, reuses production code) /// /// SignalR tags for benchmarks - matches TestSignalRTags structure /// 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; } /// /// Benchmark-optimized SignalR client with synchronous methods for accurate timing /// 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(int tag, TPost data) => PostDataAsync(tag, data).GetAwaiter().GetResult(); public TResponse? PostSync(int tag, object[] parameters) => PostAsync(tag, parameters).GetAwaiter().GetResult(); public TResponse? GetAllSync(int tag) => GetAllAsync(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); } } /// /// Benchmark-optimized SignalR hub /// public class BenchmarkSignalRHub : AcWebSignalRHubBase { 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(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); } /// /// Benchmark service handlers - same logic as TestSignalRService2 /// 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 HandleStringList(List 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