using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; using Avalonia.Threading; using SkiaSharp; using LiveChartsCore; using LiveChartsCore.SkiaSharpView; using LiveChartsCore.SkiaSharpView.Painting; using LiveChartsCore.Defaults; using GSS2.Core.Hardware; using GSS2.UI.Core.ViewModels; 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); // _logger.LogInformation("Memory leakage: {} MB/s", (memoryUsageValues.TakeLast(10).Average() - memoryUsageValues.TakeLast(20).Take(10).Average()) / 5.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); } }