using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace Barotrauma { public class PerformanceCounter { private readonly object mutex = new object(); public double AverageFramesPerSecond { get; private set; } public double CurrentFramesPerSecond { get; private set; } public double AverageFramesPerSecondInPastMinute { get; private set; } public const int MaximumSamples = 10; private readonly Queue sampleBuffer = new Queue(); private readonly Queue averageFramesPerSecondBuffer = new Queue(); private readonly Stopwatch timer = new Stopwatch(); private long lastSecondMark = 0; private long lastMinuteMark = 0; public class TickInfo { public Queue ElapsedTicks { get; set; } = new Queue(); public long AvgTicksPerFrame { get; set; } } private readonly Dictionary> elapsedTicks = new Dictionary>(); private readonly Dictionary avgTicksPerFrame = new Dictionary(); #if CLIENT internal Graph UpdateTimeGraph = new Graph(500), DrawTimeGraph = new Graph(500); #endif private readonly List tempSavedIdentifiers = new List(); public IReadOnlyList GetSavedIdentifiers { get { lock (mutex) { tempSavedIdentifiers.Clear(); tempSavedIdentifiers.AddRange(avgTicksPerFrame.Keys); } return tempSavedIdentifiers; } } public PerformanceCounter() { timer.Start(); } public void AddElapsedTicks(string identifier, long ticks) { lock (mutex) { if (!elapsedTicks.ContainsKey(identifier)) { elapsedTicks.Add(identifier, new Queue()); } elapsedTicks[identifier].Enqueue(ticks); if (elapsedTicks[identifier].Count > MaximumSamples) { elapsedTicks[identifier].Dequeue(); avgTicksPerFrame[identifier] = (long)elapsedTicks[identifier].Average(i => i); } } } public float GetAverageElapsedMillisecs(string identifier) { long ticksPerFrame = 0; lock (mutex) { avgTicksPerFrame.TryGetValue(identifier, out ticksPerFrame); } return ticksPerFrame * 1000.0f / Stopwatch.Frequency; } public bool Update(double deltaTime) { if (deltaTime == 0.0f) { return false; } CurrentFramesPerSecond = 1.0 / deltaTime; sampleBuffer.Enqueue(CurrentFramesPerSecond); if (sampleBuffer.Count > MaximumSamples) { sampleBuffer.Dequeue(); AverageFramesPerSecond = sampleBuffer.Average(); } else { AverageFramesPerSecond = CurrentFramesPerSecond; } long currentTime = timer.ElapsedMilliseconds; long currentSecond = currentTime / 1000; if (currentSecond > lastSecondMark) { averageFramesPerSecondBuffer.Enqueue(AverageFramesPerSecond); lastSecondMark = currentSecond; } if (currentTime - lastMinuteMark >= 60 * 1000 && /* we don't need info of the FPS every minute, we can get a good sample size just by logging a small sample */ GameAnalyticsManager.ShouldLogRandomSample()) { //the FPS could be even higher than this on a high-end monitor, but let's restrict it to 144 to reduce the number of distinct event IDs const int MaxFPS = 144; AverageFramesPerSecondInPastMinute = averageFramesPerSecondBuffer.Average(); GameAnalyticsManager.AddDesignEvent($"FPS:{MathHelper.Clamp((int)AverageFramesPerSecondInPastMinute, 0, MaxFPS)}"); GameAnalyticsManager.AddDesignEvent($"FPSLowest:{MathHelper.Clamp((int)averageFramesPerSecondBuffer.Min(), 0, MaxFPS)}"); averageFramesPerSecondBuffer.Clear(); lastMinuteMark = currentTime; } return true; } } }