From 58f307c44480f70f3f1c32f0e1e1ca64bdfbc7f0 Mon Sep 17 00:00:00 2001 From: Alek-ban Date: Mon, 26 Jan 2026 13:54:29 +0300 Subject: [PATCH] feat: GSS2.Test refactor + ComputeResourcesService --- GSS2.Core/DependencyInjectionHelper.cs | 2 + GSS2.Core/Hardware/ComputeResourcesService.cs | 141 ++++++ GSS2.Test/App.axaml.cs | 15 +- GSS2.Test/DependencyInjectionHelper.cs | 22 +- GSS2.Test/DependencyInjectionViewLocator.cs | 6 +- GSS2.Test/GSS2.Test.csproj | 1 + GSS2.Test/{ => Logging}/ConsoleFormatter.cs | 2 +- GSS2.Test/{ => Logging}/FileLogger.cs | 2 +- GSS2.Test/{ => Logging}/FileLoggerProvider.cs | 4 +- GSS2.Test/{ => Logging}/LibCameraLogSink.cs | 4 +- GSS2.Test/MainWindow.axaml | 15 +- GSS2.Test/MainWindow.axaml.cs | 1 - GSS2.Test/MainWindowViewModel.cs | 31 -- GSS2.Test/Program.cs | 7 +- GSS2.Test/{ => ViewModels}/CameraViewModel.cs | 3 +- GSS2.Test/ViewModels/MainViewModel.cs | 18 + GSS2.Test/ViewModels/MainWindowViewModel.cs | 18 + GSS2.Test/ViewModels/ResourcesViewModel.cs | 458 ++++++++++++++++++ GSS2.Test/{ => ViewModels}/ViewModelBase.cs | 2 +- GSS2.Test/{ => Views}/CameraView.axaml | 10 +- GSS2.Test/{ => Views}/CameraView.axaml.cs | 9 +- GSS2.Test/Views/MainView.axaml | 10 + GSS2.Test/Views/MainView.axaml.cs | 11 + GSS2.Test/Views/ResourcesView.axaml | 82 ++++ GSS2.Test/Views/ResourcesView.axaml.cs | 11 + 25 files changed, 798 insertions(+), 87 deletions(-) create mode 100644 GSS2.Core/Hardware/ComputeResourcesService.cs rename GSS2.Test/{ => Logging}/ConsoleFormatter.cs (99%) rename GSS2.Test/{ => Logging}/FileLogger.cs (98%) rename GSS2.Test/{ => Logging}/FileLoggerProvider.cs (87%) rename GSS2.Test/{ => Logging}/LibCameraLogSink.cs (98%) delete mode 100644 GSS2.Test/MainWindowViewModel.cs rename GSS2.Test/{ => ViewModels}/CameraViewModel.cs (90%) create mode 100644 GSS2.Test/ViewModels/MainViewModel.cs create mode 100644 GSS2.Test/ViewModels/MainWindowViewModel.cs create mode 100644 GSS2.Test/ViewModels/ResourcesViewModel.cs rename GSS2.Test/{ => ViewModels}/ViewModelBase.cs (76%) rename GSS2.Test/{ => Views}/CameraView.axaml (54%) rename GSS2.Test/{ => Views}/CameraView.axaml.cs (99%) create mode 100644 GSS2.Test/Views/MainView.axaml create mode 100644 GSS2.Test/Views/MainView.axaml.cs create mode 100644 GSS2.Test/Views/ResourcesView.axaml create mode 100644 GSS2.Test/Views/ResourcesView.axaml.cs diff --git a/GSS2.Core/DependencyInjectionHelper.cs b/GSS2.Core/DependencyInjectionHelper.cs index f1765ff..1f0a858 100644 --- a/GSS2.Core/DependencyInjectionHelper.cs +++ b/GSS2.Core/DependencyInjectionHelper.cs @@ -74,6 +74,8 @@ public static class DependencyInjectionHelper return new CameraService(logger, serviceConfiguration, autoInitialize); } ); + public static IServiceCollection AddComputeResourcesService(this IServiceCollection services) => services + .AddSingleton(); public static IServiceCollection AddAmineContentCalibrationContext(this IServiceCollection services, IConfigurationManager configuration) => services .AddDbContext( diff --git a/GSS2.Core/Hardware/ComputeResourcesService.cs b/GSS2.Core/Hardware/ComputeResourcesService.cs new file mode 100644 index 0000000..3414bbb --- /dev/null +++ b/GSS2.Core/Hardware/ComputeResourcesService.cs @@ -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 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 _logger; + + private (long idle, long total)? _prevAll = null; + private Dictionary? _prevCores = null; + + public ComputeResourcesService(ILogger 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()); + + var (allUsage, allIdle, allTotal) = ParceLines(lines.First(), _prevAll); + _prevAll = (allIdle, allTotal); + + var coreUsages = new List(); + + foreach (var (i, line) in lines.Skip(1).Enumerate()) + { + if (_prevCores is null) + _prevCores = new Dictionary(); + 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); + } +} diff --git a/GSS2.Test/App.axaml.cs b/GSS2.Test/App.axaml.cs index c03b8ea..ded5886 100644 --- a/GSS2.Test/App.axaml.cs +++ b/GSS2.Test/App.axaml.cs @@ -2,6 +2,8 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using GSS2.Test.ViewModels; + namespace GSS2.Test; public partial class App : Application @@ -13,13 +15,14 @@ public partial class App : Application public override void OnFrameworkInitializationCompleted() { - var mainWindowViewModel = Program.ApplicationHost.Services.GetRequiredService(); - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - desktop.MainWindow = new MainWindow() - { - DataContext = mainWindowViewModel - }; + { + desktop.Startup += (_, _) => Program.ApplicationHost.RunAsync(); + desktop.Exit += (_, _) => Program.ApplicationHost.StopAsync(); + + desktop.MainWindow = new MainWindow(); + desktop.MainWindow.Activated += (_, _) => desktop.MainWindow.DataContext = Program.ApplicationHost.Services.GetRequiredService(); + } base.OnFrameworkInitializationCompleted(); } } \ No newline at end of file diff --git a/GSS2.Test/DependencyInjectionHelper.cs b/GSS2.Test/DependencyInjectionHelper.cs index 9eba52c..d7755e6 100644 --- a/GSS2.Test/DependencyInjectionHelper.cs +++ b/GSS2.Test/DependencyInjectionHelper.cs @@ -1,19 +1,17 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Protocols.Configuration; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -using GSS2.Core.Hardware; -using GSS2.Core.Analysis.AmineContent.Database; +using GSS2.Test.Views; +using GSS2.Test.ViewModels; namespace GSS2.Test; public static class DependencyInjectionHelper { - public static IServiceCollection AddUi(this IServiceCollection services) => + public static IServiceCollection AddViewsAndModels(this IServiceCollection services) => services - .AddTransient() - .AddTransient() - .AddTransient(); + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + // .AddSingleton() + // .AddSingleton(); } \ No newline at end of file diff --git a/GSS2.Test/DependencyInjectionViewLocator.cs b/GSS2.Test/DependencyInjectionViewLocator.cs index a9c285a..aa5a620 100644 --- a/GSS2.Test/DependencyInjectionViewLocator.cs +++ b/GSS2.Test/DependencyInjectionViewLocator.cs @@ -1,6 +1,8 @@ using Avalonia.Controls; using Avalonia.Controls.Templates; +using GSS2.Test.ViewModels; + namespace GSS2.Test; public class DependencyInjectionViewLocator : IDataTemplate @@ -10,7 +12,9 @@ public class DependencyInjectionViewLocator : IDataTemplate if (data is 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) return new TextBlock { Text = $"Not found: {data}" }; var type = Type.GetType(name); diff --git a/GSS2.Test/GSS2.Test.csproj b/GSS2.Test/GSS2.Test.csproj index e09d129..7345c70 100644 --- a/GSS2.Test/GSS2.Test.csproj +++ b/GSS2.Test/GSS2.Test.csproj @@ -12,6 +12,7 @@ + diff --git a/GSS2.Test/ConsoleFormatter.cs b/GSS2.Test/Logging/ConsoleFormatter.cs similarity index 99% rename from GSS2.Test/ConsoleFormatter.cs rename to GSS2.Test/Logging/ConsoleFormatter.cs index 2818c56..39a8880 100644 --- a/GSS2.Test/ConsoleFormatter.cs +++ b/GSS2.Test/Logging/ConsoleFormatter.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Logging.Abstractions; -namespace GSS2.Test; +namespace GSS2.Test.Logging; public sealed class ConsoleFormatter : Microsoft.Extensions.Logging.Console.ConsoleFormatter { diff --git a/GSS2.Test/FileLogger.cs b/GSS2.Test/Logging/FileLogger.cs similarity index 98% rename from GSS2.Test/FileLogger.cs rename to GSS2.Test/Logging/FileLogger.cs index 02a777a..c2067bf 100644 --- a/GSS2.Test/FileLogger.cs +++ b/GSS2.Test/Logging/FileLogger.cs @@ -1,4 +1,4 @@ -namespace GSS2.Test; +namespace GSS2.Test.Logging; public sealed class FileLogger : ILogger { diff --git a/GSS2.Test/FileLoggerProvider.cs b/GSS2.Test/Logging/FileLoggerProvider.cs similarity index 87% rename from GSS2.Test/FileLoggerProvider.cs rename to GSS2.Test/Logging/FileLoggerProvider.cs index 59d5e36..b047314 100644 --- a/GSS2.Test/FileLoggerProvider.cs +++ b/GSS2.Test/Logging/FileLoggerProvider.cs @@ -1,6 +1,4 @@ -using Microsoft.Extensions.Logging; - -namespace GSS2.Test; +namespace GSS2.Test.Logging; public sealed class FileLoggerProvider : ILoggerProvider { diff --git a/GSS2.Test/LibCameraLogSink.cs b/GSS2.Test/Logging/LibCameraLogSink.cs similarity index 98% rename from GSS2.Test/LibCameraLogSink.cs rename to GSS2.Test/Logging/LibCameraLogSink.cs index fdf759b..e3c7c0e 100644 --- a/GSS2.Test/LibCameraLogSink.cs +++ b/GSS2.Test/Logging/LibCameraLogSink.cs @@ -1,8 +1,6 @@ using LibCameraSharp; -using Microsoft.Extensions.Logging; - -namespace GSS2.Test; +namespace GSS2.Test.Logging; public sealed class LibCameraLogSink : IDisposable { diff --git a/GSS2.Test/MainWindow.axaml b/GSS2.Test/MainWindow.axaml index 6457f9a..baf97d3 100644 --- a/GSS2.Test/MainWindow.axaml +++ b/GSS2.Test/MainWindow.axaml @@ -1,16 +1,11 @@ - - - - + Title="{Binding Title}"> - - + diff --git a/GSS2.Test/MainWindow.axaml.cs b/GSS2.Test/MainWindow.axaml.cs index 992b15d..a77fec3 100644 --- a/GSS2.Test/MainWindow.axaml.cs +++ b/GSS2.Test/MainWindow.axaml.cs @@ -1,5 +1,4 @@ using Avalonia.Controls; -using Avalonia.Threading; namespace GSS2.Test; diff --git a/GSS2.Test/MainWindowViewModel.cs b/GSS2.Test/MainWindowViewModel.cs deleted file mode 100644 index 5563cbd..0000000 --- a/GSS2.Test/MainWindowViewModel.cs +++ /dev/null @@ -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 _logger; - [ObservableProperty] private CameraViewModel _cameraViewModel; - - public MainWindowViewModel( - CameraService cameraService, - IlluminatorService illuminatorService, - LightsService lightsService, - ILogger logger, - CameraViewModel cameraViewModel - ) - { - _cameraService = cameraService; - _illuminatorService = illuminatorService; - _lightsService = lightsService; - _logger = logger; - CameraViewModel = cameraViewModel; - } -} \ No newline at end of file diff --git a/GSS2.Test/Program.cs b/GSS2.Test/Program.cs index 6572a0c..67c4a91 100644 --- a/GSS2.Test/Program.cs +++ b/GSS2.Test/Program.cs @@ -6,6 +6,9 @@ using GSS2.Core.Analysis.AmineContent.Database; using Avalonia; using Avalonia.Vulkan; +using GSS2.Test.Logging; + +using ConsoleFormatter = GSS2.Test.Logging.ConsoleFormatter; namespace GSS2.Test; @@ -21,7 +24,6 @@ public class Program [STAThread] public static void Main(string[] args) { - ApplicationHost.RunAsync(); BuildAvaloniaApp() .StartWithClassicDesktopLifetime(args); } @@ -44,8 +46,9 @@ public class Program builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity"); builder.Services.AddIlluminatorService("Hardware:Illuminator"); builder.Services.AddCameraService("Hardware:Camera", true); + builder.Services.AddComputeResourcesService(); - builder.Services.AddUi(); + builder.Services.AddViewsAndModels(); var host = builder.Build(); host.Services.GetRequiredService(); diff --git a/GSS2.Test/CameraViewModel.cs b/GSS2.Test/ViewModels/CameraViewModel.cs similarity index 90% rename from GSS2.Test/CameraViewModel.cs rename to GSS2.Test/ViewModels/CameraViewModel.cs index 256e0ab..1ec2dba 100644 --- a/GSS2.Test/CameraViewModel.cs +++ b/GSS2.Test/ViewModels/CameraViewModel.cs @@ -2,7 +2,7 @@ using GSS2.Core.Hardware; using LibCameraSharp; -namespace GSS2.Test; +namespace GSS2.Test.ViewModels; public class CameraViewModel : ViewModelBase { @@ -13,6 +13,7 @@ public class CameraViewModel : ViewModelBase { _cameraService = cameraService; _logger = logger; + _logger.LogInformation("Initialized"); } public async Task<(FrameBuffer, StreamConfiguration)?> CaptureImage(int bufferId) diff --git a/GSS2.Test/ViewModels/MainViewModel.cs b/GSS2.Test/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..6c598d2 --- /dev/null +++ b/GSS2.Test/ViewModels/MainViewModel.cs @@ -0,0 +1,18 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace GSS2.Test.ViewModels; + +public partial class MainViewModel : ViewModelBase +{ + private readonly ILogger _logger; + [ObservableProperty] private CameraViewModel _cameraViewModel; + [ObservableProperty] private ResourcesViewModel _resourcesViewModel; + + public MainViewModel(ILogger logger, ResourcesViewModel resourcesViewModel) + { + _logger = logger; + // CameraViewModel = cameraViewModel; + ResourcesViewModel = resourcesViewModel; + _logger.LogInformation("{} initialized", nameof(MainViewModel)); + } +} \ No newline at end of file diff --git a/GSS2.Test/ViewModels/MainWindowViewModel.cs b/GSS2.Test/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..6b5964e --- /dev/null +++ b/GSS2.Test/ViewModels/MainWindowViewModel.cs @@ -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 _logger; + + public MainWindowViewModel(ILogger logger, MainViewModel mainViewModel) + { + _logger = logger; + MainViewModel = mainViewModel; + Title = "GSS2.Test"; + _logger.LogInformation("{} initialized", nameof(MainWindowViewModel)); + } +} \ No newline at end of file diff --git a/GSS2.Test/ViewModels/ResourcesViewModel.cs b/GSS2.Test/ViewModels/ResourcesViewModel.cs new file mode 100644 index 0000000..9b36a37 --- /dev/null +++ b/GSS2.Test/ViewModels/ResourcesViewModel.cs @@ -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 _logger; + + [ObservableProperty] private ObservableCollection _cpuUsageSeries; + [ObservableProperty] private ObservableCollection _cpuUsageSeriesYAxes; + [ObservableProperty] private ObservableCollection _cpuUsageSeriesXAxes; + + [ObservableProperty] private ObservableCollection _cpuUsagePieSeries; + [ObservableProperty] private ObservableCollection _cpu0UsagePieSeries; + [ObservableProperty] private ObservableCollection _cpu1UsagePieSeries; + [ObservableProperty] private ObservableCollection _cpu2UsagePieSeries; + [ObservableProperty] private ObservableCollection _cpu3UsagePieSeries; + + [ObservableProperty] private ObservableCollection _cpuTemperatureSeries; + + [ObservableProperty] private ObservableCollection _cpuTemperatureSeriesYAxes; + [ObservableProperty] private ObservableCollection _cpuTemperatureSeriesXAxes; + [ObservableProperty] private ObservableCollection _cpuTemperaturePieSeries; + + [ObservableProperty] private ObservableCollection _memoryUsageSeries; + [ObservableProperty] private ObservableCollection _memoryUsageSeriesYAxes; + [ObservableProperty] private ObservableCollection _memoryUsageSeriesXAxes; + [ObservableProperty] private ObservableCollection _memoryUsagePieSeries; + + + [ObservableProperty] private int _intervalMs = 500; + [ObservableProperty] private int _chartsHistorySize = 120; + private readonly System.Timers.Timer _updateTimer; + + public ResourcesViewModel(ComputeResourcesService computeResourcesService, ILogger logger) + { + _computeResourcesService = computeResourcesService; + _logger = logger; + + CpuUsageSeries = [ + new LineSeries + { + Name = "Нагрузка ЦПУ, %", + Values = new ObservableCollection(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(){ $"{IntervalMs * ChartsHistorySize / 1000} сек" }, + LabelsAlignment = LiveChartsCore.Drawing.Align.Start + } + ]; + + CpuUsagePieSeries = [ + new PieSeries + { + Values = new ObservableCollection { 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 + { + Values = new ObservableCollection { new ObservableValue(1) }, + Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), + InnerRadius = 100, + ShowDataLabels = false, + IsHoverable = false + } + ]; + Cpu0UsagePieSeries = [ + new PieSeries + { + Values = new ObservableCollection { 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 + { + Values = new ObservableCollection { new ObservableValue(1) }, + Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), + InnerRadius = 50, + ShowDataLabels = false, + IsHoverable = false + } + ]; + Cpu1UsagePieSeries = [ + new PieSeries + { + Values = new ObservableCollection { 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 + { + Values = new ObservableCollection { new ObservableValue(1) }, + Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), + InnerRadius = 50, + ShowDataLabels = false, + IsHoverable = false + } + ]; + Cpu2UsagePieSeries = [ + new PieSeries + { + Values = new ObservableCollection { 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 + { + Values = new ObservableCollection { new ObservableValue(1) }, + Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), + InnerRadius = 50, + ShowDataLabels = false, + IsHoverable = false + } + ]; + Cpu3UsagePieSeries = [ + new PieSeries + { + Values = new ObservableCollection { 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 + { + Values = new ObservableCollection { new ObservableValue(1) }, + Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), + InnerRadius = 50, + ShowDataLabels = false, + IsHoverable = false + } + ]; + + CpuTemperatureSeries = [ + new LineSeries + { + Name = "Температура ЦПУ, ℃", + Values = new ObservableCollection(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(){ $"{IntervalMs * ChartsHistorySize / 1000} сек" }, + LabelsAlignment = LiveChartsCore.Drawing.Align.Start, + } + ]; + + CpuTemperaturePieSeries = [ + new PieSeries + { + Values = new ObservableCollection { 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 + { + Values = new ObservableCollection { new ObservableValue(1) }, + Fill = new SolidColorPaint(SKColors.DeepSkyBlue.WithAlpha(64)), + InnerRadius = 100, + ShowDataLabels = false, + IsHoverable = false + } + ]; + + MemoryUsageSeries = [ + new LineSeries + { + Name = "Нагрузка ОЗУ, МБ", + Values = new ObservableCollection(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(){ $"{IntervalMs * ChartsHistorySize / 1000} сек" }, + LabelsAlignment = LiveChartsCore.Drawing.Align.Start + } + ]; + + MemoryUsagePieSeries = [ + new PieSeries + { + Values = new ObservableCollection { 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 + { + Values = new ObservableCollection { 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 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 cpuUsagePieValue1 && + cpuUsagePieValue1.Count() == 1 && + CpuUsagePieSeries.Skip(1).First().Values is ObservableCollection 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 cpu0UsagePieValue1 && + cpu0UsagePieValue1.Count() == 1 && + Cpu0UsagePieSeries.Skip(1).First().Values is ObservableCollection 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 cpu1UsagePieValue1 && + cpu1UsagePieValue1.Count() == 1 && + Cpu1UsagePieSeries.Skip(1).First().Values is ObservableCollection 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 cpu2UsagePieValue1 && + cpu2UsagePieValue1.Count() == 1 && + Cpu2UsagePieSeries.Skip(1).First().Values is ObservableCollection 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 cpu3UsagePieValue1 && + cpu3UsagePieValue1.Count() == 1 && + Cpu3UsagePieSeries.Skip(1).First().Values is ObservableCollection 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 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 cpuTemperaturePieValue1 && + cpuTemperaturePieValue1.Count() == 1 && + CpuTemperaturePieSeries.Skip(1).First().Values is ObservableCollection 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 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 memoryUsagePieValue1 && + memoryUsagePieValue1.Count() == 1 && + MemoryUsagePieSeries.Skip(1).First().Values is ObservableCollection 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); + } +} \ No newline at end of file diff --git a/GSS2.Test/ViewModelBase.cs b/GSS2.Test/ViewModels/ViewModelBase.cs similarity index 76% rename from GSS2.Test/ViewModelBase.cs rename to GSS2.Test/ViewModels/ViewModelBase.cs index 4cf40d9..56c194a 100644 --- a/GSS2.Test/ViewModelBase.cs +++ b/GSS2.Test/ViewModels/ViewModelBase.cs @@ -1,6 +1,6 @@ using CommunityToolkit.Mvvm.ComponentModel; -namespace GSS2.Test; +namespace GSS2.Test.ViewModels; public abstract class ViewModelBase : ObservableObject { } \ No newline at end of file diff --git a/GSS2.Test/CameraView.axaml b/GSS2.Test/Views/CameraView.axaml similarity index 54% rename from GSS2.Test/CameraView.axaml rename to GSS2.Test/Views/CameraView.axaml index 75d4976..2de6355 100644 --- a/GSS2.Test/CameraView.axaml +++ b/GSS2.Test/Views/CameraView.axaml @@ -2,11 +2,7 @@ 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:local="using:GSS2.Test" - x:Class="GSS2.Test.CameraView" - x:DataType="local:CameraViewModel"> - - - - + xmlns:vm="using:GSS2.Test.ViewModels" + x:DataType="vm:CameraViewModel" + x:Class="GSS2.Test.Views.CameraView"> diff --git a/GSS2.Test/CameraView.axaml.cs b/GSS2.Test/Views/CameraView.axaml.cs similarity index 99% rename from GSS2.Test/CameraView.axaml.cs rename to GSS2.Test/Views/CameraView.axaml.cs index bc56e4b..f5bef49 100644 --- a/GSS2.Test/CameraView.axaml.cs +++ b/GSS2.Test/Views/CameraView.axaml.cs @@ -1,23 +1,18 @@ using System.Runtime.InteropServices; -using System.Security.Cryptography; - using Avalonia; using Avalonia.Controls; using Avalonia.LogicalTree; using Avalonia.Platform; using Avalonia.Rendering.Composition; using Avalonia.Threading; - +using GSS2.Test.ViewModels; using GSS2.Vulkan; - using LibCameraSharp; - using Silk.NET.Vulkan; using Silk.NET.Vulkan.Extensions.KHR; - using Image = Silk.NET.Vulkan.Image; -namespace GSS2.Test; +namespace GSS2.Test.Views; interface IImage { diff --git a/GSS2.Test/Views/MainView.axaml b/GSS2.Test/Views/MainView.axaml new file mode 100644 index 0000000..0f8bfee --- /dev/null +++ b/GSS2.Test/Views/MainView.axaml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/GSS2.Test/Views/MainView.axaml.cs b/GSS2.Test/Views/MainView.axaml.cs new file mode 100644 index 0000000..5a6120a --- /dev/null +++ b/GSS2.Test/Views/MainView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace GSS2.Test.Views; + +public partial class MainView : UserControl +{ + public MainView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/GSS2.Test/Views/ResourcesView.axaml b/GSS2.Test/Views/ResourcesView.axaml new file mode 100644 index 0000000..98fc4c4 --- /dev/null +++ b/GSS2.Test/Views/ResourcesView.axaml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + diff --git a/GSS2.Test/Views/ResourcesView.axaml.cs b/GSS2.Test/Views/ResourcesView.axaml.cs new file mode 100644 index 0000000..9ec97d3 --- /dev/null +++ b/GSS2.Test/Views/ResourcesView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace GSS2.Test.Views; + +public partial class ResourcesView : UserControl +{ + public ResourcesView() + { + InitializeComponent(); + } +} \ No newline at end of file