As a curious programmer interested in creating utility functions and/or small applications for my own personal use (and as an learning exercise to keep my software engineering chops healthy), @brad have you given any thought into enabling the parsing of Profiler data, or perhaps exposing API methods to parse that data?
I have written a few simple modules (nothing suitable or tested well-enough for public release). Such as migrating all of the various patch libraries for my numerous VSTs into a central location, and then tracking them with a git repo. Or tracking the versions of each VST, and trying to check which plugins have new versions available.
Optimizing my Racks and Songs by being able to remotely analyze the data that Profiler creates is another utility I would love to tackle.
Unlike most of the other data files created by Cantabile, the profiler data is binary (not JSON) - primarily to keep the size down. I never expected anyone would want to process this themselves, but Cantabile users always surprise me
Anyway, here’s an cut/paste extract of various pieces of C# code that should get you started.
Let me know if anything is missing.
class Profiler
{
public static uint Signature = 0x48525043; // "CPRH" = Cantabile PRofile History
public static int CurrentVersion = 1;
public void Read(filename)
{
using (var stm = new FileStream(filename, FileMode.Open))
{
using (var compress = new DeflateStream(stm, CompressionMode.Decompress))
{
using (var b = new BinaryReader(compress))
{
var sig = b.ReadUInt32();
if (sig != Signature)
throw new InvalidDataException("Not a valid profile file");
var ver = b.ReadInt32();
if (ver != CurrentVersion)
throw new InvalidDataException("Unsupported file version");
_timeFactorNum = b.ReadInt64();
_timeFactorDen = b.ReadInt64();
_profilerQueueSize = b.ReadInt32();
int count = b.ReadInt32();
for (int i = 0; i < count; i++)
{
var interval = new Interval();
interval.Read(b, ver);
_intervalHistory.Add(interval);
}
}
}
}
}
public long timeFactorNum => _timeFactorNum;
public long timeFactorDen => _timeFactorDen;
public int profilerQueueSize => _profilerQueueSize;
public IReadOnlyList<Interval> IntervalHistory => _intervalHistory;
private long _timeFactorNum;
private long _timeFactorDen;
private int _profilerQueueSize;
private List<Interval> _intervalHistory = new List<Interval>();
// Represents a single time interval (currently hard coded to 1 second)
public class Interval : Range
{
public uint elapsedTime;
public DateTime systemTime;
public int sampleRate;
public int bufferSize;
public int bufferCount;
public MEMORYCOUNTERS memoryCounters;
public CPUTIMES cpuTimes;
public uint pageFaultDelta;
public string songName;
public string stateName;
public int coreObjectCount;
public int lockedZombieCount;
public int loadedRackCount;
public int runningRackCount;
public int profilerQueueUsage;
// Load from stream
public override void Read(BinaryReader b, int version)
{
elapsedTime = b.ReadUInt32();
systemTime = DateTime.FromBinary(b.ReadInt64());
sampleRate = b.ReadInt32();
bufferSize = b.ReadInt32();
bufferCount = b.ReadInt32();
memoryCounters.Read(b, version);
cpuTimes.Read(b, version);
pageFaultDelta = b.ReadUInt32();
songName = b.ReadString();
stateName = b.ReadString();
coreObjectCount = b.ReadInt32();
lockedZombieCount = b.ReadInt32();
runningRackCount = b.ReadInt32();
loadedRackCount = b.ReadInt32();
profilerQueueUsage = b.ReadInt32();
base.Read(b, version);
}
}
// Represents a profiled range and calculates/stores stats
// for timing across that range. Note that a range consists of
// multiple audio cycles coalesced into one.
public class Range
{
public string name;
public int sortOrder;
public uint count;
public uint totalTime;
public uint minTime;
public uint maxTime;
public List<Range> subRanges;
// Read from stream
public virtual void Read(BinaryReader b, int version)
{
// basics
name = b.ReadString();
sortOrder = b.ReadInt32();
count = b.ReadUInt32();
totalTime = b.ReadUInt32();
minTime = b.ReadUInt32();
maxTime = b.ReadUInt32();
// SubRanges
int subRangeCount = b.ReadInt32();
if (subRangeCount > 0)
{
subRanges = new List<Range>();
for (int i = 0; i < subRangeCount; i++)
{
var sr = new Range();
sr.Read(b, version);
subRanges.Add(sr);
}
}
}
}
public struct MEMORYCOUNTERS
{
public ulong WorkingSetSize;
public ulong MemoryUsage;
public uint PageFaultsTotal; // Core returns total, profiler converts to delta for the interval
public void Read(BinaryReader b, int version)
{
WorkingSetSize = b.ReadUInt64();
MemoryUsage = b.ReadUInt64();
PageFaultsTotal = b.ReadUInt32();
}
}
public struct CPUTIMES
{
public ulong SystemIdleTime;
public ulong SystemKernelTime;
public ulong SystemUserTime;
public ulong ProcessKernelTime;
public ulong ProcessUserTime;
public double ProcessCpuLoad
{
get
{
return (ProcessKernelTime + ProcessUserTime) * 100.0 / (SystemUserTime + SystemKernelTime);
}
}
public double SystemCpuLoad
{
get
{
return (SystemKernelTime + SystemUserTime - SystemIdleTime) * 100.0 / (SystemUserTime + SystemKernelTime);
}
}
public void Read(BinaryReader b, int verion)
{
SystemIdleTime = b.ReadUInt64();
SystemKernelTime = b.ReadUInt64();
SystemUserTime = b.ReadUInt64();
ProcessKernelTime = b.ReadUInt64();
ProcessUserTime = b.ReadUInt64();
}
}
}