feat(analysis): add coverage profiler and report
This commit is contained in:
1 parent
6ff06cfe6e
commit
c9c9bbe68b
2 files changed
+161
No files matched your search
@@ -0,0 +1,69 @@
|
||||
using System.Collections.Generic;
|
||||
using Chip8.Core;
|
||||
|
||||
namespace Chip8.Analysis;
|
||||
|
||||
public sealed class CoverageProfiler
|
||||
{
|
||||
public CoverageProfiler(ushort entry = InstructionDecoder.defaultBaseAddress, ushort baseAddress = InstructionDecoder.defaultBaseAddress)
|
||||
{
|
||||
this.entry = entry;
|
||||
this.baseAddress = baseAddress;
|
||||
}
|
||||
|
||||
public ushort entry { get; }
|
||||
public ushort baseAddress { get; }
|
||||
|
||||
public CoverageReport profile(byte[] program, int maxSteps, Platform platform = Platform.Chip8, Quirks? quirks = null, int seed = 1, bool stopOnSpin = true)
|
||||
{
|
||||
var cfg = ControlFlowGraph.build(program, entry, baseAddress);
|
||||
var reachable = new SortedSet<ushort>(cfg.reachableAddresses);
|
||||
|
||||
var machine = new Machine(quirks, seed, platform);
|
||||
machine.loadRom(program);
|
||||
|
||||
var executionCounts = new Dictionary<ushort, int>();
|
||||
var opcodeHistogram = new Dictionary<ushort, int>();
|
||||
int steps = 0;
|
||||
int spinRun = 0;
|
||||
bool haltedOnSpin = false;
|
||||
|
||||
for (int index = 0; index < maxSteps; index++)
|
||||
{
|
||||
if (machine.exited)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ushort address = machine.pc;
|
||||
ushort opcode = (ushort)((machine.memory[address] << 8) | machine.memory[address + 1]);
|
||||
|
||||
increment(executionCounts, address);
|
||||
increment(opcodeHistogram, opcode);
|
||||
steps++;
|
||||
|
||||
machine.step();
|
||||
|
||||
if (stopOnSpin && machine.pc == address)
|
||||
{
|
||||
spinRun++;
|
||||
if (spinRun >= 2)
|
||||
{
|
||||
haltedOnSpin = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
spinRun = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return new CoverageReport(steps, haltedOnSpin, executionCounts, opcodeHistogram, reachable);
|
||||
}
|
||||
|
||||
static void increment(Dictionary<ushort, int> counter, ushort key)
|
||||
{
|
||||
counter[key] = counter.TryGetValue(key, out int value) ? value + 1 : 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Chip8.Analysis;
|
||||
|
||||
public sealed class CoverageReport
|
||||
{
|
||||
readonly Dictionary<ushort, int> executionCounts;
|
||||
readonly Dictionary<ushort, int> opcodeHistogram;
|
||||
readonly SortedSet<ushort> reachable;
|
||||
|
||||
public CoverageReport(
|
||||
int totalSteps,
|
||||
bool haltedOnSpin,
|
||||
Dictionary<ushort, int> executionCounts,
|
||||
Dictionary<ushort, int> opcodeHistogram,
|
||||
SortedSet<ushort> reachable)
|
||||
{
|
||||
this.totalSteps = totalSteps;
|
||||
this.haltedOnSpin = haltedOnSpin;
|
||||
this.executionCounts = executionCounts;
|
||||
this.opcodeHistogram = opcodeHistogram;
|
||||
this.reachable = reachable;
|
||||
}
|
||||
|
||||
public int totalSteps { get; }
|
||||
public bool haltedOnSpin { get; }
|
||||
|
||||
public IReadOnlyDictionary<ushort, int> executionCountByAddress => executionCounts;
|
||||
public IReadOnlyDictionary<ushort, int> opcodeCounts => opcodeHistogram;
|
||||
|
||||
public IReadOnlyCollection<ushort> executedAddresses => executionCounts.Keys;
|
||||
public IReadOnlyCollection<ushort> reachableAddresses => reachable;
|
||||
|
||||
public int executedInstructionCount => executionCounts.Count;
|
||||
public int reachableInstructionCount => reachable.Count;
|
||||
|
||||
public int executionCount(ushort address) =>
|
||||
executionCounts.TryGetValue(address, out int value) ? value : 0;
|
||||
|
||||
public IReadOnlyCollection<ushort> coveredReachableAddresses =>
|
||||
executionCounts.Keys.Where(address => reachable.Contains(address)).ToList();
|
||||
|
||||
public IReadOnlyCollection<ushort> executedOutsideReachable =>
|
||||
executionCounts.Keys.Where(address => !reachable.Contains(address)).OrderBy(address => address).ToList();
|
||||
|
||||
public double coveragePercent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (reachable.Count == 0)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
int covered = executionCounts.Keys.Count(address => reachable.Contains(address));
|
||||
return 100.0 * covered / reachable.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ushort> reachableButNotExecuted =>
|
||||
reachable.Where(address => !executionCounts.ContainsKey(address)).ToList();
|
||||
|
||||
public (ushort address, int count) hotspot()
|
||||
{
|
||||
ushort best = 0;
|
||||
int bestCount = -1;
|
||||
foreach (var pair in executionCounts)
|
||||
{
|
||||
if (pair.Value > bestCount)
|
||||
{
|
||||
bestCount = pair.Value;
|
||||
best = pair.Key;
|
||||
}
|
||||
}
|
||||
return (best, bestCount < 0 ? 0 : bestCount);
|
||||
}
|
||||
|
||||
public string renderHeatmap()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("coverage: ").Append(coveragePercent.ToString("F1", CultureInfo.InvariantCulture)).Append("% over ");
|
||||
builder.Append(totalSteps).Append(" steps\n");
|
||||
foreach (var address in executionCounts.Keys.OrderBy(value => value))
|
||||
{
|
||||
builder.Append(" 0x").Append(address.ToString("X4", CultureInfo.InvariantCulture));
|
||||
builder.Append(" x").Append(executionCounts[address].ToString(CultureInfo.InvariantCulture)).Append('\n');
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user