Files
XplorePlane/XplorePlane/ViewModels/Debug/PerformanceMonitorViewModel.cs
T
zhengxuan.zhang 5a11af9ab1 优化调试面板
2026-06-01 14:35:37 +08:00

152 lines
5.2 KiB
C#

using Prism.Mvvm;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Threading;
using XP.Common.Logging.Interfaces;
using XplorePlane.Models;
using XplorePlane.Services.AppState;
namespace XplorePlane.ViewModels.Debug
{
public class PerformanceMonitorViewModel : BindableBase, IDisposable
{
private readonly IAppStateService _appStateService;
private readonly ILoggerService _logger;
private readonly Dispatcher _dispatcher;
private readonly DispatcherTimer _timer;
private readonly ConcurrentQueue<(string Category, DateTime Timestamp)> _eventQueue = new();
private readonly Dictionary<string, Queue<double>> _latencyHistory = new();
private double _maxLatency;
private bool _isLatencyWarning;
private bool _initialized;
private bool _disposed;
public ObservableCollection<PerformanceMetric> Metrics { get; } = new();
public double MaxLatency
{
get => _maxLatency;
private set => SetProperty(ref _maxLatency, value);
}
public bool IsLatencyWarning
{
get => _isLatencyWarning;
private set => SetProperty(ref _isLatencyWarning, value);
}
public PerformanceMonitorViewModel(IAppStateService appStateService, ILoggerService loggerService, Dispatcher dispatcher)
{
_appStateService = appStateService ?? throw new ArgumentNullException(nameof(appStateService));
_logger = (loggerService ?? throw new ArgumentNullException(nameof(loggerService))).ForModule<PerformanceMonitorViewModel>();
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
foreach (var category in DebugPanelStateMetadata.StateCategories)
{
if (category == "CalibrationMatrix")
{
continue;
}
Metrics.Add(new PerformanceMetric { StateType = category });
_latencyHistory[category] = new Queue<double>();
}
_timer = new DispatcherTimer(DispatcherPriority.Background, _dispatcher)
{
Interval = TimeSpan.FromSeconds(1)
};
_timer.Tick += (_, _) =>
{
UpdateFrequencyMetrics();
};
}
public void Initialize()
{
if (_initialized || _disposed)
{
return;
}
_appStateService.MotionStateChanged += (_, _) => RecordEvent("MotionState");
_appStateService.RaySourceStateChanged += (_, _) => RecordEvent("RaySourceState");
_appStateService.DetectorStateChanged += (_, _) => RecordEvent("DetectorState");
_appStateService.SystemStateChanged += (_, _) => RecordEvent("SystemState");
_appStateService.CameraStateChanged += (_, _) => RecordEvent("CameraState");
_appStateService.LinkedViewStateChanged += (_, _) => RecordEvent("LinkedViewState");
_appStateService.RecipeExecutionStateChanged += (_, _) => RecordEvent("RecipeExecutionState");
_timer.Start();
_initialized = true;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_timer.Stop();
Metrics.Clear();
_disposed = true;
}
private void RecordEvent(string category)
{
var timestamp = DateTime.UtcNow;
_eventQueue.Enqueue((category, timestamp));
_dispatcher.BeginInvoke(new Action(() =>
{
var latency = (DateTime.UtcNow - timestamp).TotalMilliseconds;
UpdateLatencyMetric(category, latency);
}));
}
private void UpdateFrequencyMetrics()
{
var cutoff = DateTime.UtcNow.AddSeconds(-1);
var events = _eventQueue.ToArray();
var grouped = events.Where(e => e.Timestamp >= cutoff).GroupBy(e => e.Category).ToDictionary(g => g.Key, g => g.Count());
foreach (var metric in Metrics)
{
metric.EventsPerSecond = grouped.TryGetValue(metric.StateType, out var count) ? count : 0;
}
while (_eventQueue.TryPeek(out var item) && item.Timestamp < cutoff.AddSeconds(-1))
{
_eventQueue.TryDequeue(out _);
}
}
private void UpdateLatencyMetric(string category, double latency)
{
if (!_latencyHistory.TryGetValue(category, out var history))
{
return;
}
history.Enqueue(latency);
while (history.Count > 60)
{
history.Dequeue();
}
var metric = Metrics.FirstOrDefault(m => m.StateType == category);
if (metric != null)
{
metric.AverageLatency = history.Average();
}
MaxLatency = Metrics.Count == 0 ? 0 : Metrics.Max(m => m.AverageLatency);
IsLatencyWarning = MaxLatency > 500;
}
}
}