Pad/internal/editor/perf_probe.go
Greg Pomerantz be48ad8157 perf: default-off in-app profiler + debug scroll jumps; verify scroll perf & clamping
internal/perf: single-goroutine profiler (no locks). When enabled by the
/storage/emulated/0/PadPerf/enable marker it records one CSV row per logic
frame (seq, ms, frame delta, page, scroll Dp, max-scroll Dp, total lines,
visible byte range), logs a rolling ~1/s summary, and on Stop reports
nearest-rank p50/p90/p99/max. Flushes per row-batch but never fsyncs per
flush (avoids periodic hitches in the logic path).

editor: PerfRecord package-level hook (nil when off) + ProbeRecord;
Logic.emitFrame() now centralizes every frame emission so the profiler sees
each logic frame exactly once, on the owner goroutine. State gains
VisibleStart/VisibleEnd so the probe can confirm shaping stays
viewport-bounded.

logic: optional debug cmd poller (off by default) watches <dir>/cmd as a
one-shot file (top/bottom/frac <0..1>/dp <int>) and the owner applies a
clamped [0,MaxScroll] jump + frame. Enables deterministic large-offset scroll
tests without pixel taps.

main: wires the profiler when the marker file exists; logs present-fps
every 2s; stops the profiler on Destroy.

docs: README package inventory (add internal/perf); architecture §11
profiler facility; spec §5 invariant 7 (scroll always clamped to
[0,maxScroll]) + §6 measured rows; development_plan v5 + Phase 6 results.

Verified: go build/vet + full -race green. On-device 10 MB file:
logic-frame cadence flat across offsets 0.02->1.0 (no large-offset
degradation), visible byte range <=4.3 KB at every offset, clamping exact
across 2->130,955-line files, PSS plateaus ~250 MB (bounded, no leak);
profiler overhead negligible.
2026-08-16 16:53:28 -04:00

31 lines
1.0 KiB
Go

package editor
import "time"
// ProbeRecord captures per-frame context for the in-app profiler (see
// internal/perf). It is filled on the logic goroutine and handed to the
// PerfRecord hook once per frame emission. Debug-only: PerfRecord is nil
// unless the app enables profiling (cmd/pad), so the per-frame cost is a
// single nil check.
type ProbeRecord struct {
T time.Time
DeltaMs float64 // ms since the previous frame emission
Page string // "editor" | "browser"
ScrollDP float32 // editor scroll offset (Dp); browser scroll when on browser
MaxScrollDP float32 // editor max scroll (Dp)
TotalLines int
VisStart int // visible byte range start
VisEnd int // visible byte range end (exclusive)
}
// PerfRecord is an optional per-frame probe hook, set by the app when in-app
// profiling is enabled. It is called on the logic goroutine, once per frame.
var PerfRecord func(ProbeRecord)
func pageName(p Page) string {
if p == EditorPage {
return "editor"
}
return "browser"
}