app: bench v2 -- a real fling, typing and keyboard phases
Iris's ask after using the Compose bench build on her phone: the old scroll phase used animateScrollBy, which can only ever cover the fixed distance/time it's given, so it never flings the way a real fast swipe does. BenchRun.run now has four phases: fling (8 flings out + 8 back through the list's own FlingBehavior at 12,000px/s), stream (unchanged), type (600 fixed characters into the real composer TextFieldValue, then deleted, to exercise wrapping and the transcript being pushed upward), and keyboard (five show/hide cycles via WindowInsetsControllerCompat, each confirmed by isImeVisible rather than assumed). FrameStats.markPhase/phaseLines slice the same FrameMetrics recording by phase rather than running a second recorder; debugReport gains a phaseFrames section ahead of the existing whole-run frames/accounting/ work sections, which are otherwise unchanged. Also fixes a pre-existing, unrelated break in MainActivity.kt's benchSessionSummary() -- missing several SessionSummary constructor arguments from an earlier change -- since it blocked compileBenchKotlin outright. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
3b80a88f3b
commit
a8d24553d5
5 files changed
+319
-39
No files matched your search
@@ -42,6 +42,21 @@ object FrameStats {
|
||||
private val gpu = ArrayList<Long>()
|
||||
private var since = System.currentTimeMillis()
|
||||
|
||||
/**
|
||||
* Where a named phase of a scripted run (bench v2's fling/stream/type/keyboard) started, as an
|
||||
* index into [total] and a wall-clock time -- not a second recorder, just a mark on this one,
|
||||
* so a phase's frames are the same [FrameMetrics] the whole-run report already has, sliced.
|
||||
*/
|
||||
private data class PhaseMark(val name: String, val startIndex: Int, val startMs: Long)
|
||||
|
||||
private val phaseMarks = ArrayList<PhaseMark>()
|
||||
|
||||
/** Call at the start of each named phase of a scripted run; see [BenchRun]. */
|
||||
@Synchronized
|
||||
fun markPhase(name: String) {
|
||||
phaseMarks += PhaseMark(name, total.size, System.currentTimeMillis())
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun add(metrics: FrameMetrics) {
|
||||
// The first frame after a window opens includes inflating it and is nobody's scroll.
|
||||
@@ -69,6 +84,7 @@ object FrameStats {
|
||||
listOf(total, waited, input, animation, layout, draw, sync, issue, swap, gpu).forEach {
|
||||
it.clear()
|
||||
}
|
||||
phaseMarks.clear()
|
||||
since = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
@@ -95,6 +111,38 @@ object FrameStats {
|
||||
) + if (gpu.isEmpty()) emptyList() else listOf(phase("gpu ", gpu))
|
||||
}
|
||||
|
||||
/**
|
||||
* One block per [markPhase] call: how many frames landed between that mark and the next (or the
|
||||
* end of the run, for the last one), how many were late, the total/p50/p90/p99, the worst
|
||||
* single frame, and how long the phase actually ran. Marks with no frames between them (a phase
|
||||
* that finished before a frame was drawn) still get a line rather than being silently dropped
|
||||
* -- UI_RULES' "say what you don't know" applies to a phase as much as to a single number.
|
||||
*/
|
||||
@Synchronized
|
||||
fun phaseLines(refreshHz: Float): List<String> {
|
||||
if (phaseMarks.isEmpty()) return emptyList()
|
||||
val budget = if (refreshHz > 0) 1000.0 / refreshHz else 16.7
|
||||
val lines = ArrayList<String>()
|
||||
phaseMarks.forEachIndexed { i, mark ->
|
||||
val endIndex = if (i + 1 < phaseMarks.size) phaseMarks[i + 1].startIndex else total.size
|
||||
val endMs =
|
||||
if (i + 1 < phaseMarks.size) phaseMarks[i + 1].startMs
|
||||
else System.currentTimeMillis()
|
||||
val samples = total.subList(mark.startIndex, endIndex)
|
||||
val seconds = (endMs - mark.startMs) / 1000.0
|
||||
lines += " ${mark.name}: ${samples.size} frames over ${"%.1f".format(seconds)}s"
|
||||
if (samples.isEmpty()) {
|
||||
lines += " no frames recorded in this phase"
|
||||
} else {
|
||||
val late = samples.count { it / 1_000_000.0 > budget }
|
||||
lines += " late: $late (${percent(late, samples.size)})"
|
||||
lines += " " + phase("total ", samples)
|
||||
lines += " worst ${"%.1fms".format(samples.max() / 1_000_000.0)}"
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
/** How long the frames recorded here spent in their draw phase, and how many there were. */
|
||||
@Synchronized fun drawPhase(): Pair<Long, Int> = draw.sum() to draw.size
|
||||
|
||||
|
||||
Reference in new issue
Block a user