Implemented statistics. Refactored storage abstractions. Added config options for docker and local storage. Added server service and server updating.

This commit is contained in:
2026-03-02 15:51:05 +00:00
parent 52dbd13fb5
commit 2d1b48b0d4
27 changed files with 493 additions and 147 deletions

View File

@@ -1,6 +1,6 @@
namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
public interface IInstallStatistics
public interface IInstallStatistics : IAsyncDisposable
{
public event Func<ServerStatistics, Task>? OnStatisticsReceived;

View File

@@ -2,5 +2,5 @@ namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
public interface IInstallStorage
{
public Task<string> GetHostPathAsync();
public Task<string> GetBindPathAsync();
}

View File

@@ -1,6 +1,6 @@
namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
public interface IRuntimeStatistics
public interface IRuntimeStatistics : IAsyncDisposable
{
public event Func<ServerStatistics, Task>? OnStatisticsReceived;

View File

@@ -2,5 +2,5 @@ namespace MoonlightServers.Daemon.ServerSystem.Abstractions;
public interface IRuntimeStorage
{
public Task<string> GetHostPathAsync();
public Task<string> GetBindPathAsync();
}

View File

@@ -1,12 +1,11 @@
using System.ComponentModel;
using Docker.DotNet.Models;
using MoonlightServers.Daemon.Models;
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
public static class ConfigMapper
public class ContainerConfigMapper
{
public static CreateContainerParameters GetRuntimeConfig(
public CreateContainerParameters GetRuntimeConfig(
string uuid,
string name,
RuntimeConfiguration configuration,
@@ -138,7 +137,7 @@ public static class ConfigMapper
return parameters;
}
public static CreateContainerParameters GetInstallConfig(
public CreateContainerParameters GetInstallConfig(
string uuid,
string name,
RuntimeConfiguration runtimeConfiguration,
@@ -201,7 +200,7 @@ public static class ConfigMapper
return parameters;
}
private static void ApplySharedOptions(
private void ApplySharedOptions(
CreateContainerParameters parameters,
RuntimeConfiguration configuration
)

View File

@@ -31,14 +31,15 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
Logger = logger;
}
public async Task AttachAsync()
{
// Fetch initial logs
Logger.LogTrace("Fetching pre-existing logs from container");
var logResponse = await DockerClient.Containers.GetContainerLogsAsync(
// Fetch initial container logs
Logger.LogTrace("Fetching initial container logs");
using var logStream = await DockerClient.Containers.GetContainerLogsAsync(
ContainerId,
new()
new ContainerLogsParameters()
{
Follow = false,
ShowStderr = true,
@@ -46,94 +47,50 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
}
);
// Append to cache
var logs = await logResponse.ReadOutputToEndAsync(Cts.Token);
// and process it
await ProcessStreamAsync(logStream, Cts.Token);
await CacheLock.WaitAsync(Cts.Token);
try
{
Cache.Add(logs.stdout);
Cache.Add(logs.stderr);
}
finally
{
CacheLock.Release();
}
// Stream new logs
Logger.LogTrace("Starting log streaming");
// After that we can actually start streaming the new logs
Logger.LogTrace("Attaching to container");
Stream = await DockerClient.Containers.AttachContainerAsync(
ContainerId,
new ContainerAttachParameters()
{
Stderr = true,
Stdin = true,
Stdout = true,
Stream = true
},
Cts.Token
);
Task.Run(async () =>
{
var capturedCt = Cts.Token;
Logger.LogTrace("Entered streaming loop");
Logger.LogTrace("Starting attach loop");
while (!capturedCt.IsCancellationRequested)
while (!Cts.IsCancellationRequested)
{
try
{
using var stream = await DockerClient.Containers.AttachContainerAsync(
ContainerId,
new ContainerAttachParameters()
{
Stderr = true,
Stdin = true,
Stdout = true,
Stream = true
},
capturedCt
);
// Make stream accessible from the outside
Stream = stream;
const int bufferSize = 1024;
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
while (!capturedCt.IsCancellationRequested)
if (Stream == null) // Triggers when e.g. a connection issue occurs cause the catch clause resets the stream
{
try
{
var readResult = await stream.ReadOutputAsync(buffer, 0, bufferSize, capturedCt);
Logger.LogTrace("Reattaching to container");
if (readResult.Count > 0)
Stream = await DockerClient.Containers.AttachContainerAsync(
ContainerId,
new ContainerAttachParameters()
{
var decodedBuffer = Encoding.UTF8.GetString(buffer, 0, readResult.Count);
await CacheLock.WaitAsync(capturedCt);
try
{
if (Cache.Count > 300)
Cache.RemoveRange(0, 50);
Cache.Add(decodedBuffer);
}
finally
{
CacheLock.Release();
}
if (OnOutput != null)
await OnOutput.Invoke(decodedBuffer);
}
if (readResult.EOF)
break;
}
catch (OperationCanceledException)
{
// Ignored
}
catch (Exception e)
{
Logger.LogError(e, "An unhandled error occured while processing container stream");
}
Stderr = true,
Stdin = true,
Stdout = true,
Stream = true
},
Cts.Token
);
}
ArrayPool<byte>.Shared.Return(buffer);
await ProcessStreamAsync(Stream, Cts.Token);
}
catch (OperationCanceledException)
{
@@ -141,14 +98,57 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
}
catch (Exception e)
{
Logger.LogError(e, "An unhandled error occured while handling container attaching");
Logger.LogError(e, "An unhandled error occured while processing container stream");
}
finally
{
Stream?.Dispose();
Stream = null;
}
}
Logger.LogTrace("Attach loop exited");
Logger.LogTrace("Exited streaming loop");
});
}
private async Task ProcessStreamAsync(MultiplexedStream stream, CancellationToken cancellationToken)
{
const int bufferSize = 1024;
var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
while (!cancellationToken.IsCancellationRequested)
{
var readResult = await stream.ReadOutputAsync(buffer, 0, bufferSize, cancellationToken);
if (readResult.Count > 0)
{
var decodedBuffer = Encoding.UTF8.GetString(buffer, 0, readResult.Count);
await CacheLock.WaitAsync(cancellationToken);
try
{
if (Cache.Count > 300)
Cache.RemoveRange(0, 50);
Cache.Add(decodedBuffer);
}
finally
{
CacheLock.Release();
}
if (OnOutput != null)
await OnOutput.Invoke(decodedBuffer);
}
if (readResult.EOF)
break;
}
ArrayPool<byte>.Shared.Return(buffer);
}
public async Task WriteInputAsync(string value)
{
if (Stream == null)
@@ -188,6 +188,8 @@ public class DockerConsole : IRuntimeConsole, IInstallConsole, IAsyncDisposable
public async ValueTask DisposeAsync()
{
Logger.LogTrace("Disposing");
await Cts.CancelAsync();
Stream?.Dispose();
CacheLock.Dispose();

View File

@@ -27,7 +27,7 @@ public class DockerInstallEnv : IInstallEnvironment
Logger = logger;
EventService = eventService;
InnerStatistics = new DockerStatistics();
InnerStatistics = new DockerStatistics(containerId, dockerClient, logger);
InnerConsole = new DockerConsole(containerId, dockerClient, logger);
EventService.OnContainerDied += HandleDieEventAsync;
@@ -59,5 +59,6 @@ public class DockerInstallEnv : IInstallEnvironment
EventService.OnContainerDied -= HandleDieEventAsync;
await InnerConsole.DisposeAsync();
await InnerStatistics.DisposeAsync();
}
}

View File

@@ -9,15 +9,21 @@ public class DockerInstallEnvService : IInstallEnvironmentService
private readonly DockerClient DockerClient;
private readonly ILoggerFactory LoggerFactory;
private readonly DockerEventService DockerEventService;
private readonly ContainerConfigMapper ConfigMapper;
private const string NameTemplate = "ml-install-{0}";
public DockerInstallEnvService(DockerClient dockerClient, ILoggerFactory loggerFactory,
DockerEventService dockerEventService)
public DockerInstallEnvService(
DockerClient dockerClient,
ILoggerFactory loggerFactory,
DockerEventService dockerEventService,
ContainerConfigMapper configMapper
)
{
DockerClient = dockerClient;
LoggerFactory = loggerFactory;
DockerEventService = dockerEventService;
ConfigMapper = configMapper;
}
public async Task<IInstallEnvironment?> FindAsync(string id)
@@ -28,8 +34,7 @@ public class DockerInstallEnvService : IInstallEnvironmentService
string.Format(NameTemplate, id)
);
var logger =
LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
var logger = LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
return new DockerInstallEnv(dockerInspect.ID, DockerClient, logger, DockerEventService);
}
@@ -63,8 +68,8 @@ public class DockerInstallEnvService : IInstallEnvironmentService
// Ignored
}
var runtimeStoragePath = await runtimeStorage.GetHostPathAsync();
var installStoragePath = await installStorage.GetHostPathAsync();
var runtimeStoragePath = await runtimeStorage.GetBindPathAsync();
var installStoragePath = await installStorage.GetBindPathAsync();
var parameters = ConfigMapper.GetInstallConfig(
id,
@@ -74,7 +79,7 @@ public class DockerInstallEnvService : IInstallEnvironmentService
runtimeStoragePath,
installStoragePath
);
var container = await DockerClient.Containers.CreateContainerAsync(parameters);
var logger = LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");

View File

@@ -25,7 +25,7 @@ public class DockerRuntimeEnv : IRuntimeEnvironment
DockerClient = dockerClient;
EventService = eventService;
InnerStatistics = new DockerStatistics();
InnerStatistics = new DockerStatistics(containerId, dockerClient, logger);
InnerConsole = new DockerConsole(containerId, dockerClient, logger);
EventService.OnContainerDied += HandleDieEventAsync;
@@ -57,5 +57,6 @@ public class DockerRuntimeEnv : IRuntimeEnvironment
EventService.OnContainerDied -= HandleDieEventAsync;
await InnerConsole.DisposeAsync();
await InnerStatistics.DisposeAsync();
}
}

