InternalProfile.cs 7.0 KB

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