Skip to content

Commit 908fa60

Browse files
Treehugger RobotGerrit Code Review
authored andcommitted
Merge "Add support for trace processor queries" into androidx-main
2 parents 8ca990c + 869b9bb commit 908fa60

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)