using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Ryujinx.Common;
namespace Ryujinx.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);
_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)
IsBegin = false,
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();