View File

@@ -9,15 +9,21 @@ public class DockerRuntimeEnvService : IRuntimeEnvironmentService
private readonly DockerClient DockerClient;
private readonly ILoggerFactory LoggerFactory;
private readonly DockerEventService DockerEventService;
private readonly ContainerConfigMapper ConfigMapper;
private const string NameTemplate = "ml-runtime-{0}";
public DockerRuntimeEnvService(DockerClient dockerClient, ILoggerFactory loggerFactory,
DockerEventService dockerEventService)
public DockerRuntimeEnvService(
DockerClient dockerClient,
ILoggerFactory loggerFactory,
DockerEventService dockerEventService,
ContainerConfigMapper configMapper
)
{
DockerClient = dockerClient;
LoggerFactory = loggerFactory;
DockerEventService = dockerEventService;
ConfigMapper = configMapper;
}
public async Task<IRuntimeEnvironment?> FindAsync(string id)
@@ -28,8 +34,7 @@ public class DockerRuntimeEnvService : IRuntimeEnvironmentService
string.Format(NameTemplate, id)
);
var logger =
LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
var logger = LoggerFactory.CreateLogger($"MoonlightServers.Daemon.ServerSystem.Implementations.Docker({id})");
return new DockerRuntimeEnv(dockerInspect.ID, DockerClient, logger, DockerEventService);
}
@@ -61,7 +66,7 @@ public class DockerRuntimeEnvService : IRuntimeEnvironmentService
// Ignored
}
var storagePath = await storage.GetHostPathAsync();
var storagePath = await storage.GetBindPathAsync();
var parameters = ConfigMapper.GetRuntimeConfig(
id,
@@ -78,9 +83,7 @@ public class DockerRuntimeEnvService : IRuntimeEnvironmentService
}
public Task UpdateAsync(IRuntimeEnvironment environment, RuntimeConfiguration configuration)
{
throw new NotImplementedException();
}
=> Task.CompletedTask;
public async Task DeleteAsync(IRuntimeEnvironment environment)
{

View File

@@ -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();
}
}

