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