Skip to content

Crankier server #12406

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Aug 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -11,5 +12,6 @@ internal static class Defaults
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;
}
}
60 changes: 60 additions & 0 deletions src/SignalR/perf/benchmarkapps/Crankier/Commands/ServerCommand.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
5 changes: 5 additions & 0 deletions src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
1 change: 1 addition & 0 deletions src/SignalR/perf/benchmarkapps/Crankier/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public static void Main(string[] args)
LocalCommand.Register(app);
AgentCommand.Register(app);
WorkerCommand.Register(app);
ServerCommand.Register(app);

app.Command("help", cmd =>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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++;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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; }
}
}
72 changes: 72 additions & 0 deletions src/SignalR/perf/benchmarkapps/Crankier/Server/EchoHub.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
39 changes: 39 additions & 0 deletions src/SignalR/perf/benchmarkapps/Crankier/Server/Startup.cs
Original file line number Diff line number Diff line change
@@ -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");
});
}
}
}