Files
HellionChat/HellionChat/SelfTests/PerformanceBaselineStep.cs
T
JonKazama-Hellion b9feb8650f chore(comments): drop the spec task codes the last pass missed
Codes like POP-1c or B4b-2 name a task in a planning document, not
anything in the code. A reader has no way to resolve them and they age
into noise the moment the document is closed. Where a code was used as a
reference, the sentence now names the function it meant.
2026-08-20 07:54:45 +02:00

112 lines
3.7 KiB
C#

using Dalamud.Bindings.ImGui;
using Dalamud.Plugin.SelfTest;
namespace HellionChat.SelfTests;
// Optional metric capture. Accumulates 1000 steady-state frames of ImGui IO
// counters plus the plugin's full-Draw wall-time (Plugin.LastDrawMs),
// then writes a single perf-baseline.json into the plugin ConfigDirectory so
// the cycle-notes author can copy the baseline figures without a separate
// profiling harness. The step only records — it never fails on a threshold
// (the budgets are evaluated by a human against the JSON ("optional,
// manual"). It returns Waiting until the sample window fills, mirroring the
// per-frame poll idiom of ThemeSwitchSelfTestStep.
internal sealed class PerformanceBaselineStep : ISelfTestStep
{
// Steady-state window. 1000 frames ≈ 16s at 60fps, long enough to
// average out GC blips without making the manual step tedious.
private const int TargetFrames = 1000;
// Rough draw-call proxy: ImGui emits 6 indices per quad, so vertices/6 is an
// intentional under-count of draw work, not the exact quad count.
private const int VerticesPerQuadProxy = 6;
private readonly Plugin _plugin;
private int _frames;
private ulong _lastFrameCount;
private double _drawMsSum;
private double _drawMsMax;
private long _vertexSum;
private long _vertexMax;
private double _deltaMsSum;
private double _deltaMsMax;
private string? _logPath;
public PerformanceBaselineStep(Plugin plugin)
{
_plugin = plugin;
}
public string Name => "Hellion Chat - Performance baseline capture";
public SelfTestStepResult RunStep()
{
var io = ImGui.GetIO();
// Count each real frame once. Without the FrameCount gate a step that
// is polled more than once per frame would inflate the sample count.
var frameCount = Plugin.Interface.UiBuilder.FrameCount;
if (frameCount != _lastFrameCount)
{
_lastFrameCount = frameCount;
_frames++;
var drawMs = _plugin.LastDrawMs;
_drawMsSum += drawMs;
if (drawMs > _drawMsMax)
_drawMsMax = drawMs;
long vertices = io.MetricsRenderVertices;
_vertexSum += vertices;
if (vertices > _vertexMax)
_vertexMax = vertices;
var deltaMs = io.DeltaTime * 1000f;
_deltaMsSum += deltaMs;
if (deltaMs > _deltaMsMax)
_deltaMsMax = deltaMs;
}
if (_frames < TargetFrames)
{
ImGui.Text(
$"Sampling steady-state… {_frames}/{TargetFrames} frames. "
+ "Keep the chat window visible and idle."
);
return SelfTestStepResult.Waiting;
}
_logPath ??= WriteBaselineLog();
ImGui.Text($"Baseline captured ({TargetFrames} frames). Wrote: {_logPath}");
return SelfTestStepResult.Pass;
}
public void CleanUp()
{
_frames = 0;
_lastFrameCount = 0;
_drawMsSum = 0;
_drawMsMax = 0;
_vertexSum = 0;
_vertexMax = 0;
_deltaMsSum = 0;
_deltaMsMax = 0;
_logPath = null;
}
private string WriteBaselineLog()
{
// Disk write happens here, never in the per-frame hot path.
return PerformanceBaselineLog.Write(
avgDrawMs: _drawMsSum / TargetFrames,
maxDrawMs: _drawMsMax,
avgDrawCallsProxy: _vertexSum / (double)TargetFrames / VerticesPerQuadProxy,
maxDrawCallsProxy: _vertexMax / (double)VerticesPerQuadProxy,
avgDeltaMs: _deltaMsSum / TargetFrames,
maxDeltaMs: _deltaMsMax,
frames: TargetFrames
);
}
}