Improved time tracking to keep history

This commit is contained in:
Andy Adshead 2019-01-28 17:00:34 +00:00
commit 8a40e0a77c
7 changed files with 174 additions and 45 deletions

View file

@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading;
namespace Ryujinx.Profiler namespace Ryujinx.Profiler
{ {
@ -14,57 +15,58 @@ namespace Ryujinx.Profiler
private readonly object _sessionLock = new object(); private readonly object _sessionLock = new object();
private int _sessionCounter = 0; private int _sessionCounter = 0;
public InternalProfile() // Cleanup thread
private readonly Thread _cleanupThread;
private bool _cleanupRunning;
private readonly long _history;
public InternalProfile(long history)
{ {
Timers = new ConcurrentDictionary<ProfileConfig, TimingInfo>(); Timers = new ConcurrentDictionary<ProfileConfig, TimingInfo>();
_history = history;
_cleanupRunning = true;
// Create low priority cleanup thread, it only cleans up RAM hence the low priority
_cleanupThread = new Thread(CleanupLoop)
{
Priority = ThreadPriority.Lowest
};
_cleanupThread.Start();
SW = new Stopwatch(); SW = new Stopwatch();
SW.Start(); SW.Start();
} }
private void CleanupLoop()
{
while (_cleanupRunning)
{
foreach (var timer in Timers)
{
timer.Value.Cleanup(SW.ElapsedTicks - _history);
}
// No need to run too often
Thread.Sleep(50);
}
}
public void BeginProfile(ProfileConfig config) public void BeginProfile(ProfileConfig config)
{ {
long timestamp = SW.ElapsedTicks; Timers.GetOrAdd(config, profileConfig => new TimingInfo()).Begin(SW.ElapsedTicks);
Timers.AddOrUpdate(config,
(c) => CreateTimer(timestamp),
((s, info) =>
{
info.BeginTime = timestamp;
return info;
}));
} }
public void EndProfile(ProfileConfig config) public void EndProfile(ProfileConfig config)
{ {
long timestamp = SW.ElapsedTicks; if (Timers.TryGetValue(config, out var timingInfo))
{
Timers.AddOrUpdate(config, timingInfo.End(SW.ElapsedTicks);
(c => new TimingInfo()),
((s, time) => UpdateTimer(time, timestamp)));
} }
else
private TimingInfo CreateTimer(long timestamp)
{ {
return new TimingInfo() // Throw exception if config isn't already being tracked
{ throw new Exception($"Profiler end called before begin for {config.Tag}");
BeginTime = timestamp,
LastTime = 0,
Count = 0,
Instant = 0,
InstantCount = 0,
};
} }
private TimingInfo UpdateTimer(TimingInfo time, long timestamp)
{
time.Count++;
time.InstantCount++;
time.LastTime = timestamp - time.BeginTime;
time.TotalTime += time.LastTime;
time.Instant += time.LastTime;
return time;
} }
public string GetSession() public string GetSession()
@ -103,5 +105,11 @@ namespace Ryujinx.Profiler
return outDict; return outDict;
} }
public void Dispose()
{
_cleanupRunning = false;
_cleanupThread.Join();
}
} }
} }

View file

@ -16,7 +16,8 @@ namespace Ryujinx.Profiler
return false; return false;
if (_profileInstance == null) if (_profileInstance == null)
_profileInstance = new InternalProfile(); _profileInstance = new InternalProfile(_settings.History);
return true; return true;
} }
@ -32,6 +33,8 @@ namespace Ryujinx.Profiler
if (_settings.FileDumpEnabled) if (_settings.FileDumpEnabled)
DumpProfile.ToFile(_settings.DumpLocation, _profileInstance); DumpProfile.ToFile(_settings.DumpLocation, _profileInstance);
_profileInstance.Dispose();
} }
public static void Begin(ProfileConfig config) public static void Begin(ProfileConfig config)
@ -74,6 +77,11 @@ namespace Ryujinx.Profiler
return (((double)ticks) / Stopwatch.Frequency) * 1000.0; return (((double)ticks) / Stopwatch.Frequency) * 1000.0;
} }
public static long ConvertSecondsToTicks(double seconds)
{
return (long)(seconds * Stopwatch.Frequency);
}
public static Dictionary<ProfileConfig, TimingInfo> GetProfilingData() public static Dictionary<ProfileConfig, TimingInfo> GetProfilingData()
{ {
if (!ProfilingEnabled()) if (!ProfilingEnabled())

View file

@ -11,5 +11,8 @@ namespace Ryujinx.Profiler
public bool FileDumpEnabled = false; public bool FileDumpEnabled = false;
public string DumpLocation = ""; public string DumpLocation = "";
public float UpdateRate = 0.1f; public float UpdateRate = 0.1f;
// 19531225 = 5 seconds in ticks
public long History = 19531225;
} }
} }

