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