Improved time tracking to keep history
This commit is contained in:
parent
4417259c6c
commit
8a40e0a77c
7 changed files with 174 additions and 45 deletions
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue