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