InternalProfile.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using Ryujinx.Common;
  8. namespace Ryujinx.Profiler
  9. {
  10. public class InternalProfile
  11. {
  12. private struct TimerQueueValue
  13. {
  14. public ProfileConfig Config;
  15. public long Time;
  16. public bool IsBegin;
  17. }
  18. internal Dictionary<ProfileConfig, TimingInfo> Timers { get; set; }
  19. private readonly object _timerQueueClearLock = new object();
  20. private ConcurrentQueue<TimerQueueValue> _timerQueue;
  21. private int _sessionCounter = 0;
  22. // Cleanup thread
  23. private readonly Thread _cleanupThread;
  24. private bool _cleanupRunning;
  25. private readonly long _history;
  26. private long _preserve;
  27. // Timing flags
  28. private TimingFlag[] _timingFlags;
  29. private long[] _timingFlagAverages;
  30. private long[] _timingFlagLast;
  31. private long[] _timingFlagLastDelta;
  32. private int _timingFlagCount;
  33. private int _timingFlagIndex;
  34. private int _maxFlags;
  35. private Action<TimingFlag> _timingFlagCallback;
  36. public InternalProfile(long history, int maxFlags)
  37. {
  38. _maxFlags = maxFlags;
  39. Timers = new Dictionary<ProfileConfig, TimingInfo>();
  40. _timingFlags = new TimingFlag[_maxFlags];
  41. _timingFlagAverages = new long[(int)TimingFlagType.Count];
  42. _timingFlagLast = new long[(int)TimingFlagType.Count];
  43. _timingFlagLastDelta = new long[(int)TimingFlagType.Count];
  44. _timerQueue = new ConcurrentQueue<TimerQueueValue>();
  45. _history = history;
  46. _cleanupRunning = true;
  47. // Create cleanup thread.
  48. _cleanupThread = new Thread(CleanupLoop);
  49. _cleanupThread.Start();
  50. }
  51. private void CleanupLoop()
  52. {
  53. bool queueCleared = false;
  54. while (_cleanupRunning)
  55. {
  56. // Ensure we only ever have 1 instance modifying timers or timerQueue
  57. if (Monitor.TryEnter(_timerQueueClearLock))
  58. {
  59. queueCleared = ClearTimerQueue();
  60. // Calculate before foreach to mitigate redundant calculations
  61. long cleanupBefore = PerformanceCounter.ElapsedTicks - _history;
  62. long preserveStart = _preserve - _history;
  63. // Each cleanup is self contained so run in parallel for maximum efficiency
  64. Parallel.ForEach(Timers, (t) => t.Value.Cleanup(cleanupBefore, preserveStart, _preserve));
  65. Monitor.Exit(_timerQueueClearLock);
  66. }
  67. // Only sleep if queue was successfully cleared
  68. if (queueCleared)
  69. {
  70. Thread.Sleep(5);
  71. }
  72. }
  73. }
  74. private bool ClearTimerQueue()
  75. {
  76. int count = 0;
  77. while (_timerQueue.TryDequeue(out TimerQueueValue item))
  78. {
  79. if (!Timers.TryGetValue(item.Config, out TimingInfo value))
  80. {
  81. value = new TimingInfo();
  82. Timers.Add(item.Config, value);
  83. }
  84. if (item.IsBegin)
  85. {
  86. value.Begin(item.Time);
  87. }
  88. else
  89. {
  90. value.End(item.Time);
  91. }
  92. // Don't block for too long as memory disposal is blocked while this function runs
  93. if (count++ > 10000)
  94. {
  95. return false;
  96. }
  97. }
  98. return true;
  99. }
  100. public void FlagTime(TimingFlagType flagType)
  101. {
  102. int flagId = (int)flagType;
  103. _timingFlags[_timingFlagIndex] = new TimingFlag()
  104. {
  105. FlagType = flagType,
  106. Timestamp = PerformanceCounter.ElapsedTicks
  107. };
  108. _timingFlagCount = Math.Max(_timingFlagCount + 1, _maxFlags);
  109. // Work out average
  110. if (_timingFlagLast[flagId] != 0)
  111. {
  112. _timingFlagLastDelta[flagId] = _timingFlags[_timingFlagIndex].Timestamp - _timingFlagLast[flagId];
  113. _timingFlagAverages[flagId] = (_timingFlagAverages[flagId] == 0) ? _timingFlagLastDelta[flagId] :
  114. (_timingFlagLastDelta[flagId] + _timingFlagAverages[flagId]) >> 1;
  115. }
  116. _timingFlagLast[flagId] = _timingFlags[_timingFlagIndex].Timestamp;
  117. // Notify subscribers
  118. _timingFlagCallback?.Invoke(_timingFlags[_timingFlagIndex]);
  119. if (++_timingFlagIndex >= _maxFlags)
  120. {
  121. _timingFlagIndex = 0;
  122. }
  123. }
  124. public void BeginProfile(ProfileConfig config)
  125. {
  126. _timerQueue.Enqueue(new TimerQueueValue()
  127. {
  128. Config = config,
  129. IsBegin = true,
  130. Time = PerformanceCounter.ElapsedTicks,
  131. });
  132. }
  133. public void EndProfile(ProfileConfig config)
  134. {
  135. _timerQueue.Enqueue(new TimerQueueValue()
  136. {
  137. Config = config,
  138. IsBegin = false,
  139. Time = PerformanceCounter.ElapsedTicks,
  140. });
  141. }
  142. public string GetSession()
  143. {
  144. // Can be called from multiple threads so we need to ensure no duplicate sessions are generated
  145. return Interlocked.Increment(ref _sessionCounter).ToString();
  146. }
  147. public List<KeyValuePair<ProfileConfig, TimingInfo>> GetProfilingData()
  148. {
  149. _preserve = PerformanceCounter.ElapsedTicks;
  150. lock (_timerQueueClearLock)
  151. {
  152. ClearTimerQueue();
  153. return Timers.ToList();
  154. }
  155. }
  156. public TimingFlag[] GetTimingFlags()
  157. {
  158. int count = Math.Max(_timingFlagCount, _maxFlags);
  159. TimingFlag[] outFlags = new TimingFlag[count];
  160. for (int i = 0, sourceIndex = _timingFlagIndex; i < count; i++, sourceIndex++)
  161. {
  162. if (sourceIndex >= _maxFlags)
  163. sourceIndex = 0;
  164. outFlags[i] = _timingFlags[sourceIndex];
  165. }
  166. return outFlags;
  167. }
  168. public (long[], long[]) GetTimingAveragesAndLast()
  169. {
  170. return (_timingFlagAverages, _timingFlagLastDelta);
  171. }
  172. public void RegisterFlagReceiver(Action<TimingFlag> receiver)
  173. {
  174. _timingFlagCallback = receiver;
  175. }
  176. public void Dispose()
  177. {
  178. _cleanupRunning = false;
  179. _cleanupThread.Join();
  180. }
  181. }
  182. }