Improved node statistics. Added overview for single nodes and replaced mockup values with api fetched values for nodes list
This commit is contained in:
@@ -1,11 +1,21 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Mono.Unix.Native;
|
||||
using MoonCore.Attributes;
|
||||
using MoonCore.Helpers;
|
||||
using MoonlightServers.Daemon.Models;
|
||||
|
||||
namespace MoonlightServers.Daemon.Helpers;
|
||||
|
||||
[Singleton]
|
||||
public class HostSystemHelper
|
||||
{
|
||||
private readonly ILogger<HostSystemHelper> Logger;
|
||||
|
||||
public HostSystemHelper(ILogger<HostSystemHelper> logger)
|
||||
{
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public string GetOsName()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
@@ -44,6 +54,210 @@ public class HostSystemHelper
|
||||
// Unknown platform
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region CPU Usage
|
||||
|
||||
public async Task<CpuUsageDetails> GetCpuUsage()
|
||||
{
|
||||
var result = new CpuUsageDetails();
|
||||
var perCoreUsages = new List<double>();
|
||||
|
||||
// Initial read
|
||||
var (cpuLastStats, cpuLastSums) = await ReadAllCpuStats();
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
// Second read
|
||||
var (cpuNowStats, cpuNowSums) = await ReadAllCpuStats();
|
||||
|
||||
for (var i = 0; i < cpuNowStats.Length; i++)
|
||||
{
|
||||
var cpuDelta = cpuNowSums[i] - cpuLastSums[i];
|
||||
var cpuIdle = cpuNowStats[i][3] - cpuLastStats[i][3];
|
||||
var cpuUsed = cpuDelta - cpuIdle;
|
||||
|
||||
var usage = 100.0 * cpuUsed / cpuDelta;
|
||||
|
||||
if (i == 0)
|
||||
result.OverallUsage = usage;
|
||||
else
|
||||
perCoreUsages.Add(usage);
|
||||
}
|
||||
|
||||
result.PerCoreUsage = perCoreUsages.ToArray();
|
||||
|
||||
// Get model name
|
||||
var cpuInfoLines = await File.ReadAllLinesAsync("/proc/cpuinfo");
|
||||
var modelLine = cpuInfoLines.FirstOrDefault(x => x.StartsWith("model name"));
|
||||
result.Model = modelLine?.Split(":")[1].Trim() ?? "N/A";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<(long[][] cpuStatsList, long[] cpuSums)> ReadAllCpuStats()
|
||||
{
|
||||
var lines = await File.ReadAllLinesAsync("/proc/stat");
|
||||
|
||||
lines = lines.Where(line => line.StartsWith("cpu"))
|
||||
.TakeWhile(line => line.StartsWith("cpu")) // Ensures only CPU lines are read
|
||||
.ToArray();
|
||||
|
||||
var statsList = new List<long[]>();
|
||||
var sumList = new List<long>();
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Skip(1) // Skip the "cpu" label
|
||||
.ToArray();
|
||||
|
||||
var cpuTimes = parts
|
||||
.Select(long.Parse)
|
||||
.ToArray();
|
||||
|
||||
var sum = cpuTimes.Sum();
|
||||
|
||||
statsList.Add(cpuTimes);
|
||||
sumList.Add(sum);
|
||||
}
|
||||
|
||||
return (statsList.ToArray(), sumList.ToArray());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Memory
|
||||
|
||||
public async Task ClearCachedMemory()
|
||||
{
|
||||
await File.WriteAllTextAsync("/proc/sys/vm/drop_caches", "3");
|
||||
}
|
||||
|
||||
public async Task<MemoryUsageDetails> GetMemoryUsage()
|
||||
{
|
||||
var details = new MemoryUsageDetails();
|
||||
|
||||
var lines = await File.ReadAllLinesAsync("/proc/meminfo");
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
// We want to ignore all non kilobyte values
|
||||
if (!line.Contains("kB"))
|
||||
continue;
|
||||
|
||||
// Split the line up so we can extract the id and the value
|
||||
// to map it to the model field
|
||||
var parts = line.Split(":");
|
||||
|
||||
var id = parts[0];
|
||||
var value = parts[1]
|
||||
.Replace("kB", "")
|
||||
.Trim();
|
||||
|
||||
if (!long.TryParse(value, out var longValue))
|
||||
continue;
|
||||
|
||||
var bytes = ByteConverter.FromKiloBytes(longValue).Bytes;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case "MemTotal":
|
||||
details.Total = bytes;
|
||||
break;
|
||||
|
||||
case "MemFree":
|
||||
details.Free = bytes;
|
||||
break;
|
||||
|
||||
case "MemAvailable":
|
||||
details.Available = bytes;
|
||||
break;
|
||||
|
||||
case "Cached":
|
||||
details.Cached = bytes;
|
||||
break;
|
||||
|
||||
case "SwapTotal":
|
||||
details.SwapTotal = bytes;
|
||||
break;
|
||||
|
||||
case "SwapFree":
|
||||
details.SwapFree = bytes;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Disks
|
||||
|
||||
public async Task<DiskUsageDetails[]> GetDiskUsages()
|
||||
{
|
||||
var details = new List<DiskUsageDetails>();
|
||||
|
||||
// First we need to check which mounts actually exist
|
||||
var diskDevices = new Dictionary<string, string>();
|
||||
string[] ignoredMounts = ["/boot/efi", "/boot"];
|
||||
|
||||
var mountLines = await File.ReadAllLinesAsync("/proc/mounts");
|
||||
|
||||
foreach (var mountLine in mountLines)
|
||||
{
|
||||
var parts = mountLine.Split(" ");
|
||||
|
||||
var device = parts[0];
|
||||
var mountedAt = parts[1];
|
||||
|
||||
// We only want to handle mounted physical devices
|
||||
if (!device.StartsWith("/dev/"))
|
||||
continue;
|
||||
|
||||
// Ignore certain mounts which we dont want to show
|
||||
if (ignoredMounts.Contains(mountedAt))
|
||||
continue;
|
||||
|
||||
diskDevices.Add(device, mountedAt);
|
||||
}
|
||||
|
||||
foreach (var diskMount in diskDevices)
|
||||
{
|
||||
var device = diskMount.Key;
|
||||
var mount = diskMount.Value;
|
||||
|
||||
var statusCode = Syscall.statvfs(mount, out var statvfs);
|
||||
|
||||
if (statusCode != 0)
|
||||
{
|
||||
var error = Stdlib.GetLastError();
|
||||
|
||||
Logger.LogError(
|
||||
"An error occured while checking disk stats for mount {mount}: {error}",
|
||||
mount,
|
||||
error
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Source: https://man7.org/linux/man-pages/man3/statvfs.3.html
|
||||
var detail = new DiskUsageDetails()
|
||||
{
|
||||
Device = device,
|
||||
MountPath = mount,
|
||||
DiskTotal = statvfs.f_blocks * statvfs.f_frsize,
|
||||
DiskFree = statvfs.f_bfree * statvfs.f_frsize,
|
||||
InodesTotal = statvfs.f_files,
|
||||
InodesFree = statvfs.f_ffree
|
||||
};
|
||||
|
||||
details.Add(detail);
|
||||
}
|
||||
|
||||
return details.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using MoonCore.Attributes;
|
||||
|
||||
namespace MoonlightServers.Daemon.Helpers;
|
||||
|
||||
[Singleton]
|
||||
public class OwnProcessHelper
|
||||
{
|
||||
public long GetMemoryUsage()
|
||||
{
|
||||
var process = Process.GetCurrentProcess();
|
||||
var bytes = process.PrivateMemorySize64;
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public TimeSpan GetUptime()
|
||||
{
|
||||
var process = Process.GetCurrentProcess();
|
||||
var uptime = DateTime.Now - process.StartTime;
|
||||
|
||||
return uptime;
|
||||
}
|
||||
|
||||
public int CpuUsage()
|
||||
{
|
||||
var process = Process.GetCurrentProcess();
|
||||
var cpuTime = process.TotalProcessorTime;
|
||||
var wallClockTime = DateTime.UtcNow - process.StartTime.ToUniversalTime();
|
||||
|
||||
var cpuUsage = (int)(100.0 * cpuTime.TotalMilliseconds / wallClockTime.TotalMilliseconds /
|
||||
Environment.ProcessorCount);
|
||||
|
||||
return cpuUsage;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MoonlightServers.Daemon.Helpers;
|
||||
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics;
|
||||
|
||||
namespace MoonlightServers.Daemon.Http.Controllers.Statistics;
|
||||
|
||||
// This controller hosts endpoints for the statistics for the daemon application itself
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/statistics/application")]
|
||||
public class StatisticsApplicationController : Controller
|
||||
{
|
||||
private readonly OwnProcessHelper ProcessHelper;
|
||||
|
||||
public StatisticsApplicationController(OwnProcessHelper processHelper)
|
||||
{
|
||||
ProcessHelper = processHelper;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<StatisticsApplicationResponse> Get()
|
||||
{
|
||||
return new StatisticsApplicationResponse()
|
||||
{
|
||||
Uptime = ProcessHelper.GetUptime(),
|
||||
MemoryUsage = ProcessHelper.GetMemoryUsage(),
|
||||
CpuUsage = ProcessHelper.CpuUsage()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MoonlightServers.Daemon.Helpers;
|
||||
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics;
|
||||
|
||||
namespace MoonlightServers.Daemon.Http.Controllers.Statistics;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/statistics")]
|
||||
public class StatisticsController : Controller
|
||||
{
|
||||
private readonly HostSystemHelper HostSystemHelper;
|
||||
|
||||
public StatisticsController(HostSystemHelper hostSystemHelper)
|
||||
{
|
||||
HostSystemHelper = hostSystemHelper;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<StatisticsResponse> Get()
|
||||
{
|
||||
var response = new StatisticsResponse();
|
||||
|
||||
var cpuUsage = await HostSystemHelper.GetCpuUsage();
|
||||
|
||||
response.Cpu.Model = cpuUsage.Model;
|
||||
response.Cpu.Usage = cpuUsage.OverallUsage;
|
||||
response.Cpu.UsagePerCore = cpuUsage.PerCoreUsage;
|
||||
|
||||
var memoryUsage = await HostSystemHelper.GetMemoryUsage();
|
||||
|
||||
response.Memory.Available = memoryUsage.Available;
|
||||
response.Memory.Cached = memoryUsage.Cached;
|
||||
response.Memory.Free = memoryUsage.Free;
|
||||
response.Memory.Total = memoryUsage.Total;
|
||||
response.Memory.SwapTotal = memoryUsage.SwapTotal;
|
||||
response.Memory.SwapFree = memoryUsage.SwapFree;
|
||||
|
||||
var diskDetails = await HostSystemHelper.GetDiskUsages();
|
||||
|
||||
response.Disks = diskDetails.Select(x => new StatisticsResponse.DiskData()
|
||||
{
|
||||
Device = x.Device,
|
||||
MountPath = x.MountPath,
|
||||
DiskFree = x.DiskFree,
|
||||
DiskTotal = x.DiskTotal,
|
||||
InodesFree = x.InodesFree,
|
||||
InodesTotal = x.InodesTotal
|
||||
}).ToArray();
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MoonlightServers.Daemon.Helpers;
|
||||
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics;
|
||||
|
||||
namespace MoonlightServers.Daemon.Http.Controllers.Statistics;
|
||||
|
||||
// This controller hosts endpoints for the statistics for host system the daemon runs on
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/statistics/host")]
|
||||
public class StatisticsHostController : Controller
|
||||
{
|
||||
private readonly HostSystemHelper HostSystemHelper;
|
||||
|
||||
public StatisticsHostController(HostSystemHelper hostSystemHelper)
|
||||
{
|
||||
HostSystemHelper = hostSystemHelper;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<StatisticsHostResponse> Get()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
OperatingSystem = HostSystemHelper.GetOsName()
|
||||
};
|
||||
}
|
||||
}
|
||||
8
MoonlightServers.Daemon/Models/CpuUsageDetails.cs
Normal file
8
MoonlightServers.Daemon/Models/CpuUsageDetails.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace MoonlightServers.Daemon.Models;
|
||||
|
||||
public class CpuUsageDetails
|
||||
{
|
||||
public string Model { get; set; }
|
||||
public double OverallUsage { get; set; }
|
||||
public double[] PerCoreUsage { get; set; }
|
||||
}
|
||||
11
MoonlightServers.Daemon/Models/DiskUsageDetails.cs
Normal file
11
MoonlightServers.Daemon/Models/DiskUsageDetails.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace MoonlightServers.Daemon.Models;
|
||||
|
||||
public class DiskUsageDetails
|
||||
{
|
||||
public string Device { get; set; }
|
||||
public string MountPath { get; set; }
|
||||
public ulong DiskTotal { get; set; }
|
||||
public ulong DiskFree { get; set; }
|
||||
public ulong InodesTotal { get; set; }
|
||||
public ulong InodesFree { get; set; }
|
||||
}
|
||||
11
MoonlightServers.Daemon/Models/MemoryUsageDetails.cs
Normal file
11
MoonlightServers.Daemon/Models/MemoryUsageDetails.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace MoonlightServers.Daemon.Models;
|
||||
|
||||
public class MemoryUsageDetails
|
||||
{
|
||||
public long Total { get; set; }
|
||||
public long Available { get; set; }
|
||||
public long Free { get; set; }
|
||||
public long Cached { get; set; }
|
||||
public long SwapTotal { get; set; }
|
||||
public long SwapFree { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user