mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
fix(sidecar): measure the stability streak as observed quiet
parameterising pollUntilStable also moved the clock to the start of the read that opened a run of identical snapshots, so a read's own duration counted as quiet. the pre-existing caller polls a real uiautomator dump: at 400ms a read, 750ms of required quiet became 250ms of observed quiet and the poll settled in two reads instead of four. the parameters stay, the semantics go back.
This commit is contained in:
1 parent
e7f66b8a95
commit
0e35978e25
2 files changed
+29
-37
No files matched your search
@@ -128,15 +128,11 @@ internal fun awaitSettledTree(read: () -> String): String {
|
|||||||
// streak resets and the loop keeps polling instead of declaring a partial
|
// streak resets and the loop keeps polling instead of declaring a partial
|
||||||
// state stable.
|
// state stable.
|
||||||
//
|
//
|
||||||
// The streak is measured from the start of the read that opened the current
|
// streakMillis is quiet the poll OBSERVED: the clock starts when the read that
|
||||||
// run of identical snapshots, not from when that read returned, so a slow read
|
// first matched its predecessor returns, so the reads spanning it are not
|
||||||
// is charged to the streak. That is a deliberate trade and not a free one: the
|
// charged to it. A hierarchy fetch on Android costs more than the poll
|
||||||
// quiet the poll actually OBSERVED spans the last read's start back to the
|
// interval, and charging it would let a 500ms read clear the default 750ms
|
||||||
// first read's return, which is shorter than streakMillis by up to the two
|
// streak having watched 250ms of quiet.
|
||||||
// reads' durations. On Android a hierarchy fetch costs more than the poll
|
|
||||||
// interval, so charging it is the difference between two reads and four, and a
|
|
||||||
// caller wanting the full streak observed has to widen streakMillis rather than
|
|
||||||
// assume it. StabilityPollTest.slowSnapshotReadsCountTowardTheStreak pins this.
|
|
||||||
internal fun pollUntilStable(
|
internal fun pollUntilStable(
|
||||||
timeoutMillis: Long,
|
timeoutMillis: Long,
|
||||||
streakMillis: Long = MIN_STABLE_STREAK_MILLIS,
|
streakMillis: Long = MIN_STABLE_STREAK_MILLIS,
|
||||||
@@ -145,24 +141,25 @@ internal fun pollUntilStable(
|
|||||||
) {
|
) {
|
||||||
if (timeoutMillis <= 0) return
|
if (timeoutMillis <= 0) return
|
||||||
val deadline = System.currentTimeMillis() + timeoutMillis
|
val deadline = System.currentTimeMillis() + timeoutMillis
|
||||||
var runStart = System.currentTimeMillis()
|
|
||||||
var prior = try {
|
var prior = try {
|
||||||
snapshot()
|
snapshot()
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
var streakStart = 0L
|
||||||
while (System.currentTimeMillis() < deadline) {
|
while (System.currentTimeMillis() < deadline) {
|
||||||
Thread.sleep(intervalMillis)
|
Thread.sleep(intervalMillis)
|
||||||
val currentStart = System.currentTimeMillis()
|
|
||||||
val current = try {
|
val current = try {
|
||||||
snapshot()
|
snapshot()
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
if (prior != null && current != null && prior == current) {
|
if (prior != null && current != null && prior == current) {
|
||||||
if (System.currentTimeMillis() - runStart >= streakMillis) return
|
if (streakStart == 0L) streakStart = now
|
||||||
|
if (now - streakStart >= streakMillis) return
|
||||||
} else {
|
} else {
|
||||||
runStart = currentStart
|
streakStart = 0L
|
||||||
}
|
}
|
||||||
prior = current
|
prior = current
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,12 @@ class StabilityPollTest {
|
|||||||
assertTrue(elapsed < 3000L, "should not run to cap when stable, elapsed=${elapsed}ms")
|
assertTrue(elapsed < 3000L, "should not run to cap when stable, elapsed=${elapsed}ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun slowSnapshotReadsCountTowardTheStreak() {
|
@Test fun slowSnapshotReadsDoNotEatTheStreak() {
|
||||||
// The streak runs from the START of the read that opened the run of
|
// Every other test here uses an instant lambda and so passes whether or
|
||||||
// identical snapshots, so the read's own duration is charged to it.
|
// not a read is charged to the streak; this one is the difference.
|
||||||
// Every other test here uses an instant lambda and so passes under
|
// StubDriverBackend's waitForIdle polls a real `uiautomator dump`,
|
||||||
// either semantics; this one is the difference. StubDriverBackend's
|
// which costs hundreds of milliseconds, so the slow read is the case it
|
||||||
// waitForIdle polls a real `uiautomator dump`, which costs hundreds of
|
// runs in.
|
||||||
// milliseconds, so the slow read is the case it runs in.
|
|
||||||
val readMillis = 400L
|
val readMillis = 400L
|
||||||
val sampleStarts = mutableListOf<Long>()
|
val sampleStarts = mutableListOf<Long>()
|
||||||
val sampleEnds = mutableListOf<Long>()
|
val sampleEnds = mutableListOf<Long>()
|
||||||
@@ -33,26 +32,18 @@ class StabilityPollTest {
|
|||||||
sampleEnds += System.currentTimeMillis() - start
|
sampleEnds += System.currentTimeMillis() - start
|
||||||
"stable"
|
"stable"
|
||||||
}
|
}
|
||||||
val elapsed = System.currentTimeMillis() - start
|
|
||||||
|
|
||||||
// One read plus one interval plus one read already clears 750ms, so the
|
|
||||||
// poll settles for two samples where an instant read takes four.
|
|
||||||
assertEquals(
|
|
||||||
2,
|
|
||||||
sampleStarts.size,
|
|
||||||
"a ${readMillis}ms read should clear the streak in two samples, starts=$sampleStarts",
|
|
||||||
)
|
|
||||||
assertTrue(elapsed >= MIN_STABLE_STREAK_MILLIS, "elapsed=${elapsed}ms")
|
|
||||||
|
|
||||||
// What the poll actually watched: the last read began this long after
|
// What the poll actually watched: the last read began this long after
|
||||||
// the first one returned. It is the poll interval, not the streak, and
|
// the first one returned, and every sample in between matched.
|
||||||
// a caller that needs MIN_STABLE_STREAK_MILLIS of observed quiet has to
|
|
||||||
// ask for a wider streak rather than assume this one delivers it.
|
|
||||||
val observedQuiet = sampleStarts.last() - sampleEnds.first()
|
val observedQuiet = sampleStarts.last() - sampleEnds.first()
|
||||||
assertTrue(
|
assertTrue(
|
||||||
observedQuiet < MIN_STABLE_STREAK_MILLIS,
|
observedQuiet >= MIN_STABLE_STREAK_MILLIS,
|
||||||
"the poll returned having observed ${observedQuiet}ms of quiet, not " +
|
"the poll returned having observed only ${observedQuiet}ms of quiet, not " +
|
||||||
"${MIN_STABLE_STREAK_MILLIS}ms; if that changed, the streak semantics changed",
|
"${MIN_STABLE_STREAK_MILLIS}ms; starts=$sampleStarts ends=$sampleEnds",
|
||||||
|
)
|
||||||
|
assertTrue(
|
||||||
|
sampleStarts.size >= 3,
|
||||||
|
"a ${readMillis}ms read cannot clear the streak in one pair, starts=$sampleStarts",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +68,11 @@ class StabilityPollTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val sinceTransition = System.currentTimeMillis() - transientAt
|
val sinceTransition = System.currentTimeMillis() - transientAt
|
||||||
assertTrue(calls > 3, "must keep sampling past the transition, got $calls")
|
assertTrue(
|
||||||
|
calls >= 8,
|
||||||
|
"after the transition the poll needs a fresh matching pair and then a full " +
|
||||||
|
"${MIN_STABLE_STREAK_MILLIS}ms of quiet, which is 8 samples, got $calls",
|
||||||
|
)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
sinceTransition >= MIN_STABLE_STREAK_MILLIS,
|
sinceTransition >= MIN_STABLE_STREAK_MILLIS,
|
||||||
"the calm prefix must not count: a full ${MIN_STABLE_STREAK_MILLIS}ms streak has to " +
|
"the calm prefix must not count: a full ${MIN_STABLE_STREAK_MILLIS}ms streak has to " +
|
||||||
|
|||||||
Reference in new issue
Block a user