mirror of
https://github.com/Stone-Red-Code/RemoteExec.git
synced 2026-09-04 09:06:20 +02:00
Add load balancing
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
using RemoteExec.Shared;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Channels;
|
||||
@@ -15,6 +16,11 @@ public class RemoteExecutionHub(ILogger<RemoteExecutionHub> logger) : Hub
|
||||
|
||||
private static readonly ConcurrentDictionary<Guid, TaskCompletionSource<byte[]>> pendingAssemblyRequests = new();
|
||||
|
||||
private static DateTime lastMetricsTimestamp;
|
||||
private static TimeSpan lastTotalProcessorTime;
|
||||
|
||||
private static int activeTasks = 0;
|
||||
|
||||
public override Task OnConnectedAsync()
|
||||
{
|
||||
RemoteJobAssemblyLoadContext assemblyLoadContext = new RemoteJobAssemblyLoadContext($"RemoteJob_{Guid.NewGuid()}");
|
||||
@@ -39,6 +45,8 @@ public class RemoteExecutionHub(ILogger<RemoteExecutionHub> logger) : Hub
|
||||
|
||||
public async Task<RemoteExecutionResult> Execute(RemoteExecutionRequest req)
|
||||
{
|
||||
_ = Interlocked.Increment(ref activeTasks);
|
||||
|
||||
try
|
||||
{
|
||||
if (!connections.TryGetValue(Context.ConnectionId, out RemoteJobAssemblyLoadContext? assemblyLoadContext))
|
||||
@@ -110,6 +118,23 @@ public class RemoteExecutionHub(ILogger<RemoteExecutionHub> logger) : Hub
|
||||
|
||||
object? result = method.Invoke(null, invokeArgs);
|
||||
|
||||
if (result is Task taskResult)
|
||||
{
|
||||
await taskResult.ConfigureAwait(false);
|
||||
Type returnType = method.ReturnType;
|
||||
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
{
|
||||
// For Task<T>, get the Result property
|
||||
PropertyInfo resultProperty = returnType.GetProperty("Result")!;
|
||||
result = resultProperty.GetValue(taskResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For non-generic Task, result is null
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
|
||||
return new RemoteExecutionResult
|
||||
{
|
||||
Result = result
|
||||
@@ -124,6 +149,10 @@ public class RemoteExecutionHub(ILogger<RemoteExecutionHub> logger) : Hub
|
||||
Exception = ex.ToString()
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ = Interlocked.Decrement(ref activeTasks);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProvideAssembly(Guid requestId, ChannelReader<byte> stream)
|
||||
@@ -146,6 +175,48 @@ public class RemoteExecutionHub(ILogger<RemoteExecutionHub> logger) : Hub
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ServerMetrics> GetMetrics()
|
||||
{
|
||||
return await GetServerMetrics();
|
||||
}
|
||||
|
||||
public static async Task BroadcastMetricsAsync(IHubContext<RemoteExecutionHub> hubContext)
|
||||
{
|
||||
ServerMetrics metrics = await GetServerMetrics();
|
||||
await hubContext.Clients.All.SendAsync("MetricsUpdated", metrics);
|
||||
}
|
||||
|
||||
private static async Task<ServerMetrics> GetServerMetrics()
|
||||
{
|
||||
Process currentProcess = Process.GetCurrentProcess();
|
||||
|
||||
// Capture current values
|
||||
DateTime currentTime = DateTime.UtcNow;
|
||||
TimeSpan currentProcessorTime = currentProcess.TotalProcessorTime;
|
||||
|
||||
// Calculate the difference since the last check
|
||||
double elapsedMs = (currentTime - lastMetricsTimestamp).TotalMilliseconds;
|
||||
double cpuMsUsed = (currentProcessorTime - lastTotalProcessorTime).TotalMilliseconds;
|
||||
|
||||
// Calculate percentage: (Time Used / Time Elapsed) / Cores
|
||||
// We multiply by 100 to get a 0-100 scale
|
||||
double cpuUsagePercent = cpuMsUsed / elapsedMs / Environment.ProcessorCount * 100;
|
||||
|
||||
// Update static variables for the next call
|
||||
lastMetricsTimestamp = currentTime;
|
||||
lastTotalProcessorTime = currentProcessorTime;
|
||||
|
||||
return new ServerMetrics
|
||||
{
|
||||
ServerId = Environment.MachineName,
|
||||
ActiveConnections = connections.Count,
|
||||
ActiveTasks = activeTasks,
|
||||
TotalMemoryUsage = currentProcess.WorkingSet64,
|
||||
CpuUsage = Math.Clamp(Math.Round(cpuUsagePercent, 2), 0, 100),
|
||||
Timestamp = currentTime
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<Assembly> RequestAssemblyAsync(string assemblyName)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using RemoteExec.Server.Hubs;
|
||||
using RemoteExec.Server.Services;
|
||||
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -7,6 +8,10 @@ WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddSignalR();
|
||||
builder.Services.AddOpenApi();
|
||||
builder.Services.AddHealthChecks();
|
||||
|
||||
// Add metrics broadcast background service
|
||||
builder.Services.AddHostedService<MetricsBroadcastService>();
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
@@ -18,10 +23,12 @@ if (app.Environment.IsDevelopment())
|
||||
_ = app.MapOpenApi();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
//app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.MapHealthChecks("/health");
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
using RemoteExec.Server.Hubs;
|
||||
|
||||
namespace RemoteExec.Server.Services;
|
||||
|
||||
public class MetricsBroadcastService(IHubContext<RemoteExecutionHub> hubContext, ILogger<MetricsBroadcastService> logger) : BackgroundService
|
||||
{
|
||||
private readonly TimeSpan _broadcastInterval = TimeSpan.FromSeconds(2);
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
logger.LogInformation("Metrics broadcast service started");
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(_broadcastInterval, stoppingToken);
|
||||
|
||||
await RemoteExecutionHub.BroadcastMetricsAsync(hubContext);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Expected when service is stopping
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error broadcasting metrics");
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("Metrics broadcast service stopped");
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace RemoteExec.Server;
|
||||
|
||||
public class WeatherForecast
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
|
||||
public int TemperatureC { get; set; }
|
||||
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
|
||||
public string? Summary { get; set; }
|
||||
}
|
||||
@@ -4,5 +4,16 @@
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "http://*:5000"
|
||||
},
|
||||
"Https": {
|
||||
"Url": "https://*:5001"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user