1 /*
<lambda>null2 * Copyright 2024 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 android.os.Build
20 import android.util.Log
21 import androidx.benchmark.Arguments
22 import androidx.benchmark.ExperimentalBenchmarkConfigApi
23 import androidx.benchmark.ExperimentalConfig
24 import androidx.benchmark.Outputs
25 import androidx.benchmark.Profiler
26 import androidx.benchmark.inMemoryTrace
27 import androidx.benchmark.perfetto.PerfettoCapture
28 import androidx.benchmark.perfetto.PerfettoCaptureWrapper
29 import androidx.benchmark.perfetto.PerfettoConfig
30 import androidx.benchmark.perfetto.UiState
31 import androidx.benchmark.perfetto.appendUiState
32 import androidx.benchmark.traceprocessor.Insight
33 import androidx.benchmark.traceprocessor.PerfettoTrace
34 import androidx.benchmark.traceprocessor.StartupInsights
35 import androidx.benchmark.traceprocessor.TraceProcessor
36 import androidx.tracing.trace
37 import java.io.File
38
39 /** A Profiler being used during a Macro Benchmark Phase. */
40 internal interface PhaseProfiler {
41 /** Starts a Phase profiler. */
42 fun start()
43
44 /** Stops a Phase profiler. */
45 fun stop(): List<Profiler.ResultFile>
46 }
47
48 /** A [PhaseProfiler] that performs method tracing. */
49 internal class MethodTracingProfiler(private val scope: MacrobenchmarkScope) : PhaseProfiler {
startnull50 override fun start() {
51 scope.startMethodTracing()
52 }
53
stopnull54 override fun stop(): List<Profiler.ResultFile> {
55 return scope.stopMethodTracing()
56 }
57 }
58
59 internal data class IterationResult(
60 val tracePath: String,
61 val profilerResultFiles: List<Profiler.ResultFile>,
62 val measurements: List<Metric.Measurement>,
63 val insights: List<Insight>
64 )
65
66 /** Run a Macrobenchmark Phase and collect a list of [IterationResult]. */
67 @ExperimentalBenchmarkConfigApi
runPhasenull68 internal fun TraceProcessor.runPhase(
69 uniqueName: String,
70 packageName: String,
71 macrobenchmarkPackageName: String,
72 iterations: Int,
73 startupMode: StartupMode?,
74 scope: MacrobenchmarkScope,
75 profiler: PhaseProfiler?,
76 metrics: List<Metric>,
77 experimentalConfig: ExperimentalConfig?,
78 perfettoSdkConfig: PerfettoCapture.PerfettoSdkConfig?,
79 setupBlock: MacrobenchmarkScope.() -> Unit,
80 measureBlock: MacrobenchmarkScope.() -> Unit
81 ): List<IterationResult> {
82 // Perfetto collector is separate from metrics, so we can control file
83 // output, and give it different (test-wide) lifecycle
84 val perfettoCollector = PerfettoCaptureWrapper()
85 val captureInfo =
86 Metric.CaptureInfo.forLocalCapture(
87 targetPackageName = packageName,
88 startupMode = startupMode
89 )
90 try {
91 // Configure metrics in the Phase.
92 metrics.forEach { it.configure(captureInfo) }
93 return List(iterations) { iteration ->
94 // Wake the device to ensure it stays awake with large iteration count
95 inMemoryTrace("wake device") { scope.device.wakeUp() }
96
97 scope.iteration = iteration
98
99 inMemoryTrace("setupBlock") { setupBlock(scope) }
100
101 // Setup file labels.
102 val iterString = iteration.toString().padStart(3, '0')
103 scope.fileLabel = "${uniqueName}_iter$iterString"
104
105 var profilerResultFiles: List<Profiler.ResultFile> = emptyList()
106
107 val tracePath =
108 perfettoCollector.record(
109 fileLabel = scope.fileLabel,
110 config =
111 experimentalConfig?.perfettoConfig
112 ?: PerfettoConfig.Benchmark(
113 /**
114 * Prior to API 24, every package name was joined into a single
115 * setprop which can overflow, and disable *ALL* app level tracing.
116 *
117 * For safety here, we only trace the macrobench package on newer
118 * platforms, and use reflection in the macrobench test process to
119 * trace important sections
120 *
121 * @see androidx.benchmark.macro.perfetto.ForceTracing
122 */
123 appTagPackages =
124 if (Build.VERSION.SDK_INT >= 24) {
125 listOf(packageName, macrobenchmarkPackageName)
126 } else {
127 listOf(packageName)
128 },
129 useStackSamplingConfig = true
130 ),
131 perfettoSdkConfig = perfettoSdkConfig,
132 // Macrobench avoids in-memory tracing, as it doesn't want to either the parsing
133 // errors from out of order events, or risk the memory cost of full ordering
134 // during
135 // trace analysis. If in-memory tracing would be useful, this full ordering cost
136 // should be evaluated.
137 inMemoryTracingLabel = null
138 ) {
139 try {
140 trace("start metrics") { metrics.forEach { it.start() } }
141 profiler?.let { trace("start profiler") { it.start() } }
142 trace("measureBlock") { measureBlock(scope) }
143 } finally {
144 profiler?.let {
145 trace("stop profiler") {
146 // Keep track of Profiler Results.
147 profilerResultFiles = it.stop()
148 }
149 }
150 trace("stop metrics") { metrics.forEach { it.stop() } }
151 }
152 }!!
153
154 // Append UI state to trace, so tools opening trace will highlight relevant
155 // parts in UI.
156 val uiState = UiState(highlightPackage = packageName)
157 Log.d(TAG, "Iteration $iteration captured $uiState")
158 File(tracePath).apply { appendUiState(uiState) }
159
160 // Accumulate measurements
161 loadTrace(PerfettoTrace(tracePath)) {
162 IterationResult(
163 tracePath = tracePath,
164 profilerResultFiles = profilerResultFiles,
165 measurements =
166 inMemoryTrace("extract metrics") {
167 metrics
168 // capture list of Measurements
169 .map { it.getMeasurements(captureInfo, this) }
170 // merge together
171 .reduceOrNull() { sum, element -> sum.merge(element) }
172 ?: emptyList()
173 },
174 insights =
175 if (experimentalConfig?.startupInsightsConfig?.isEnabled == true) {
176 StartupInsights(helpUrlBase = Arguments.startupInsightsHelpUrlBase)
177 .queryInsights(
178 session = this,
179 packageName = packageName,
180 traceLinkTitle = "$iteration",
181 traceLinkPath = Outputs.relativePathFor(tracePath)
182 )
183 } else {
184 emptyList()
185 }
186 )
187 }
188 }
189 } finally {
190 scope.killProcess()
191 }
192 }
193