View File

@@ -1,4 +1,6 @@
using Docker.DotNet;
using Microsoft.Extensions.Options;
using MoonlightServers.Daemon.Configuration;
using MoonlightServers.Daemon.ServerSystem.Abstractions;
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
@@ -7,16 +9,23 @@ public static class Extensions
{
public static void AddDockerServices(this IServiceCollection collection)
{
var client = new DockerClientBuilder()
.WithEndpoint(new Uri("unix:///var/run/docker.sock"))
.Build();
collection.AddOptions<DockerOptions>().BindConfiguration("Moonlight:Docker");
collection.AddSingleton(client);
collection.AddSingleton(sp =>
{
var options = sp.GetRequiredService<IOptions<DockerOptions>>();
return new DockerClientBuilder()
.WithEndpoint(new Uri(options.Value.SocketUri))
.Build();
});
collection.AddSingleton<DockerEventService>();
collection.AddHostedService(sp => sp.GetRequiredService<DockerEventService>());
collection.AddSingleton<IRuntimeEnvironmentService, DockerRuntimeEnvService>();
collection.AddSingleton<IInstallEnvironmentService, DockerInstallEnvService>();
collection.AddSingleton<ContainerConfigMapper>();
}
}

View File

@@ -1,3 +1,4 @@
using MoonlightServers.Daemon.Configuration;
using MoonlightServers.Daemon.ServerSystem.Abstractions;
namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
@@ -8,5 +9,7 @@ public static class Extensions
{
services.AddSingleton<IRuntimeStorageService, LocalRuntimeStorageService>();
services.AddSingleton<IInstallStorageService, LocalInstallStorageService>();
services.AddOptions<LocalStorageOptions>().BindConfiguration("Moonlight:LocalStorage");
}
}

