152 lines
5.2 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|