View file

@ -1,10 +1,17 @@
namespace Ryujinx.Profiler using System;
using System.Collections.Generic;
namespace Ryujinx.Profiler
{ {
public struct TimingInfo public struct Timestamp
{
public long BeginTime;
public long EndTime;
}
public class TimingInfo
{ {
// Timestamps // Timestamps
public long BeginTime;
public long LastTime;
public long TotalTime; public long TotalTime;
public long Instant; public long Instant;
@ -14,5 +21,101 @@
// Work out average // Work out average
public long AverageTime => (Count == 0) ? -1 : TotalTime / Count; public long AverageTime => (Count == 0) ? -1 : TotalTime / Count;
// Timestamp collection
public List<Timestamp> Timestamps;
private readonly object timestampLock = new object();
private Timestamp currentTimestamp;
// Depth of current timer,
// each begin call increments and each end call decrements
private int depth;
public TimingInfo()
{
Timestamps = new List<Timestamp>();
depth = 0;
}
public void Begin(long beginTime)
{
lock (timestampLock)
{
// Finish current timestamp if already running
if (depth > 0)
{
EndUnsafe(beginTime);
}
BeginUnsafe(beginTime);
depth++;
}
}
private void BeginUnsafe(long beginTime)
{
currentTimestamp.BeginTime = beginTime;
currentTimestamp.EndTime = -1;
}
public void End(long endTime)
{
lock (timestampLock)
{
depth--;
if (depth < 0)
{
throw new Exception("Timing info end called without corresponding begin");
}
EndUnsafe(endTime);
// Still have others using this timing info so recreate start for them
if (depth > 0)
{
BeginUnsafe(endTime);
}
}
}
private void EndUnsafe(long endTime)
{
currentTimestamp.EndTime = endTime;
Timestamps.Add(currentTimestamp);
var delta = currentTimestamp.EndTime - currentTimestamp.BeginTime;
TotalTime += delta;
Instant += delta;
Count++;
InstantCount++;
}
// Remove any timestamps before given timestamp to free memory
public void Cleanup(long before)
{
lock (timestampLock)
{
int toRemove = 0;
for (int i = 0; i < Timestamps.Count; i++)
{
if (Timestamps[i].EndTime < before)
{
toRemove++;
}
else
{
// Assume timestamps are in chronological order so no more need to be removed
break;
}
}
if (toRemove > 0)
Timestamps.RemoveRange(0, toRemove);
}
}
} }
} }

View file

@ -73,6 +73,7 @@ namespace Ryujinx
FileDumpEnabled = profilePath != "", FileDumpEnabled = profilePath != "",
DumpLocation = profilePath, DumpLocation = profilePath,
UpdateRate = 1.0f / Convert.ToSingle(parser.Value("Profiling_Update_Rate")), UpdateRate = 1.0f / Convert.ToSingle(parser.Value("Profiling_Update_Rate")),
History = Profile.ConvertSecondsToTicks(Convert.ToDouble(parser.Value("Profiling_History"))),
}); });
SystemLanguage SetLanguage = Enum.Parse<SystemLanguage>(parser.Value("System_Language")); SystemLanguage SetLanguage = Enum.Parse<SystemLanguage>(parser.Value("System_Language"));

View file

@ -5,6 +5,7 @@ using Ryujinx.Graphics.Gal.OpenGL;
using Ryujinx.HLE; using Ryujinx.HLE;
using System; using System;
using System.IO; using System.IO;
using Ryujinx.Profiler;
namespace Ryujinx namespace Ryujinx
{ {
@ -80,6 +81,8 @@ namespace Ryujinx
{ {
screen.MainLoop(); screen.MainLoop();
Profile.FinishProfiling();
device.Dispose(); device.Dispose();
} }

View file

@ -31,6 +31,9 @@ Profile_Dump_Path =
#Update rate for profiler UI, in hertz #Update rate for profiler UI, in hertz
Profiling_Update_Rate = 4 Profiling_Update_Rate = 4
#Set how long to keep profiling data in seconds, reduce if profiling is taking too much RAM
Profiling_History = 5
#System Language list: https://gist.github.com/HorrorTroll/b6e4a88d774c3c9b3bdf54d79a7ca43b #System Language list: https://gist.github.com/HorrorTroll/b6e4a88d774c3c9b3bdf54d79a7ca43b
#Change System Language #Change System Language
System_Language = AmericanEnglish System_Language = AmericanEnglish