View File

@@ -4,12 +4,12 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
public class LocalInstallStorage : IInstallStorage
{
public string HostPath { get; }
public string BindPath { get; }
public LocalInstallStorage(string hostPath)
public LocalInstallStorage(string bindPath)
{
HostPath = hostPath;
BindPath = bindPath;
}
public Task<string> GetHostPathAsync() => Task.FromResult(HostPath);
public Task<string> GetBindPathAsync() => Task.FromResult(BindPath);
}

View File

@@ -1,3 +1,5 @@
using Microsoft.Extensions.Options;
using MoonlightServers.Daemon.Configuration;
using MoonlightServers.Daemon.Models;
using MoonlightServers.Daemon.ServerSystem.Abstractions;
@@ -5,11 +7,16 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
public class LocalInstallStorageService : IInstallStorageService
{
private const string HostPathTemplate = "./mldaemon/install/{0}";
private readonly IOptions<LocalStorageOptions> Options;
public LocalInstallStorageService(IOptions<LocalStorageOptions> options)
{
Options = options;
}
public Task<IInstallStorage?> FindAsync(string id)
{
var path = string.Format(HostPathTemplate, id);
var path = Path.Combine(Options.Value.InstallPath, id);
if (!Directory.Exists(path))
return Task.FromResult<IInstallStorage?>(null);
@@ -19,7 +26,7 @@ public class LocalInstallStorageService : IInstallStorageService
public Task<IInstallStorage> CreateAsync(string id, RuntimeConfiguration runtimeConfiguration, InstallConfiguration installConfiguration)
{
var path = string.Format(HostPathTemplate, id);
var path = Path.Combine(Options.Value.InstallPath, id);
Directory.CreateDirectory(path);
@@ -35,8 +42,8 @@ public class LocalInstallStorageService : IInstallStorageService
);
}
if(Directory.Exists(localInstallStorage.HostPath))
Directory.Delete(localInstallStorage.HostPath, true);
if(Directory.Exists(localInstallStorage.BindPath))
Directory.Delete(localInstallStorage.BindPath, true);
return Task.CompletedTask;
}

View File

@@ -4,12 +4,12 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
public class LocalRuntimeStorage : IRuntimeStorage
{
public string HostPath { get; }
public string BindPath { get; }
public LocalRuntimeStorage(string hostPath)
public LocalRuntimeStorage(string bindPath)
{
HostPath = hostPath;
BindPath = bindPath;
}
public Task<string> GetHostPathAsync() => Task.FromResult(HostPath);
public Task<string> GetBindPathAsync() => Task.FromResult(BindPath);
}

View File

