Crankier server (#12406)

* move server into Crankier
* added ConnectionCounter
* allow logging
This commit is contained in:
Stafford Williams 2019-08-16 09:19:37 +10:00 committed by Stephen Halter
parent 6699353dc6
commit 6b4a101ca7
9 changed files with 336 additions and 0 deletions

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.SignalR.Crankier.Commands
{
@ -11,5 +12,6 @@ namespace Microsoft.AspNetCore.SignalR.Crankier.Commands
public static readonly int NumberOfConnections = 10_000;
public static readonly int SendDurationInSeconds = 300;
public static readonly HttpTransportType TransportType = HttpTransportType.WebSockets;
public static readonly LogLevel LogLevel = LogLevel.None;
}
}

View File

@ -0,0 +1,60 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.Extensions.CommandLineUtils;
using static Microsoft.AspNetCore.SignalR.Crankier.Commands.CommandLineUtilities;
using System.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.SignalR.Crankier.Server;
namespace Microsoft.AspNetCore.SignalR.Crankier.Commands
{
internal class ServerCommand
{
public static void Register(CommandLineApplication app)
{
app.Command("server", cmd =>
{
var logLevelOption = cmd.Option("--log <LOG_LEVEL>", "The LogLevel to use.", CommandOptionType.SingleValue);
cmd.OnExecute(() =>
{
LogLevel logLevel = Defaults.LogLevel;
if (logLevelOption.HasValue() && !Enum.TryParse(logLevelOption.Value(), out logLevel))
{
return InvalidArg(logLevelOption);
}
return Execute(logLevel);
});
});
}
private static int Execute(LogLevel logLevel)
{
Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}");
var config = new ConfigurationBuilder()
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
.Build();
var host = new WebHostBuilder()
.UseConfiguration(config)
.ConfigureLogging(loggerFactory =>
{
loggerFactory.AddConsole().SetMinimumLevel(logLevel);
})
.UseKestrel()
.UseStartup<Startup>();
host.Build().Run();
return 0;
}
}
}

View File

@ -9,6 +9,11 @@
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.SignalR.Client" />
<Reference Include="Microsoft.Extensions.CommandLineUtils.Sources" />
<Reference Include="Microsoft.AspNetCore.SignalR" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
<Reference Include="Microsoft.Extensions.Configuration.CommandLine" />
<Reference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" />
<Reference Include="Microsoft.Extensions.Logging.Console" />
<Reference Include="Newtonsoft.Json" />
</ItemGroup>

View File

@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.SignalR.Crankier
LocalCommand.Register(app);
AgentCommand.Register(app);
WorkerCommand.Register(app);
ServerCommand.Register(app);
app.Command("help", cmd =>
{

View File

@ -0,0 +1,60 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.SignalR.Crankier.Server
{
public class ConnectionCounter
{
private int _totalConnectedCount;
private int _peakConnectedCount;
private int _totalDisconnectedCount;
private int _receivedCount;
private readonly object _lock = new object();
public ConnectionSummary Summary
{
get
{
lock (_lock)
{
return new ConnectionSummary
{
CurrentConnections = _totalConnectedCount - _totalDisconnectedCount,
PeakConnections = _peakConnectedCount,
TotalConnected = _totalConnectedCount,
TotalDisconnected = _totalDisconnectedCount,
ReceivedCount = _receivedCount
};
}
}
}
public void Receive(string payload)
{
lock (_lock)
{
_receivedCount += payload.Length;
}
}
public void Connected()
{
lock (_lock)
{
_totalConnectedCount++;
_peakConnectedCount = Math.Max(_totalConnectedCount - _totalDisconnectedCount, _peakConnectedCount);
}
}
public void Disconnected()
{
lock (_lock)
{
_totalDisconnectedCount++;
}
}
}
}

View File

@ -0,0 +1,79 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
namespace Microsoft.AspNetCore.SignalR.Crankier.Server
{
public class ConnectionCounterHostedService : IHostedService, IDisposable
{
private Stopwatch _timeSinceFirstConnection;
private readonly ConnectionCounter _counter;
private ConnectionSummary _lastSummary;
private Timer _timer;
private int _executingDoWork;
public ConnectionCounterHostedService(ConnectionCounter counter)
{
_counter = counter;
_timeSinceFirstConnection = new Stopwatch();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
return Task.CompletedTask;
}
private void DoWork(object state)
{
if (Interlocked.Exchange(ref _executingDoWork, 1) == 0)
{
var summary = _counter.Summary;
if (summary.PeakConnections > 0)
{
if (_timeSinceFirstConnection.ElapsedTicks == 0)
{
_timeSinceFirstConnection.Start();
}
var elapsed = _timeSinceFirstConnection.Elapsed;
if (_lastSummary != null)
{
Console.WriteLine(@"[{0:hh\:mm\:ss}] Current: {1}, peak: {2}, connected: {3}, disconnected: {4}, rate: {5}/s",
elapsed,
summary.CurrentConnections,
summary.PeakConnections,
summary.TotalConnected - _lastSummary.TotalConnected,
summary.TotalDisconnected - _lastSummary.TotalDisconnected,
summary.CurrentConnections - _lastSummary.CurrentConnections
);
}
_lastSummary = summary;
}
Interlocked.Exchange(ref _executingDoWork, 0);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.SignalR.Crankier.Server
{
public class ConnectionSummary
{
public int TotalConnected { get; set; }
public int TotalDisconnected { get; set; }
public int PeakConnections { get; set; }
public int CurrentConnections { get; set; }
public int ReceivedCount { get; set; }
}
}

View File

@ -0,0 +1,72 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace Microsoft.AspNetCore.SignalR.Crankier.Server
{
public class EchoHub : Hub
{
private ConnectionCounter _counter;
public EchoHub(ConnectionCounter counter)
{
_counter = counter;
}
public async Task Broadcast(int duration)
{
var sent = 0;
try
{
var t = new CancellationTokenSource();
t.CancelAfter(TimeSpan.FromSeconds(duration));
while (!t.IsCancellationRequested && !Context.ConnectionAborted.IsCancellationRequested)
{
await Clients.All.SendAsync("send", DateTime.UtcNow);
sent++;
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
Console.WriteLine("Broadcast exited: Sent {0} messages", sent);
}
public override Task OnConnectedAsync()
{
_counter?.Connected();
return Task.CompletedTask;
}
public override Task OnDisconnectedAsync(Exception exception)
{
_counter?.Disconnected();
return Task.CompletedTask;
}
public DateTime Echo(DateTime time)
{
return time;
}
public Task EchoAll(DateTime time)
{
return Clients.All.SendAsync("send", time);
}
public void SendPayload(string payload)
{
_counter?.Receive(payload);
}
public DateTime GetCurrentTime()
{
return DateTime.UtcNow;
}
}
}

View File

@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.SignalR.Crankier.Server
{
public class Startup
{
private readonly IConfiguration _config;
public Startup(IConfiguration configuration)
{
_config = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
var signalrBuilder = services.AddSignalR()
.AddMessagePackProtocol();
services.AddSingleton<ConnectionCounter>();
services.AddHostedService<ConnectionCounterHostedService>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<EchoHub>("/echo");
});
}
}
}