Skip to content

Commit 869b9bb

Browse files
committed
Add support for trace processor queries
Fixes:194808912 Test: ./gradlew benchmark:benchmark-macro:cC Additionally, changes test previously only checking file size to also validate trace contents. This revealed an issue that trace sections weren't captured immediately after starting the trace, which is currently worked around with a Thread.sleep(). Additionally, adds 'FileLinkingRule' internal test API for more easy trace and file diagnostic issues, by enabling files produced by tests (especially traces) to be easily reported to Studio as links. Change-Id: I4c05e1bd0749c8d7aad7b36bff877916d2a6a9aa
1 parent 46ff029 commit 869b9bb

File tree

5 files changed

+281
-38
lines changed

5 files changed

+281
-38
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright 2021 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.benchmark.macro
18+
19+
import androidx.benchmark.InstrumentationResults
20+
import androidx.benchmark.Outputs
21+
import androidx.benchmark.macro.perfetto.UiState
22+
import androidx.benchmark.macro.perfetto.appendUiState
23+
import org.junit.rules.RuleChain
24+
import org.junit.rules.TestRule
25+
import org.junit.runner.Description
26+
import org.junit.runners.model.Statement
27+
import java.io.File
28+
29+
/**
30+
* Rule to enable linking files and traces to Studio UI for macrobench correctness tests.
31+
*
32+
* Filepaths are registered, and reported, but files are not created by this class, that should
33+
* be handled by the test. Ensure you don't clean up the file - it needs to persist to be copied
34+
* over by Studio.
35+
*/
36+
class FileLinkingRule : TestRule {
37+
private lateinit var currentDescription: Description
38+
private var summaryString = ""
39+
40+
private fun createReportedFilePath(
41+
label: String,
42+
@Suppress("SameParameterValue") extension: String,
43+
): String {
44+
// remove parens / brackets, as it confuses linking
45+
val methodLabel = currentDescription.toUniqueName()
46+
.replace("(", "_")
47+
.replace(")", "_")
48+
.replace("[", "_")
49+
.replace("]", "_")
50+
51+
val file = File(
52+
Outputs.dirUsableByAppAndShell,
53+
"${label}_${Outputs.dateToFileName()}.$extension"
54+
)
55+
val absolutePath: String = file.absolutePath
56+
val relativePath = Outputs.relativePathFor(absolutePath)
57+
58+
summaryString += "$methodLabel [$label](file://$relativePath)\n"
59+
return absolutePath
60+
}
61+
62+
/**
63+
* Map of trace abs path -> process to highlight.
64+
*
65+
* After trace is complete (at end of test), we write a UI state packet to it, so trace UI
66+
* can highlight/select the relevant process.
67+
*/
68+
private val traceToPackageMap = mutableMapOf<String, String>()
69+
70+
fun createReportedTracePath(
71+
packageName: String,
72+
label: String = "trace"
73+
): String {
74+
val absolutePath = createReportedFilePath(label, "perfetto-trace")
75+
traceToPackageMap[absolutePath] = packageName
76+
return absolutePath
77+
}
78+
79+
override fun apply(base: Statement, description: Description): Statement {
80+
return RuleChain
81+
.outerRule(::applyInternal)
82+
.apply(base, description)
83+
}
84+
85+
private fun applyInternal(base: Statement, description: Description) = object : Statement() {
86+
override fun evaluate() {
87+
require(Outputs.outputDirectory == Outputs.dirUsableByAppAndShell) {
88+
"FileLinkingRule may only be used when outputDirectory == dirUsableByAppAndShell"
89+
}
90+
91+
currentDescription = description
92+
try {
93+
base.evaluate()
94+
} finally {
95+
flush()
96+
}
97+
}
98+
}
99+
100+
private fun flush() {
101+
traceToPackageMap.forEach { entry ->
102+
File(entry.key).apply {
103+
if (exists()) {
104+
appendUiState(
105+
UiState(null, null, entry.value)
106+
)
107+
}
108+
}
109+
}
110+
111+
InstrumentationResults.instrumentationReport {
112+
ideSummaryRecord(
113+
summaryV1 = "", // not supported
114+
summaryV2 = summaryString.trim()
115+
)
116+
}
117+
}
118+
119+
private fun Description.toUniqueName() = testClass.simpleName + "_" + methodName
120+
}

benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -44,31 +44,31 @@ public class MacrobenchmarkScopeTest {
4444
// since error messages from e.g. startActivityAndWait may be less clear
4545
try {
4646
val pm = InstrumentationRegistry.getInstrumentation().context.packageManager
47-
pm.getApplicationInfo(TARGET_PACKAGE_NAME, 0)
47+
pm.getApplicationInfo(Packages.TARGET, 0)
4848
} catch (notFoundException: PackageManager.NameNotFoundException) {
4949
throw IllegalStateException(
50-
"Unable to find target $TARGET_PACKAGE_NAME, is it installed?"
50+
"Unable to find target ${Packages.TARGET}, is it installed?"
5151
)
5252
}
5353
}
5454

