Implemented statistics. Refactored storage abstractions. Added config options for docker and local storage. Added server service and server updating.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
using Docker.DotNet;
|
||||
using Docker.DotNet.Models;
|
||||
using MoonlightServers.Daemon.ServerSystem.Abstractions;
|
||||
|
||||
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
|
||||
@@ -6,9 +8,186 @@ public class DockerStatistics : IRuntimeStatistics, IInstallStatistics
|
||||
{
|
||||
public event Func<ServerStatistics, Task>? OnStatisticsReceived;
|
||||
|
||||
public Task AttachAsync() => Task.CompletedTask;
|
||||
private readonly string ContainerId;
|
||||
private readonly DockerClient DockerClient;
|
||||
private readonly ILogger Logger;
|
||||
|
||||
public Task ClearCacheAsync() => Task.CompletedTask;
|
||||
private readonly List<ServerStatistics> Cache = new(102);
|
||||
private readonly SemaphoreSlim CacheLock = new(1, 1);
|
||||
private readonly CancellationTokenSource Cts = new();
|
||||
|
||||
public Task<ServerStatistics[]> GetCacheAsync() => Task.FromResult<ServerStatistics[]>([]);
|
||||
public DockerStatistics(string containerId, DockerClient dockerClient, ILogger logger)
|
||||
{
|
||||
ContainerId = containerId;
|
||||
DockerClient = dockerClient;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public Task AttachAsync()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
Logger.LogTrace("Streaming loop entered");
|
||||
|
||||
while (!Cts.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await DockerClient.Containers.GetContainerStatsAsync(
|
||||
ContainerId,
|
||||
new ContainerStatsParameters()
|
||||
{
|
||||
Stream = true
|
||||
},
|
||||
new Progress<ContainerStatsResponse>(HandleStatsAsync),
|
||||
Cts.Token
|
||||
);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e, "An unhandled error occured while processing container stats");
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogTrace("Streaming loop exited");
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async void HandleStatsAsync(ContainerStatsResponse statsResponse)
|
||||
{
|
||||
try
|
||||
{
|
||||
var convertedStats = ToServerStatistics(statsResponse);
|
||||
|
||||
await CacheLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
if(Cache.Count > 100)
|
||||
Cache.RemoveRange(0, 30);
|
||||
|
||||
Cache.Add(convertedStats);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CacheLock.Release();
|
||||
}
|
||||
|
||||
if (OnStatisticsReceived != null)
|
||||
await OnStatisticsReceived.Invoke(convertedStats);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e, "An unhandled error occured while handling container stats");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ClearCacheAsync()
|
||||
{
|
||||
await CacheLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
Cache.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
CacheLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ServerStatistics[]> GetCacheAsync()
|
||||
{
|
||||
await CacheLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
return Cache.ToArray();
|
||||
}
|
||||
finally
|
||||
{
|
||||
CacheLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
public static ServerStatistics ToServerStatistics(ContainerStatsResponse stats)
|
||||
{
|
||||
double cpuUsage = CalculateCpuUsage(stats);
|
||||
|
||||
ulong usedMemory = stats.MemoryStats?.Usage ?? 0;
|
||||
ulong totalMemory = stats.MemoryStats?.Limit ?? 0;
|
||||
|
||||
ulong outgoingNetwork = 0;
|
||||
ulong ingoingNetwork = 0;
|
||||
|
||||
if (stats.Networks != null)
|
||||
{
|
||||
foreach (var network in stats.Networks.Values)
|
||||
{
|
||||
if (network == null)
|
||||
continue;
|
||||
|
||||
outgoingNetwork += network.TxBytes;
|
||||
ingoingNetwork += network.RxBytes;
|
||||
}
|
||||
}
|
||||
|
||||
ulong writeDisk = stats.StorageStats?.WriteSizeBytes ?? 0;
|
||||
ulong readDisk = stats.StorageStats?.ReadSizeBytes ?? 0;
|
||||
|
||||
return new ServerStatistics(
|
||||
CpuUsage: cpuUsage,
|
||||
UsedMemory: usedMemory,
|
||||
TotalMemory: totalMemory,
|
||||
OutgoingNetwork: outgoingNetwork,
|
||||
IngoingNetwork: ingoingNetwork,
|
||||
WriteDisk: writeDisk,
|
||||
ReadDisk: readDisk
|
||||
);
|
||||
}
|
||||
|
||||
private static double CalculateCpuUsage(ContainerStatsResponse stats)
|
||||
{
|
||||
var cpuStats = stats.CPUStats;
|
||||
var preCpuStats = stats.PreCPUStats;
|
||||
|
||||
if (cpuStats == null || preCpuStats == null)
|
||||
return 0;
|
||||
|
||||
var cpuUsage = cpuStats.CPUUsage;
|
||||
var preCpuUsage = preCpuStats.CPUUsage;
|
||||
|
||||
if (cpuUsage == null || preCpuUsage == null)
|
||||
return 0;
|
||||
|
||||
ulong cpuDelta = cpuUsage.TotalUsage - preCpuUsage.TotalUsage;
|
||||
ulong systemDelta = cpuStats.SystemUsage - preCpuStats.SystemUsage;
|
||||
|
||||
if (systemDelta == 0 || cpuDelta == 0)
|
||||
return 0;
|
||||
|
||||
uint onlineCpus = cpuStats.OnlineCPUs;
|
||||
|
||||
if (onlineCpus == 0 && cpuUsage.PercpuUsage != null)
|
||||
{
|
||||
onlineCpus = (uint)cpuUsage.PercpuUsage.Count;
|
||||
}
|
||||
|
||||
if (onlineCpus == 0)
|
||||
onlineCpus = 1;
|
||||
|
||||
return (double)cpuDelta / systemDelta * onlineCpus * 100.0;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await Cts.CancelAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user