193 lines
5.1 KiB
C#
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();
|
|
}
|
|
} |