5555
@Test
5656
public fun killTest() {
57-
val scope = MacrobenchmarkScope(TARGET_PACKAGE_NAME, launchWithClearTask = true)
57+
val scope = MacrobenchmarkScope(Packages.TARGET, launchWithClearTask = true)
5858
scope.pressHome()
5959
scope.startActivityAndWait()
60-
assertTrue(isProcessAlive(TARGET_PACKAGE_NAME))
60+
assertTrue(isProcessAlive(Packages.TARGET))
6161
scope.killProcess()
62-
assertFalse(isProcessAlive(TARGET_PACKAGE_NAME))
62+
assertFalse(isProcessAlive(Packages.TARGET))
6363
}
6464

6565
@Test
6666
public fun compile_speedProfile() {
67-
val scope = MacrobenchmarkScope(TARGET_PACKAGE_NAME, launchWithClearTask = true)
67+
val scope = MacrobenchmarkScope(Packages.TARGET, launchWithClearTask = true)
6868
val iterations = 1
6969
var executions = 0
7070
val compilation = CompilationMode.SpeedProfile(warmupIterations = iterations)
71-
compilation.compile(TARGET_PACKAGE_NAME) {
71+
compilation.compile(Packages.TARGET) {
7272
executions += 1
7373
scope.pressHome()
7474
scope.startActivityAndWait()
@@ -79,19 +79,19 @@ public class MacrobenchmarkScopeTest {
7979
@Test
8080
public fun compile_speed() {
8181
val compilation = CompilationMode.Speed
82-
compilation.compile(TARGET_PACKAGE_NAME) {
82+
compilation.compile(Packages.TARGET) {
8383
fail("Should never be called for $compilation")
8484
}
8585
}
8686

8787
@Test
8888
public fun startActivityAndWait_activityNotExported() {
89-
val scope = MacrobenchmarkScope(TARGET_PACKAGE_NAME, launchWithClearTask = true)
89+
val scope = MacrobenchmarkScope(Packages.TARGET, launchWithClearTask = true)
9090
scope.pressHome()
9191

9292
val intent = Intent()
93-
intent.setPackage(TARGET_PACKAGE_NAME)
94-
intent.action = "$TARGET_PACKAGE_NAME.NOT_EXPORTED_ACTIVITY"
93+
intent.setPackage(Packages.TARGET)
94+
intent.action = "${Packages.TARGET}.NOT_EXPORTED_ACTIVITY"
9595

9696
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
9797
val prop = device.executeShellCommand("getprop service.adb.root").trim()
@@ -113,12 +113,12 @@ public class MacrobenchmarkScopeTest {
113113

114114
@Test
115115
public fun startActivityAndWait_invalidActivity() {
116-
val scope = MacrobenchmarkScope(TARGET_PACKAGE_NAME, launchWithClearTask = true)
116+
val scope = MacrobenchmarkScope(Packages.TARGET, launchWithClearTask = true)
117117
scope.pressHome()
118118

119119
val intent = Intent()
120120
intent.setPackage("this.is.not.a.real.package")
121-
intent.action = "$TARGET_PACKAGE_NAME.NOT_EXPORTED_ACTIVITY"
121+
intent.action = "${Packages.TARGET}.NOT_EXPORTED_ACTIVITY"
122122

123123
// should throw, unable to resolve Intent
124124
val exceptionMessage = assertFailsWith<IllegalStateException> {
@@ -130,7 +130,10 @@ public class MacrobenchmarkScopeTest {
130130

131131
@Test
132132
public fun startActivityAndWait_sameActivity() {
133-
val scope = MacrobenchmarkScope(LOCAL_PACKAGE_NAME, launchWithClearTask = true)
133+
val scope = MacrobenchmarkScope(
134+
Packages.TEST, // self-instrumenting macrobench, so don't kill the process!
135+
launchWithClearTask = true
136+
)
134137
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
135138

136139
// Launch first activity, and validate it is displayed
@@ -160,13 +163,4 @@ public class MacrobenchmarkScopeTest {
160163
private fun isProcessAlive(packageName: String): Boolean {
161164
return processes().any { it.contains(packageName) }
162165
}
163-
164-
public companion object {
165-
// Separate target app. Use this app/package if killing/compiling target process.
166-
private const val TARGET_PACKAGE_NAME =
167-
"androidx.benchmark.integration.macrobenchmark.target"
168-
169-
// This test app. Use this app/package if not killing/compiling target.
170-
private const val LOCAL_PACKAGE_NAME = "androidx.benchmark.macro.test"
171-
}
172166
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2021 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.benchmark.macro
18+
19+
object Packages {
20+
/**
21+
* Separate target app.
22+
*
23+
* Use this app/package if it's necessary to kill/compile target process.
24+
*/
25+
const val TARGET =
26+
"androidx.benchmark.integration.macrobenchmark.target"
27+
28+
/**
29+
* This test app - this process.
30+
*
31+
* Preferably use this app/package if not killing/compiling target.
32+
*/
33+
const val TEST = "androidx.benchmark.macro.test"
34+
}

benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
package androidx.benchmark.macro.perfetto
1818

1919
import android.os.Build
20-
import androidx.benchmark.Outputs
20+
import androidx.benchmark.macro.FileLinkingRule
21+
import androidx.benchmark.macro.Packages
2122
import androidx.benchmark.macro.perfetto.PerfettoHelper.Companion.isAbiSupported
2223
import androidx.test.filters.LargeTest
2324
import androidx.test.filters.SdkSuppress
@@ -28,29 +29,31 @@ import org.junit.After
2829
import org.junit.Assert.assertTrue
2930
import org.junit.Assume.assumeTrue
3031
import org.junit.Before
32+
import org.junit.Rule
3133
import org.junit.Test
3234
import org.junit.runner.RunWith
3335
import org.junit.runners.Parameterized
34-
import java.io.File
36+
import kotlin.test.assertEquals
3537

3638
@SdkSuppress(minSdkVersion = 29) // Lower to 21 after fixing trace config.
3739
@RunWith(Parameterized::class)
38-
public class PerfettoCaptureTest(private val unbundled: Boolean) {
39-
private val traceFile = File(Outputs.dirUsableByAppAndShell, "PerfettoCaptureTest.trace")
40-
private val traceFilePath = traceFile.absolutePath
40+
class PerfettoCaptureTest(private val unbundled: Boolean) {
41+
@get:Rule
42+
val linkRule = FileLinkingRule()
4143

4244
@Before
4345
@After
44-
public fun cleanup() {
46+
fun cleanup() {
4547
PerfettoCapture(unbundled).cancel()
46-
traceFile.delete()
4748
}
4849

4950
@LargeTest
5051
@Test
51-
public fun traceAndCheckFileSize() {
52+
fun captureAndValidateTrace() {
5253
// Change the check to API >=21, once we have the correct Perfetto config.
5354
assumeTrue(Build.VERSION.SDK_INT >= 29 && isAbiSupported())
55+
56+
val traceFilePath = linkRule.createReportedTracePath(Packages.TEST)
5457
val perfettoCapture = PerfettoCapture(unbundled)
5558

5659
verifyTraceEnable(false)
@@ -59,27 +62,40 @@ public class PerfettoCaptureTest(private val unbundled: Boolean) {
5962

6063
verifyTraceEnable(true)
6164

62-
trace("PerfettoCaptureTest") {
65+
// TODO: figure out why this sleep (200ms+) is needed - possibly related to b/194105203
66+
Thread.sleep(500)
67+
68+
trace(TRACE_SECTION_LABEL) {
6369
// Tracing non-trivial duration for manual debugging/verification
6470
Thread.sleep(20)
6571
}
6672

6773
perfettoCapture.stop(traceFilePath)
6874

69-
val length = traceFile.length()
70-
assertTrue("Expect > 10KiB file, was $length bytes", length > 10 * 1024)
75+
val matchingSlices = PerfettoTraceProcessor.querySlices(
76+
absoluteTracePath = traceFilePath,
77+
TRACE_SECTION_LABEL
78+
)
79+
80+
assertEquals(1, matchingSlices.size)
81+
matchingSlices.first().apply {
82+
assertEquals(TRACE_SECTION_LABEL, name)
83+
assertTrue(dur > 15_000_000) // should be at least 15ms
84+
}
7185
}
7286

73-
public companion object {
87+
companion object {
88+
const val TRACE_SECTION_LABEL = "PerfettoCaptureTest"
89+
7490
@Parameterized.Parameters(name = "unbundled={0}")
7591
@JvmStatic
76-
public fun parameters(): Array<Any> {
92+
fun parameters(): Array<Any> {
7793
return arrayOf(true, false)
7894
}
7995
}
8096
}
8197

82-
public fun verifyTraceEnable(enabled: Boolean) {
98+
fun verifyTraceEnable(enabled: Boolean) {
8399
// We poll here, since we may need to wait for enable flags to propagate to apps
84100
verifyWithPolling(
85101
"Timeout waiting for Trace.isEnabled == $enabled",

0 commit comments

Comments
 (0)