feat: GSS2.Test refactor + ComputeResourcesService

This commit is contained in:
2026-01-26 13:54:29 +03:00
parent d4e7c300f4
commit 58f307c444
25 changed files with 798 additions and 87 deletions
+2
View File
@@ -74,6 +74,8 @@ public static class DependencyInjectionHelper
return new CameraService(logger, serviceConfiguration, autoInitialize); return new CameraService(logger, serviceConfiguration, autoInitialize);
} }
); );
public static IServiceCollection AddComputeResourcesService(this IServiceCollection services) => services
.AddSingleton<ComputeResourcesService>();
public static IServiceCollection AddAmineContentCalibrationContext(this IServiceCollection services, IConfigurationManager configuration) => services public static IServiceCollection AddAmineContentCalibrationContext(this IServiceCollection services, IConfigurationManager configuration) => services
.AddDbContext<AmineContentCalibrationContext>( .AddDbContext<AmineContentCalibrationContext>(
@@ -0,0 +1,141 @@
using System.Globalization;
using GSS2.Core.Extentions;
using Microsoft.Extensions.Logging;
namespace GSS2.Core.Hardware;
public class ComputeResourcesService
{
public record CpuUsage(double AllUsagePercent, List<double> CoreUsagePercents);
public record CpuTemperature(double Current, double Critical);
public record MemoryUsage(
long TotalB, long UsedB, long AvailableB,
double TotalkB, double UsedkB, double AvailablekB,
double TotalMB, double UsedMB, double AvailableMB,
double TotalGB, double UsedGB, double AvailableGB)
{
public MemoryUsage(long totalB, long usedB, long availableB) :
this(
TotalB: totalB, UsedB: usedB, AvailableB: availableB,
TotalkB: totalB / 1000, UsedkB: usedB / 1000, AvailablekB: availableB / 1000,
TotalMB: totalB / Math.Pow(1000.0, 2), UsedMB: usedB / Math.Pow(1000.0, 2), AvailableMB: availableB / Math.Pow(1000.0, 2),
TotalGB: totalB / Math.Pow(1000.0, 3), UsedGB: usedB / Math.Pow(1000.0, 3), AvailableGB: availableB / Math.Pow(1000.0, 3)
)
{ }
}
public record ComputeResourcesSnapshot(CpuUsage CpuUsage, MemoryUsage MemoryUsage, CpuTemperature CpuTemperature, DateTime Timestamp);
private readonly ILogger<ComputeResourcesService> _logger;
private (long idle, long total)? _prevAll = null;
private Dictionary<int, (long idle, long total)?>? _prevCores = null;
public ComputeResourcesService(ILogger<ComputeResourcesService> logger)
{
_logger = logger;
}
public ComputeResourcesSnapshot GetSnapshot()
{
return new ComputeResourcesSnapshot(
CpuUsage: GetCpuUsage(),
CpuTemperature: GetCpuTemperature(),
MemoryUsage: GetMemoryUsage(),
Timestamp: DateTime.UtcNow
);
}
private CpuUsage GetCpuUsage()
{
static (double usage, long idle, long total) ParceLines(string line, (long idle, long total)? prev)
{
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
long user = long.Parse(parts[1]);
long nice = long.Parse(parts[2]);
long system = long.Parse(parts[3]);
long idle = long.Parse(parts[4]);
long iowait = line.Length > 5 ? long.Parse(parts[5]) : 0;
long irq = line.Length > 6 ? long.Parse(parts[6]) : 0;
long softirq = line.Length > 7 ? long.Parse(parts[7]) : 0;
long idleTime = idle + iowait;
long totalTime = idleTime + user + nice + system + irq + softirq;
double usage = 0;
if (prev is not null)
{
long totalDiff = totalTime - prev.Value.total;
long idleDiff = idleTime - prev.Value.idle;
if (totalDiff > 0)
usage = (double)(totalDiff - idleDiff) / totalDiff * 100.0;
}
return (usage, idleTime, totalTime);
}
var lines = File.ReadLines("/proc/stat").Where(l => l.StartsWith("cpu"));
if (lines.Count() == 0)
return new CpuUsage(0, new List<double>());
var (allUsage, allIdle, allTotal) = ParceLines(lines.First(), _prevAll);
_prevAll = (allIdle, allTotal);
var coreUsages = new List<double>();
foreach (var (i, line) in lines.Skip(1).Enumerate())
{
if (_prevCores is null)
_prevCores = new Dictionary<int, (long idle, long total)?>();
if (!_prevCores.ContainsKey(i))
_prevCores.Add(i, null);
var (coreUsage, coreIdle, coreTotal) = ParceLines(line, _prevCores[i]);
_prevCores[i] = (coreIdle, coreTotal);
coreUsages.Add(coreUsage);
}
return new CpuUsage(
AllUsagePercent: allUsage,
CoreUsagePercents: coreUsages
);
}
private CpuTemperature GetCpuTemperature()
{
var currentStr = File.ReadAllText("/sys/class/thermal/thermal_zone0/temp").Trim();
var current = int.Parse(currentStr) / 1000.0;
var criticalFile = Directory.GetFiles("/sys/class/thermal/thermal_zone0/", "trip_point_*_type")
.FirstOrDefault(f => File.ReadAllText(f).Trim() == "critical")?
.Replace("_type", "_temp");
double critical = 100.0;
if (criticalFile is not null)
{
var criticalStr = File.ReadAllText(criticalFile).Trim();
critical = int.Parse(criticalStr) / 1000.0;
}
return new CpuTemperature(
Current: current,
Critical: critical
);
}
private MemoryUsage GetMemoryUsage()
{
var memInfo = File.ReadAllLines("/proc/meminfo")
.Select(l => l.Split(':', 2))
.ToDictionary(
p => p[0],
p => long.Parse(p[1].Trim().Split(' ')[0], CultureInfo.InvariantCulture) * 1024
);
long total = memInfo["MemTotal"];
long available = memInfo.ContainsKey("MemAvailable") ? memInfo["MemAvailable"] : memInfo["MemFree"];
long used = total - available;
return new MemoryUsage(totalB: total, usedB: used, availableB: available);
}
}
+9 -6
View File
@@ -2,6 +2,8 @@ using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using GSS2.Test.ViewModels;
namespace GSS2.Test; namespace GSS2.Test;
public partial class App : Application public partial class App : Application
@@ -13,13 +15,14 @@ public partial class App : Application
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
var mainWindowViewModel = Program.ApplicationHost.Services.GetRequiredService<MainWindowViewModel>();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = new MainWindow() {
{ desktop.Startup += (_, _) => Program.ApplicationHost.RunAsync();
DataContext = mainWindowViewModel desktop.Exit += (_, _) => Program.ApplicationHost.StopAsync();
};
desktop.MainWindow = new MainWindow();
desktop.MainWindow.Activated += (_, _) => desktop.MainWindow.DataContext = Program.ApplicationHost.Services.GetRequiredService<MainWindowViewModel>();
}
base.OnFrameworkInitializationCompleted(); base.OnFrameworkInitializationCompleted();
} }
} }
+10 -12
View File
@@ -1,19 +1,17 @@
using Microsoft.EntityFrameworkCore; using GSS2.Test.Views;
using Microsoft.IdentityModel.Protocols.Configuration; using GSS2.Test.ViewModels;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using GSS2.Core.Hardware;
using GSS2.Core.Analysis.AmineContent.Database;
namespace GSS2.Test; namespace GSS2.Test;
public static class DependencyInjectionHelper public static class DependencyInjectionHelper
{ {
public static IServiceCollection AddUi(this IServiceCollection services) => public static IServiceCollection AddViewsAndModels(this IServiceCollection services) =>
services services
.AddTransient<CameraView>() .AddSingleton<MainWindowViewModel>()
.AddTransient<CameraViewModel>() .AddSingleton<MainView>()
.AddTransient<MainWindowViewModel>(); .AddSingleton<MainViewModel>()
.AddSingleton<ResourcesView>()
.AddSingleton<ResourcesViewModel>();
// .AddSingleton<CameraView>()
// .AddSingleton<CameraViewModel>();
} }
+5 -1
View File
@@ -1,6 +1,8 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using GSS2.Test.ViewModels;
namespace GSS2.Test; namespace GSS2.Test;
public class DependencyInjectionViewLocator : IDataTemplate public class DependencyInjectionViewLocator : IDataTemplate
@@ -10,7 +12,9 @@ public class DependencyInjectionViewLocator : IDataTemplate
if (data is null) if (data is null)
return null; return null;
var name = data.GetType().FullName?.Replace("ViewModel", "View"); var name = data.GetType().FullName?
.Replace("ViewModels", "Views")
.Replace("ViewModel", "View");
if (name is null) if (name is null)
return new TextBlock { Text = $"Not found: {data}" }; return new TextBlock { Text = $"Not found: {data}" };
var type = Type.GetType(name); var type = Type.GetType(name);
+1
View File
@@ -12,6 +12,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" Version="2.0.0-rc6.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
<PackageReference Include="Avalonia" Version="11.3.11" /> <PackageReference Include="Avalonia" Version="11.3.11" />
@@ -1,6 +1,6 @@
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
namespace GSS2.Test; namespace GSS2.Test.Logging;
public sealed class ConsoleFormatter : Microsoft.Extensions.Logging.Console.ConsoleFormatter public sealed class ConsoleFormatter : Microsoft.Extensions.Logging.Console.ConsoleFormatter
{ {
@@ -1,4 +1,4 @@
namespace GSS2.Test; namespace GSS2.Test.Logging;
public sealed class FileLogger : ILogger public sealed class FileLogger : ILogger
{ {
@@ -1,6 +1,4 @@
using Microsoft.Extensions.Logging; namespace GSS2.Test.Logging;
namespace GSS2.Test;
public sealed class FileLoggerProvider : ILoggerProvider public sealed class FileLoggerProvider : ILoggerProvider
{ {
@@ -1,8 +1,6 @@
using LibCameraSharp; using LibCameraSharp;
using Microsoft.Extensions.Logging; namespace GSS2.Test.Logging;
namespace GSS2.Test;
public sealed class LibCameraLogSink : IDisposable public sealed class LibCameraLogSink : IDisposable
{ {
+5 -10
View File
@@ -1,16 +1,11 @@
<Window xmlns="https://github.com/avaloniaui" <Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="using:GSS2.Test" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="using:GSS2.Test.ViewModels"
x:DataType="vm:MainWindowViewModel"
x:Class="GSS2.Test.MainWindow" x:Class="GSS2.Test.MainWindow"
x:DataType="local:MainWindowViewModel" Title="{Binding Title}">
Title="GSS2.Test">
<Design.DataContext> <ContentControl Content="{Binding MainViewModel}"/>
<local:MainWindowViewModel/>
</Design.DataContext>
<ContentControl x:Name="cameraView" Content="{Binding CameraViewModel}"/>
<!-- <local:CameraView x:Name="cameraView"/> -->
</Window> </Window>
-1
View File
@@ -1,5 +1,4 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Threading;
namespace GSS2.Test; namespace GSS2.Test;
-31
View File
@@ -1,31 +0,0 @@
using Avalonia.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using GSS2.Core.Hardware;
namespace GSS2.Test;
public partial class MainWindowViewModel : ViewModelBase
{
private readonly CameraService _cameraService;
private readonly IlluminatorService _illuminatorService;
private readonly LightsService _lightsService;
private readonly ILogger<MainWindowViewModel> _logger;
[ObservableProperty] private CameraViewModel _cameraViewModel;
public MainWindowViewModel(
CameraService cameraService,
IlluminatorService illuminatorService,
LightsService lightsService,
ILogger<MainWindowViewModel> logger,
CameraViewModel cameraViewModel
)
{
_cameraService = cameraService;
_illuminatorService = illuminatorService;
_lightsService = lightsService;
_logger = logger;
CameraViewModel = cameraViewModel;
}
}
+5 -2
View File
@@ -6,6 +6,9 @@ using GSS2.Core.Analysis.AmineContent.Database;
using Avalonia; using Avalonia;
using Avalonia.Vulkan; using Avalonia.Vulkan;
using GSS2.Test.Logging;
using ConsoleFormatter = GSS2.Test.Logging.ConsoleFormatter;
namespace GSS2.Test; namespace GSS2.Test;
@@ -21,7 +24,6 @@ public class Program
[STAThread] [STAThread]
public static void Main(string[] args) public static void Main(string[] args)
{ {
ApplicationHost.RunAsync();
BuildAvaloniaApp() BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args); .StartWithClassicDesktopLifetime(args);
} }
@@ -44,8 +46,9 @@ public class Program
builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity"); builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity");
builder.Services.AddIlluminatorService("Hardware:Illuminator"); builder.Services.AddIlluminatorService("Hardware:Illuminator");
builder.Services.AddCameraService("Hardware:Camera", true); builder.Services.AddCameraService("Hardware:Camera", true);
builder.Services.AddComputeResourcesService();
builder.Services.AddUi(); builder.Services.AddViewsAndModels();
var host = builder.Build(); var host = builder.Build();
host.Services.GetRequiredService<LibCameraLogSink>(); host.Services.GetRequiredService<LibCameraLogSink>();
@@ -2,7 +2,7 @@ using GSS2.Core.Hardware;
using LibCameraSharp; using LibCameraSharp;
namespace GSS2.Test; namespace GSS2.Test.ViewModels;
public class CameraViewModel : ViewModelBase public class CameraViewModel : ViewModelBase
{ {
@@ -13,6 +13,7 @@ public class CameraViewModel : ViewModelBase
{ {
_cameraService = cameraService; _cameraService = cameraService;
_logger = logger; _logger = logger;
_logger.LogInformation("Initialized");
} }
public async Task<(FrameBuffer, StreamConfiguration)?> CaptureImage(int bufferId) public async Task<(FrameBuffer, StreamConfiguration)?> CaptureImage(int bufferId)
+18
View File
@@ -0,0 +1,18 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace GSS2.Test.ViewModels;
public partial class MainViewModel : ViewModelBase
{
private readonly ILogger<MainViewModel> _logger;
[ObservableProperty] private CameraViewModel _cameraViewModel;
[ObservableProperty] private ResourcesViewModel _resourcesViewModel;
public MainViewModel(ILogger<MainViewModel> logger, ResourcesViewModel resourcesViewModel)
{
_logger = logger;
// CameraViewModel = cameraViewModel;
ResourcesViewModel = resourcesViewModel;
_logger.LogInformation("{} initialized", nameof(MainViewModel));
}
}
@@ -0,0 +1,18 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace GSS2.Test.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
[ObservableProperty] private string _title;
[ObservableProperty] private MainViewModel _mainViewModel;
private readonly ILogger<MainWindowViewModel> _logger;
public MainWindowViewModel(ILogger<MainWindowViewModel> logger, MainViewModel mainViewModel)
{
_logger = logger;
MainViewModel = mainViewModel;
Title = "GSS2.Test";
_logger.LogInformation("{} initialized", nameof(MainWindowViewModel));
}
}
+458
View File
@@ -0,0 +1,458 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using Avalonia.Threading;
using LiveChartsCore;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Painting;
using SkiaSharp;
using GSS2.Core.Hardware;
using LiveChartsCore.SkiaSharpView.Avalonia;
using LiveChartsCore.Defaults;
namespace GSS2.Test.ViewModels;
public partial class ResourcesViewModel : ViewModelBase, IDisposable
{
private bool _disposedValue;
private readonly ComputeResourcesService _computeResourcesService;
private readonly ILogger<ResourcesViewModel> _logger;
[ObservableProperty] private ObservableCollection<ISeries> _cpuUsageSeries;
[ObservableProperty] private ObservableCollection<Axis> _cpuUsageSeriesYAxes;
[ObservableProperty] private ObservableCollection<Axis> _cpuUsageSeriesXAxes;
[ObservableProperty] private ObservableCollection<ISeries> _cpuUsagePieSeries;
[ObservableProperty] private ObservableCollection<ISeries> _cpu0UsagePieSeries;
[ObservableProperty] private ObservableCollection<ISeries> _cpu1UsagePieSeries;
[ObservableProperty] private ObservableCollection<ISeries> _cpu2UsagePieSeries;
[ObservableProperty] private ObservableCollection<ISeries> _cpu3UsagePieSeries;
[ObservableProperty] private ObservableCollection<ISeries> _cpuTemperatureSeries;
[ObservableProperty] private ObservableCollection<Axis> _cpuTemperatureSeriesYAxes;
[ObservableProperty] private ObservableCollection<Axis> _cpuTemperatureSeriesXAxes;
[ObservableProperty] private ObservableCollection<ISeries> _cpuTemperaturePieSeries;
[ObservableProperty] private ObservableCollection<ISeries> _memoryUsageSeries;
[ObservableProperty] private ObservableCollection<Axis> _memoryUsageSeriesYAxes;
[ObservableProperty] private ObservableCollection<Axis> _memoryUsageSeriesXAxes;
[ObservableProperty] private ObservableCollection<ISeries> _memoryUsagePieSeries;
[ObservableProperty] private int _intervalMs = 500;
[ObservableProperty] private int _chartsHistorySize = 120;
private readonly System.Timers.Timer _updateTimer;
public ResourcesViewModel(ComputeResourcesService computeResourcesService, ILogger<ResourcesViewModel> logger)
{
_computeResourcesService = computeResourcesService;
_logger = logger;
CpuUsageSeries = [
new LineSeries<double>
{
Name = "Нагрузка ЦПУ, %",
Values = new ObservableCollection<double>(Enumerable.Repeat(0.0, _chartsHistorySize)),
Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(144), 0),
GeometrySize = 0,
ShowDataLabels = false,
Stroke = new SolidColorPaint(SKColors.DeepSkyBlue, 1),
GeometryFill = new SolidColorPaint(SKColors.DeepSkyBlue, 0),
GeometryStroke = new SolidColorPaint(SKColors.Transparent, 0),
IsHoverable = false
}
];
CpuUsageSeriesYAxes = [
new Axis
{
MinLimit = 0.0,
MaxLimit = 100.0,
Name = "Нагрузка ЦПУ, %",
NameTextSize = 14,
MinStep = 10,
ForceStepToMin = true
}
];
CpuUsageSeriesXAxes = [
new Axis
{
Labels = new ObservableCollection<string>(){ $"{IntervalMs * ChartsHistorySize / 1000} сек" },
LabelsAlignment = LiveChartsCore.Drawing.Align.Start
}
];
CpuUsagePieSeries = [
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue),
InnerRadius = 100,
IsHoverable = false,
DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("00.00")} %",
DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter,
DataLabelsSize = 14,
DataLabelsPaint = new SolidColorPaint(SKColors.White)
},
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)),
InnerRadius = 100,
ShowDataLabels = false,
IsHoverable = false
}
];
Cpu0UsagePieSeries = [
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue),
InnerRadius = 50,
IsHoverable = false,
DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("00.00")} %",
DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter,
DataLabelsSize = 14,
DataLabelsPaint = new SolidColorPaint(SKColors.White)
},
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)),
InnerRadius = 50,
ShowDataLabels = false,
IsHoverable = false
}
];
Cpu1UsagePieSeries = [
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue),
InnerRadius = 50,
IsHoverable = false,
DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("00.00")} %",
DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter,
DataLabelsSize = 14,
DataLabelsPaint = new SolidColorPaint(SKColors.White)
},
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)),
InnerRadius = 50,
ShowDataLabels = false,
IsHoverable = false
}
];
Cpu2UsagePieSeries = [
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue),
InnerRadius = 50,
IsHoverable = false,
DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("00.00")} %",
DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter,
DataLabelsSize = 14,
DataLabelsPaint = new SolidColorPaint(SKColors.White)
},
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)),
InnerRadius = 50,
ShowDataLabels = false,
IsHoverable = false
}
];
Cpu3UsagePieSeries = [
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue),
InnerRadius = 50,
IsHoverable = false,
DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("00.00")} %",
DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter,
DataLabelsSize = 14,
DataLabelsPaint = new SolidColorPaint(SKColors.White)
},
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)),
InnerRadius = 50,
ShowDataLabels = false,
IsHoverable = false
}
];
CpuTemperatureSeries = [
new LineSeries<double>
{
Name = "Температура ЦПУ, ℃",
Values = new ObservableCollection<double>(Enumerable.Repeat(0.0, _chartsHistorySize)),
Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(144), 0),
GeometrySize = 0,
ShowDataLabels = false,
Stroke = new SolidColorPaint(SKColors.DeepSkyBlue, 1),
GeometryFill = new SolidColorPaint(SKColors.DeepSkyBlue, 0),
GeometryStroke = new SolidColorPaint(SKColors.Transparent, 0),
IsHoverable = false
}
];
CpuTemperatureSeriesYAxes = [
new Axis
{
MinLimit = 0.0,
MaxLimit = 100.0,
Name = "Температура ЦПУ, ℃",
NameTextSize = 14,
MinStep = 10,
ForceStepToMin = true
}
];
CpuTemperatureSeriesXAxes = [
new Axis
{
Labels = new ObservableCollection<string>(){ $"{IntervalMs * ChartsHistorySize / 1000} сек" },
LabelsAlignment = LiveChartsCore.Drawing.Align.Start,
}
];
CpuTemperaturePieSeries = [
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue),
InnerRadius = 100,
IsHoverable = false,
DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("00.00")} ℃",
DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter,
DataLabelsSize = 14,
DataLabelsPaint = new SolidColorPaint(SKColors.White)
},
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)),
InnerRadius = 100,
ShowDataLabels = false,
IsHoverable = false
}
];
MemoryUsageSeries = [
new LineSeries<double>
{
Name = "Нагрузка ОЗУ, МБ",
Values = new ObservableCollection<double>(Enumerable.Repeat(0.0, _chartsHistorySize)),
Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(144), 0),
GeometrySize = 0,
ShowDataLabels = false,
Stroke = new SolidColorPaint(SKColors.DeepSkyBlue, 1),
GeometryFill = new SolidColorPaint(SKColors.Transparent, 0),
GeometryStroke = new SolidColorPaint(SKColors.Transparent, 0),
IsHoverable = false
}
];
MemoryUsageSeriesYAxes = [
new Axis
{
MinLimit = 0.0,
MaxLimit = 1000.0,
Name = "Нагрузка ОЗУ, МБ",
NameTextSize = 14,
MinStep = 1000,
ForceStepToMin = true
}
];
MemoryUsageSeriesXAxes = [
new Axis
{
Labels = new ObservableCollection<string>(){ $"{IntervalMs * ChartsHistorySize / 1000} сек" },
LabelsAlignment = LiveChartsCore.Drawing.Align.Start
}
];
MemoryUsagePieSeries = [
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue),
InnerRadius = 100,
IsHoverable = false,
DataLabelsFormatter = value => $"{value.Model?.Value?.ToString("000.00")} МБ",
DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.ChartCenter,
DataLabelsSize = 14,
DataLabelsPaint = new SolidColorPaint(SKColors.White)
},
new PieSeries<ObservableValue>
{
Values = new ObservableCollection<ObservableValue> { new ObservableValue(1) },
Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)),
InnerRadius = 100,
ShowDataLabels = false,
IsHoverable = false
}
];
Update();
_updateTimer = new System.Timers.Timer(IntervalMs);
_updateTimer.Elapsed += UpdateTimerElapsed;
_updateTimer.AutoReset = true;
_updateTimer.Start();
_logger.LogInformation("{} initialized", nameof(ResourcesViewModel));
}
private void UpdateTimerElapsed(object? sender, System.Timers.ElapsedEventArgs ea) => Update();
private void Update()
{
try
{
var snapshot = _computeResourcesService.GetSnapshot();
Dispatcher.UIThread.Post(() =>
{
if (CpuUsageSeries.Count() == 1 &&
CpuUsageSeries.First().Values is ObservableCollection<double> cpuUsageValues)
{
cpuUsageValues.Add(Math.Round(snapshot.CpuUsage.AllUsagePercent, 2));
while (cpuUsageValues.Count > ChartsHistorySize)
cpuUsageValues.RemoveAt(0);
}
if (CpuUsagePieSeries.Count() == 2 &&
CpuUsagePieSeries.Skip(0).First().Values is ObservableCollection<ObservableValue> cpuUsagePieValue1 &&
cpuUsagePieValue1.Count() == 1 &&
CpuUsagePieSeries.Skip(1).First().Values is ObservableCollection<ObservableValue> cpuUsagePieValue2 &&
cpuUsagePieValue2.Count() == 1)
{
var roundedValue = Math.Round(snapshot.CpuUsage.AllUsagePercent, 2);
cpuUsagePieValue1.First().Value = roundedValue;
cpuUsagePieValue2.First().Value = 100 - roundedValue;
}
if (Cpu0UsagePieSeries.Count() == 2 &&
Cpu0UsagePieSeries.Skip(0).First().Values is ObservableCollection<ObservableValue> cpu0UsagePieValue1 &&
cpu0UsagePieValue1.Count() == 1 &&
Cpu0UsagePieSeries.Skip(1).First().Values is ObservableCollection<ObservableValue> cpu0UsagePieValue2 &&
cpu0UsagePieValue2.Count() == 1 &&
snapshot.CpuUsage.CoreUsagePercents.Count() == 4)
{
var roundedValue = Math.Round(snapshot.CpuUsage.CoreUsagePercents.Skip(0).First(), 2);
cpu0UsagePieValue1.First().Value = roundedValue;
cpu0UsagePieValue2.First().Value = 100 - roundedValue;
}
if (Cpu1UsagePieSeries.Count() == 2 &&
Cpu1UsagePieSeries.Skip(0).First().Values is ObservableCollection<ObservableValue> cpu1UsagePieValue1 &&
cpu1UsagePieValue1.Count() == 1 &&
Cpu1UsagePieSeries.Skip(1).First().Values is ObservableCollection<ObservableValue> cpu1UsagePieValue2 &&
cpu1UsagePieValue2.Count() == 1 &&
snapshot.CpuUsage.CoreUsagePercents.Count() == 4)
{
var roundedValue = Math.Round(snapshot.CpuUsage.CoreUsagePercents.Skip(1).First(), 2);
cpu1UsagePieValue1.First().Value = roundedValue;
cpu1UsagePieValue2.First().Value = 100 - roundedValue;
}
if (Cpu2UsagePieSeries.Count() == 2 &&
Cpu2UsagePieSeries.Skip(0).First().Values is ObservableCollection<ObservableValue> cpu2UsagePieValue1 &&
cpu2UsagePieValue1.Count() == 1 &&
Cpu2UsagePieSeries.Skip(1).First().Values is ObservableCollection<ObservableValue> cpu2UsagePieValue2 &&
cpu2UsagePieValue2.Count() == 1 &&
snapshot.CpuUsage.CoreUsagePercents.Count() == 4)
{
var roundedValue = Math.Round(snapshot.CpuUsage.CoreUsagePercents.Skip(2).First(), 2);
cpu2UsagePieValue1.First().Value = roundedValue;
cpu2UsagePieValue2.First().Value = 100 - roundedValue;
}
if (Cpu3UsagePieSeries.Count() == 2 &&
Cpu3UsagePieSeries.Skip(0).First().Values is ObservableCollection<ObservableValue> cpu3UsagePieValue1 &&
cpu3UsagePieValue1.Count() == 1 &&
Cpu3UsagePieSeries.Skip(1).First().Values is ObservableCollection<ObservableValue> cpu3UsagePieValue2 &&
cpu3UsagePieValue2.Count() == 1 &&
snapshot.CpuUsage.CoreUsagePercents.Count() == 4)
{
var roundedValue = Math.Round(snapshot.CpuUsage.CoreUsagePercents.Skip(3).First(), 2);
cpu3UsagePieValue1.First().Value = roundedValue;
cpu3UsagePieValue2.First().Value = 100 - roundedValue;
}
if (CpuTemperatureSeries.Count() == 1 &&
CpuTemperatureSeries.First().Values is ObservableCollection<double> cpuTemperatureValues)
{
cpuTemperatureValues.Add(Math.Round(snapshot.CpuTemperature.Current, 2));
while (cpuTemperatureValues.Count > ChartsHistorySize)
cpuTemperatureValues.RemoveAt(0);
}
if (CpuTemperatureSeriesYAxes.Count() == 1)
CpuTemperatureSeriesYAxes.First().MaxLimit = Math.Round(snapshot.CpuTemperature.Critical);
if (CpuTemperaturePieSeries.Count() == 2 &&
CpuTemperaturePieSeries.Skip(0).First().Values is ObservableCollection<ObservableValue> cpuTemperaturePieValue1 &&
cpuTemperaturePieValue1.Count() == 1 &&
CpuTemperaturePieSeries.Skip(1).First().Values is ObservableCollection<ObservableValue> cpuTemperaturePieValue2 &&
cpuTemperaturePieValue2.Count() == 1)
{
var roundedValue = Math.Round(snapshot.CpuTemperature.Current, 2);
cpuTemperaturePieValue1.First().Value = roundedValue;
cpuTemperaturePieValue2.First().Value = snapshot.CpuTemperature.Critical - roundedValue;
}
if (MemoryUsageSeries.Count() == 1 &&
MemoryUsageSeries.First().Values is ObservableCollection<double> memoryUsageValues)
{
memoryUsageValues.Add(Math.Round(snapshot.MemoryUsage.UsedMB, 2));
while (memoryUsageValues.Count > ChartsHistorySize)
memoryUsageValues.RemoveAt(0);
}
if (MemoryUsageSeriesYAxes.Count() == 1)
MemoryUsageSeriesYAxes.First().MaxLimit = Math.Round(snapshot.MemoryUsage.TotalMB);
if (MemoryUsagePieSeries.Count() == 2 &&
MemoryUsagePieSeries.Skip(0).First().Values is ObservableCollection<ObservableValue> memoryUsagePieValue1 &&
memoryUsagePieValue1.Count() == 1 &&
MemoryUsagePieSeries.Skip(1).First().Values is ObservableCollection<ObservableValue> memoryUsagePieValue2 &&
memoryUsagePieValue2.Count() == 1)
{
var roundedValue = Math.Round(snapshot.MemoryUsage.UsedMB, 2);
memoryUsagePieValue1.First().Value = roundedValue;
memoryUsagePieValue2.First().Value = snapshot.MemoryUsage.TotalMB - roundedValue;
}
});
}
catch (Exception e)
{
_logger.LogWarning(e, "Exception while update");
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
try
{
_updateTimer.Stop();
_updateTimer.Dispose();
}
catch
{ }
}
_disposedValue = true;
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
@@ -1,6 +1,6 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
namespace GSS2.Test; namespace GSS2.Test.ViewModels;
public abstract class ViewModelBase : ObservableObject public abstract class ViewModelBase : ObservableObject
{ } { }
@@ -2,11 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="using:GSS2.Test" xmlns:vm="using:GSS2.Test.ViewModels"
x:Class="GSS2.Test.CameraView" x:DataType="vm:CameraViewModel"
x:DataType="local:CameraViewModel"> x:Class="GSS2.Test.Views.CameraView">
<Design.DataContext>
<local:CameraViewModel/>
</Design.DataContext>
</Control> </Control>
@@ -1,23 +1,18 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Avalonia.Threading; using Avalonia.Threading;
using GSS2.Test.ViewModels;
using GSS2.Vulkan; using GSS2.Vulkan;
using LibCameraSharp; using LibCameraSharp;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR; using Silk.NET.Vulkan.Extensions.KHR;
using Image = Silk.NET.Vulkan.Image; using Image = Silk.NET.Vulkan.Image;
namespace GSS2.Test; namespace GSS2.Test.Views;
interface IImage interface IImage
{ {
+10
View File
@@ -0,0 +1,10 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:GSS2.Test.ViewModels"
xmlns:local="using:GSS2.Test.Views"
x:DataType="vm:MainViewModel"
x:Class="GSS2.Test.Views.MainView">
<!-- <ContentControl Content="{Binding CameraViewModel}"/> -->
<ContentControl Content="{Binding ResourcesViewModel}"/>
</UserControl>
+11
View File
@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace GSS2.Test.Views;
public partial class MainView : UserControl
{
public MainView()
{
InitializeComponent();
}
}
+82
View File
@@ -0,0 +1,82 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:GSS2.Test.ViewModels"
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.Avalonia"
x:Class="GSS2.Test.Views.ResourcesView"
x:DataType="vm:ResourcesViewModel">
<Grid RowDefinitions="Auto,Auto,Auto" ColumnDefinitions="*,Auto">
<lvc:CartesianChart
Grid.Row="0"
Grid.Column="0"
Series="{Binding CpuUsageSeries}"
YAxes="{Binding CpuUsageSeriesYAxes}"
XAxes="{Binding CpuUsageSeriesXAxes}"
Height="300"
ZoomMode="None"
EasingFunction="{x:Null}" />
<lvc:CartesianChart
Grid.Row="1"
Grid.Column="0"
Series="{Binding CpuTemperatureSeries}"
YAxes="{Binding CpuTemperatureSeriesYAxes}"
XAxes="{Binding CpuTemperatureSeriesXAxes}"
Height="300"
ZoomMode="None"
EasingFunction="{x:Null}" />
<lvc:CartesianChart
Grid.Row="2"
Grid.Column="0"
Series="{Binding MemoryUsageSeries}"
YAxes="{Binding MemoryUsageSeriesYAxes}"
XAxes="{Binding MemoryUsageSeriesXAxes}"
Height="300"
ZoomMode="None"
EasingFunction="{x:Null}" />
<Grid Grid.Row="0" Grid.Column="1" RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,Auto,Auto">
<lvc:PieChart
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Height="200"
Width="200"
Series="{Binding CpuUsagePieSeries}" />
<lvc:PieChart
Grid.Row="0"
Grid.Column="1"
Height="120"
Width="120"
Series="{Binding Cpu0UsagePieSeries}" />
<lvc:PieChart
Grid.Row="0"
Grid.Column="2"
Height="120"
Width="120"
Series="{Binding Cpu1UsagePieSeries}" />
<lvc:PieChart
Grid.Row="1"
Grid.Column="1"
Height="120"
Width="120"
Series="{Binding Cpu2UsagePieSeries}" />
<lvc:PieChart
Grid.Row="1"
Grid.Column="2"
Height="120"
Width="120"
Series="{Binding Cpu3UsagePieSeries}" />
</Grid>
<lvc:PieChart
Grid.Row="1"
Grid.Column="1"
Height="300"
Width="300"
Series="{Binding CpuTemperaturePieSeries}" />
<lvc:PieChart
Grid.Row="2"
Grid.Column="1"
Height="300"
Width="300"
Series="{Binding MemoryUsagePieSeries}" />
</Grid>
</UserControl>
+11
View File
@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace GSS2.Test.Views;
public partial class ResourcesView : UserControl
{
public ResourcesView()
{
InitializeComponent();
}
}