@@ -1,3 +1,5 @@
using Microsoft.Extensions.Options;
using MoonlightServers.Daemon.Configuration;
using MoonlightServers.Daemon.Models;
using MoonlightServers.Daemon.ServerSystem.Abstractions;
@@ -5,11 +7,16 @@ namespace MoonlightServers.Daemon.ServerSystem.Implementations.Local;
public class LocalRuntimeStorageService : IRuntimeStorageService
{
private const string HostPathTemplate = "./mldaemon/runtime/{0}";
private readonly IOptions<LocalStorageOptions> Options;
public LocalRuntimeStorageService(IOptions<LocalStorageOptions> options)
{
Options = options;
}
public Task<IRuntimeStorage?> FindAsync(string id)
{
var path = string.Format(HostPathTemplate, id);
var path = Path.Combine(Options.Value.RuntimePath, id);
if (!Directory.Exists(path))
return Task.FromResult<IRuntimeStorage?>(null);
@@ -19,7 +26,7 @@ public class LocalRuntimeStorageService : IRuntimeStorageService
public Task<IRuntimeStorage> CreateAsync(string id, RuntimeConfiguration configuration)
{
var path = string.Format(HostPathTemplate, id);
var path = Path.Combine(Options.Value.RuntimePath, id);
Directory.CreateDirectory(path);
@@ -38,8 +45,8 @@ public class LocalRuntimeStorageService : IRuntimeStorageService
);
}
if(Directory.Exists(localRuntimeStorage.HostPath))
Directory.Delete(localRuntimeStorage.HostPath, true);
if(Directory.Exists(localRuntimeStorage.BindPath))
Directory.Delete(localRuntimeStorage.BindPath, true);
return Task.CompletedTask;
}

View File

@@ -74,7 +74,7 @@ public partial class Server
InstallStorage = await InstallStorageService.CreateAsync(Uuid, RuntimeConfiguration, InstallConfiguration);
// Write install script
var installStoragePath = await InstallStorage.GetHostPathAsync();
var installStoragePath = await InstallStorage.GetBindPathAsync();
await File.WriteAllTextAsync(
Path.Combine(installStoragePath, "install.sh"),

View File

@@ -0,0 +1,34 @@
namespace MoonlightServers.Daemon.ServerSystem;
public partial class Server
{
public async Task UpdateAsync()
{
await Lock.WaitAsync();
try
{
if (State is ServerState.Online or ServerState.Starting or ServerState.Stopping)
{
Logger.LogTrace("Fetching latest runtime configuration");
RuntimeConfiguration = await ConfigurationService.GetRuntimeConfigurationAsync(Uuid);
if (RuntimeEnvironment != null)
{
Logger.LogTrace("Updating runtime environment");
await RuntimeEnvironmentService.UpdateAsync(RuntimeEnvironment, RuntimeConfiguration);
}
if (RuntimeStorage != null)
{
Logger.LogTrace("Updating runtime storage");
await RuntimeStorageService.UpdateAsync(RuntimeStorage, RuntimeConfiguration);
}
}
}
finally
{
Lock.Release();
}
}
}

View File

@@ -67,11 +67,12 @@ public partial class Server : IAsyncDisposable
private async Task OnConsoleMessageAsync(string message)
{
Console.WriteLine($"Console: {message}");
Console.Write($"Console: {message}");
}
private async Task OnStatisticsReceivedAsync(ServerStatistics statistics)
{
Logger.LogTrace("{cpu:F} {used:F} {total:F}", statistics.CpuUsage, statistics.UsedMemory / 1024 / 1024, statistics.TotalMemory / 1024 / 1024);
}
private Task ChangeStateAsync(ServerState newState)
@@ -88,7 +89,7 @@ public partial class Server : IAsyncDisposable
if (RuntimeEnvironment != null)
{
Logger.LogTrace("Detaching and disposing runtime environment");
Logger.LogTrace("Detaching events and disposing runtime environment");
RuntimeEnvironment.Console.OnOutput -= OnConsoleMessageAsync;
RuntimeEnvironment.Statistics.OnStatisticsReceived -= OnStatisticsReceivedAsync;
@@ -99,7 +100,7 @@ public partial class Server : IAsyncDisposable
if (InstallEnvironment != null)
{
Logger.LogTrace("Detaching and disposing install environment");
Logger.LogTrace("Detaching events and disposing install environment");
InstallEnvironment.Console.OnOutput -= OnConsoleMessageAsync;
InstallEnvironment.Statistics.OnStatisticsReceived -= OnStatisticsReceivedAsync;

View File

@@ -1,3 +1,11 @@
namespace MoonlightServers.Daemon.ServerSystem;
public record ServerStatistics();
public record ServerStatistics(
double CpuUsage,
ulong UsedMemory,
ulong TotalMemory,
ulong OutgoingNetwork,
ulong IngoingNetwork,
ulong WriteDisk,
ulong ReadDisk
);