309 lines
11 KiB
C#
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
|