Files
Servers/MoonlightServers.Daemon/ServerSystem/Implementations/Docker/DockerStatistics.cs

193 lines
5.1 KiB
C#

using Docker.DotNet;
using Docker.DotNet.Models;
using MoonlightServers.Daemon.ServerSystem.Abstractions;
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
public class DockerStatistics : IRuntimeStatistics, IInstallStatistics
{
public event Func<ServerStatistics, Task>? OnStatisticsReceived;
private readonly string ContainerId;
private readonly DockerClient DockerClient;
private readonly ILogger Logger;
private readonly List<ServerStatistics> Cache = new(102);
private readonly SemaphoreSlim CacheLock = new(1, 1);
private readonly CancellationTokenSource Cts = new();
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();
}
}