Render Profiler in GUI (#854)
* move profiler output to gui * addressed commits, rebased * removed whitespaces
This commit is contained in:
parent
db9f8f999f
commit
f2b9a9c2b0
41 changed files with 1358 additions and 1639 deletions
223
Ryujinx.Debugger/Profiler/InternalProfile.cs
Normal file
223
Ryujinx.Debugger/Profiler/InternalProfile.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue