aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Debugger/Profiler/InternalProfile.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Debugger/Profiler/InternalProfile.cs')
-rw-r--r--Ryujinx.Debugger/Profiler/InternalProfile.cs223
1 files changed, 223 insertions, 0 deletions
diff --git a/Ryujinx.Debugger/Profiler/InternalProfile.cs b/Ryujinx.Debugger/Profiler/InternalProfile.cs
new file mode 100644
index 00000000..0bda9e04
--- /dev/null
+++ b/Ryujinx.Debugger/Profiler/InternalProfile.cs
@@ -0,0 +1,223 @@
+using Ryujinx.Common;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Debugger.Profiler
+{
+ public class InternalProfile
+ {
+ private struct TimerQueueValue
+ {
+ public ProfileConfig Config;
+ public long Time;
+ public bool IsBegin;
+ }
+
+ internal Dictionary<ProfileConfig, TimingInfo> Timers { get; set; }
+
+ private readonly object _timerQueueClearLock = new object();
+ private ConcurrentQueue<TimerQueueValue> _timerQueue;
+
+ private int _sessionCounter = 0;
+
+ // Cleanup thread
+ private readonly Thread _cleanupThread;
+ private bool _cleanupRunning;
+ private readonly long _history;
+ private long _preserve;
+
+ // Timing flags
+ private TimingFlag[] _timingFlags;
+ private long[] _timingFlagAverages;
+ private long[] _timingFlagLast;
+ private long[] _timingFlagLastDelta;
+ private int _timingFlagCount;
+ private int _timingFlagIndex;
+
+ private int _maxFlags;
+
+ private Action<TimingFlag> _timingFlagCallback;
+
+ public InternalProfile(long history, int maxFlags)
+ {
+ _maxFlags = maxFlags;
+ Timers = new Dictionary<ProfileConfig, TimingInfo>();
+ _timingFlags = new TimingFlag[_maxFlags];
+ _timingFlagAverages = new long[(int)TimingFlagType.Count];
+ _timingFlagLast = new long[(int)TimingFlagType.Count];
+ _timingFlagLastDelta = new long[(int)TimingFlagType.Count];
+ _timerQueue = new ConcurrentQueue<TimerQueueValue>();
+ _history = history;
+ _cleanupRunning = true;
+
+ // Create cleanup thread.
+ _cleanupThread = new Thread(CleanupLoop)
+ {
+ Name = "Profiler.CleanupThread"
+ };
+ _cleanupThread.Start();
+ }
+
+ private void CleanupLoop()
+ {
+ bool queueCleared = false;
+
+ while (_cleanupRunning)
+ {
+ // Ensure we only ever have 1 instance modifying timers or timerQueue
+ if (Monitor.TryEnter(_timerQueueClearLock))
+ {
+ queueCleared = ClearTimerQueue();
+
+ // Calculate before foreach to mitigate redundant calculations
+ long cleanupBefore = PerformanceCounter.ElapsedTicks - _history;
+ long preserveStart = _preserve - _history;
+
+ // Each cleanup is self contained so run in parallel for maximum efficiency
+ Parallel.ForEach(Timers, (t) => t.Value.Cleanup(cleanupBefore, preserveStart, _preserve));
+
+ Monitor.Exit(_timerQueueClearLock);
+ }
+
+ // Only sleep if queue was successfully cleared
+ if (queueCleared)
+ {
+ Thread.Sleep(5);
+ }
+ }
+ }
+
+ private bool ClearTimerQueue()
+ {
+ int count = 0;
+
+ while (_timerQueue.TryDequeue(out TimerQueueValue item))
+ {
+ if (!Timers.TryGetValue(item.Config, out TimingInfo value))
+ {
+ value = new TimingInfo();
+ Timers.Add(item.Config, value);
+ }
+
+ if (item.IsBegin)
+ {
+ value.Begin(item.Time);
+ }
+ else
+ {
+ value.End(item.Time);
+ }
+
+ // Don't block for too long as memory disposal is blocked while this function runs
+ if (count++ > 10000)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public void FlagTime(TimingFlagType flagType)
+ {
+ int flagId = (int)flagType;
+
+ _timingFlags[_timingFlagIndex] = new TimingFlag()
+ {
+ FlagType = flagType,
+ Timestamp = PerformanceCounter.ElapsedTicks
+ };
+
+ _timingFlagCount = Math.Max(_timingFlagCount + 1, _maxFlags);
+
+ // Work out average
+ if (_timingFlagLast[flagId] != 0)
+ {
+ _timingFlagLastDelta[flagId] = _timingFlags[_timingFlagIndex].Timestamp - _timingFlagLast[flagId];
+ _timingFlagAverages[flagId] = (_timingFlagAverages[flagId] == 0) ? _timingFlagLastDelta[flagId] :
+ (_timingFlagLastDelta[flagId] + _timingFlagAverages[flagId]) >> 1;
+ }
+ _timingFlagLast[flagId] = _timingFlags[_timingFlagIndex].Timestamp;
+
+ // Notify subscribers
+ _timingFlagCallback?.Invoke(_timingFlags[_timingFlagIndex]);
+
+ if (++_timingFlagIndex >= _maxFlags)
+ {
+ _timingFlagIndex = 0;
+ }
+ }
+
+ public void BeginProfile(ProfileConfig config)
+ {
+ _timerQueue.Enqueue(new TimerQueueValue()
+ {
+ Config = config,
+ IsBegin = true,
+ Time = PerformanceCounter.ElapsedTicks,
+ });
+ }
+
+ public void EndProfile(ProfileConfig config)
+ {
+ _timerQueue.Enqueue(new TimerQueueValue()
+ {
+ Config = config,
+ IsBegin = false,
+ Time = PerformanceCounter.ElapsedTicks,
+ });
+ }
+
+ public string GetSession()
+ {
+ // Can be called from multiple threads so we need to ensure no duplicate sessions are generated
+ return Interlocked.Increment(ref _sessionCounter).ToString();
+ }
+
+ public List<KeyValuePair<ProfileConfig, TimingInfo>> GetProfilingData()
+ {
+ _preserve = PerformanceCounter.ElapsedTicks;
+
+ lock (_timerQueueClearLock)
+ {
+ ClearTimerQueue();
+ return Timers.ToList();
+ }
+ }
+
+ public TimingFlag[] GetTimingFlags()
+ {
+ int count = Math.Max(_timingFlagCount, _maxFlags);
+ TimingFlag[] outFlags = new TimingFlag[count];
+
+ for (int i = 0, sourceIndex = _timingFlagIndex; i < count; i++, sourceIndex++)
+ {
+ if (sourceIndex >= _maxFlags)
+ sourceIndex = 0;
+ outFlags[i] = _timingFlags[sourceIndex];
+ }
+
+ return outFlags;
+ }
+
+ public (long[], long[]) GetTimingAveragesAndLast()
+ {
+ return (_timingFlagAverages, _timingFlagLastDelta);
+ }
+
+ public void RegisterFlagReceiver(Action<TimingFlag> receiver)
+ {
+ _timingFlagCallback = receiver;
+ }
+
+ public void Dispose()
+ {
+ _cleanupRunning = false;
+ _cleanupThread.Join();
+ }
+ }
+}