mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
perf: reduce per-step iteration time (~3.9s to ~2.3s) (#29)
* perf(sidecar): use exec-out + tmpfs for hierarchy dump Avoids FUSE overhead on /sdcard and shell startup cost by using exec-out with /data/local/tmp. Saves ~100ms per hierarchy fetch. * perf(sidecar): replace Thread.sleep waitForIdle with real idle detection Poll `dumpsys window -a` for mAnimating=true every 50ms instead of blindly sleeping. Breaks early when device is idle, saving 500-800ms per step since most settle in <200ms after an action. * test(sidecar): add idle detection parsing tests * perf(runner): parallelize hierarchy, metrics, and logs fetch Run fetchHierarchy, captureMetrics, and collectLogs concurrently via errgroup so metrics+logs (~150ms) hide behind the hierarchy fetch (~2s) instead of running serially. * perf(runner): pipeline post-action screenshot with next step Defer the post-action screenshot from step N and run it concurrently with step N+1's hierarchy/metrics/logs fetch. Saves ~335ms per step by hiding screenshot latency behind the hierarchy fetch. * test(runner): add tests for parallel fetch and pipelined screenshots Verify that hierarchy, metrics, and logs are all called per step. Verify post-action screenshots are written with correct step indices when pipelined, including the final flush after the loop. * perf(sidecar): grep mAnimating on-device instead of pulling full dump The full `dumpsys window -a` output is ~88KB per poll. Running grep on-device transfers only a count byte, cutting per-poll overhead from ~63ms to ~56ms and eliminating 88KB of ADB transfer.
This commit is contained in:
6 files changed
+205
-17
No files matched your search
@@ -82,6 +82,11 @@ class StubDriverBackend(private val platform: String) : DriverBackend {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val IDLE_POLL_INTERVAL_MILLIS = 50L
|
||||
|
||||
internal fun isAnimationCountIdle(grepOutput: String): Boolean =
|
||||
(grepOutput.trim().toIntOrNull() ?: 0) == 0
|
||||
|
||||
// parseResolvedActivity extracts the activity name from the output of
|
||||
// `cmd package resolve-activity --brief`. The brief output is two
|
||||
// lines: metadata, then `<pkg>/<activity>`.
|
||||
@@ -316,8 +321,8 @@ class StubDriverBackend(private val platform: String) : DriverBackend {
|
||||
return try {
|
||||
val process = ProcessBuilder(
|
||||
listOf(
|
||||
"adb", "shell",
|
||||
"uiautomator dump /sdcard/window_dump.xml >/dev/null 2>&1 && cat /sdcard/window_dump.xml",
|
||||
"adb", "exec-out",
|
||||
"uiautomator dump /data/local/tmp/window_dump.xml >/dev/null 2>&1 && cat /data/local/tmp/window_dump.xml",
|
||||
),
|
||||
).redirectErrorStream(false).start()
|
||||
val output = process.inputStream.bufferedReader().readText()
|
||||
@@ -330,9 +335,26 @@ class StubDriverBackend(private val platform: String) : DriverBackend {
|
||||
}
|
||||
|
||||
override fun waitForIdle(durationMillis: Long) {
|
||||
if (durationMillis > 0) Thread.sleep(durationMillis)
|
||||
if (durationMillis <= 0) return
|
||||
val deadline = System.currentTimeMillis() + durationMillis
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
if (isDeviceIdle()) return
|
||||
Thread.sleep(IDLE_POLL_INTERVAL_MILLIS)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDeviceIdle(): Boolean {
|
||||
return try {
|
||||
val output = captureAdb(
|
||||
listOf("shell", "dumpsys window -a | grep -c mAnimating=true"),
|
||||
)
|
||||
isAnimationCountIdle(output)
|
||||
} catch (cause: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun healthy(): Boolean = true
|
||||
|
||||
// Stateful CPU delta tracker. Reading /proc/<pid>/stat gives cumulative
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package dev.sanderling.sidecar
|
||||
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class IdleDetectionTest {
|
||||
@Test fun idleWhenCountIsZero() {
|
||||
assertTrue(StubDriverBackend.isAnimationCountIdle("0\n"))
|
||||
}
|
||||
|
||||
@Test fun idleWhenCountIsZeroNoNewline() {
|
||||
assertTrue(StubDriverBackend.isAnimationCountIdle("0"))
|
||||
}
|
||||
|
||||
@Test fun busyWhenCountIsOne() {
|
||||
assertFalse(StubDriverBackend.isAnimationCountIdle("1\n"))
|
||||
}
|
||||
|
||||
@Test fun busyWhenCountIsMultiple() {
|
||||
assertFalse(StubDriverBackend.isAnimationCountIdle("3\n"))
|
||||
}
|
||||
|
||||
@Test fun idleWhenOutputEmpty() {
|
||||
assertTrue(StubDriverBackend.isAnimationCountIdle(""))
|
||||
}
|
||||
|
||||
@Test fun idleWhenOutputIsNotANumber() {
|
||||
assertTrue(StubDriverBackend.isAnimationCountIdle("error: no service"))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user