blob: dbd8793b03921c1c6685c1ad8e1bda6f2f0cd48a [file] [log] [blame]
Chris Craikdfae3682020-06-17 11:32:08 -07001/*
2 * Copyright 2020 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
17package androidx.benchmark
18
19import android.os.Build
20import android.os.Debug
21import android.util.Log
Chris Craik42d42482020-06-17 11:32:08 -070022import androidx.annotation.RequiresApi
Chris Craik252671f2021-12-14 12:27:23 -080023import androidx.annotation.RestrictTo
Chris Craik0ded70b2021-11-09 09:08:38 -080024import androidx.benchmark.BenchmarkState.Companion.TAG
Chris Craikf7a6c192022-01-24 16:05:48 -080025import androidx.benchmark.Outputs.dateToFileName
Chris Craik42d42482020-06-17 11:32:08 -070026import androidx.benchmark.simpleperf.ProfileSession
27import androidx.benchmark.simpleperf.RecordOptions
Chris Craikdfae3682020-06-17 11:32:08 -070028
29/**
30 * Profiler abstraction used for the timing stage.
31 *
32 * Controlled externally by `androidx.benchmark.profiling.mode`
33 * Subclasses are objects, as these generally refer to device or process global state. For
34 * example, things like whether the simpleperf process is running, or whether the runtime is
35 * capturing method trace.
36 *
37 * Note: flags on this class would be simpler if we either had a 'Default'/'Noop' profiler, or a
38 * wrapper extension function (e.g. `fun Profiler? .requiresSingleMeasurementIteration`). We
39 * avoid these however, in order to avoid the runtime visiting a new class in the hot path, when
40 * switching from warmup -> timing phase, when [start] would be called.
41 */
42internal sealed class Profiler {
Chris Craikedaec692022-01-13 15:31:16 -080043 class ResultFile(
44 val label: String,
45 val outputRelativePath: String
46 )
47
48 abstract fun start(traceUniqueName: String): ResultFile?
Chris Craikdfae3682020-06-17 11:32:08 -070049 abstract fun stop()
50
51 /**
52 * Measure exactly one loop (one repeat, one iteration).
53 *
54 * Generally only set for tracing profilers.
55 */
56 open val requiresSingleMeasurementIteration = false
57
58 /**
59 * Generally only set for sampling profilers.
60 */
61 open val requiresExtraRuntime = false
62
63 /**
64 * Currently, debuggable is required to support studio-connected profiling.
65 *
66 * Remove this once stable Studio supports profileable.
67 */
68 open val requiresDebuggable = false
69
70 /**
71 * Connected modes don't need dir, since library isn't doing the capture.
72 */
73 open val requiresLibraryOutputDir = true
74
75 companion object {
76 const val CONNECTED_PROFILING_SLEEP_MS = 20_000L
77
78 fun getByName(name: String): Profiler? = mapOf(
Jeff Gaston0ecb8492020-09-18 14:06:15 -040079 "MethodTracing" to MethodTracing,
Chris Craikdfae3682020-06-17 11:32:08 -070080
Chris Craikc28da962021-08-05 14:55:18 -070081 "StackSampling" to if (Build.VERSION.SDK_INT >= 29) {
82 StackSamplingSimpleperf // only supported on 29+ without root/debug/sideload
83 } else {
84 StackSamplingLegacy
85 },
86
Jeff Gaston0ecb8492020-09-18 14:06:15 -040087 "ConnectedAllocation" to ConnectedAllocation,
88 "ConnectedSampling" to ConnectedSampling,
Chris Craikdfae3682020-06-17 11:32:08 -070089
Jeff Gaston0ecb8492020-09-18 14:06:15 -040090 // Below are compat codepaths for old names. Remove before 1.1 stable.
Chris Craikc28da962021-08-05 14:55:18 -070091
92 "MethodSampling" to StackSamplingLegacy,
93 "MethodSamplingSimpleperf" to StackSamplingSimpleperf,
Jeff Gaston0ecb8492020-09-18 14:06:15 -040094 "Method" to MethodTracing,
Chris Craikc28da962021-08-05 14:55:18 -070095 "Sampled" to StackSamplingLegacy,
Jeff Gaston0ecb8492020-09-18 14:06:15 -040096 "ConnectedSampled" to ConnectedSampling
97 )
Jim Sproche238bad2021-03-23 16:47:15 -070098 .mapKeys { it.key.lowercase() }[name.lowercase()]
Chris Craikf7a6c192022-01-24 16:05:48 -080099
100 fun traceName(traceUniqueName: String, traceTypeLabel: String): String {
101 return "$traceUniqueName-$traceTypeLabel-${dateToFileName()}.trace"
102 }
Chris Craikdfae3682020-06-17 11:32:08 -0700103 }
104}
105
Chris Craikedaec692022-01-13 15:31:16 -0800106internal fun startRuntimeMethodTracing(
107 traceFileName: String,
108 sampled: Boolean
109): Profiler.ResultFile {
Rahul Ravikumar56428c02021-03-04 12:12:03 -0800110 val path = Outputs.testOutputFile(traceFileName).absolutePath
Chris Craikdfae3682020-06-17 11:32:08 -0700111
Chris Craikec8b0e22021-11-19 14:52:10 -0800112 Log.d(TAG, "Profiling output file: $path")
Chris Craikdfae3682020-06-17 11:32:08 -0700113 InstrumentationResults.reportAdditionalFileToCopy("profiling_trace", path)
114
115 val bufferSize = 16 * 1024 * 1024
116 if (sampled &&
117 Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
118 ) {
Chris Craik54db19e2021-07-13 12:49:06 -0700119 startMethodTracingSampling(path, bufferSize, Arguments.profilerSampleFrequency)
Chris Craikdfae3682020-06-17 11:32:08 -0700120 } else {
121 Debug.startMethodTracing(path, bufferSize, 0)
122 }
Chris Craikedaec692022-01-13 15:31:16 -0800123
124 return Profiler.ResultFile(
125 outputRelativePath = traceFileName,
126 label = if (sampled) "Stack Sampling (legacy) Trace" else "Method Trace"
127 )
Chris Craikdfae3682020-06-17 11:32:08 -0700128}
129
130internal fun stopRuntimeMethodTracing() {
131 Debug.stopMethodTracing()
132}
133
Chris Craikc28da962021-08-05 14:55:18 -0700134internal object StackSamplingLegacy : Profiler() {
Chris Craikf7a6c192022-01-24 16:05:48 -0800135 @get:RestrictTo(RestrictTo.Scope.TESTS)
Chris Craik252671f2021-12-14 12:27:23 -0800136 var isRunning = false
137
Chris Craikedaec692022-01-13 15:31:16 -0800138 override fun start(traceUniqueName: String): ResultFile {
139 isRunning = true
140 return startRuntimeMethodTracing(
Chris Craikf7a6c192022-01-24 16:05:48 -0800141 traceFileName = traceName(traceUniqueName, "stackSamplingLegacy"),
Chris Craik42d42482020-06-17 11:32:08 -0700142 sampled = true
143 )
Chris Craikdfae3682020-06-17 11:32:08 -0700144 }
145
146 override fun stop() {
147 stopRuntimeMethodTracing()
Chris Craik252671f2021-12-14 12:27:23 -0800148 isRunning = false
Chris Craikdfae3682020-06-17 11:32:08 -0700149 }
150
151 override val requiresExtraRuntime: Boolean = true
152}
153
154internal object MethodTracing : Profiler() {
Chris Craikedaec692022-01-13 15:31:16 -0800155 override fun start(traceUniqueName: String): ResultFile {
156 return startRuntimeMethodTracing(
Chris Craikf7a6c192022-01-24 16:05:48 -0800157 traceFileName = traceName(traceUniqueName, "methodTracing"),
Chris Craik42d42482020-06-17 11:32:08 -0700158 sampled = false
159 )
Chris Craikdfae3682020-06-17 11:32:08 -0700160 }
161
162 override fun stop() {
163 stopRuntimeMethodTracing()
164 }
165
166 override val requiresSingleMeasurementIteration: Boolean = true
167}
168
169internal object ConnectedAllocation : Profiler() {
Chris Craikedaec692022-01-13 15:31:16 -0800170 override fun start(traceUniqueName: String): ResultFile? {
Chris Craikdfae3682020-06-17 11:32:08 -0700171 Thread.sleep(CONNECTED_PROFILING_SLEEP_MS)
Chris Craikedaec692022-01-13 15:31:16 -0800172 return null
Chris Craikdfae3682020-06-17 11:32:08 -0700173 }
174
175 override fun stop() {
176 Thread.sleep(CONNECTED_PROFILING_SLEEP_MS)
177 }
178
179 override val requiresSingleMeasurementIteration: Boolean = true
180 override val requiresDebuggable: Boolean = true
181 override val requiresLibraryOutputDir: Boolean = false
182}
183
184internal object ConnectedSampling : Profiler() {
Chris Craikedaec692022-01-13 15:31:16 -0800185 override fun start(traceUniqueName: String): ResultFile? {
Chris Craikdfae3682020-06-17 11:32:08 -0700186 Thread.sleep(CONNECTED_PROFILING_SLEEP_MS)
Chris Craikedaec692022-01-13 15:31:16 -0800187 return null
Chris Craikdfae3682020-06-17 11:32:08 -0700188 }
189
190 override fun stop() {
191 Thread.sleep(CONNECTED_PROFILING_SLEEP_MS)
192 }
193
194 override val requiresDebuggable: Boolean = true
195 override val requiresLibraryOutputDir: Boolean = false
Chris Craik42d42482020-06-17 11:32:08 -0700196}
197
Chris Craikc28da962021-08-05 14:55:18 -0700198/**
199 * Simpleperf profiler.
200 *
201 * API 29+ currently, since it relies on the platform system image simpleperf.
202 *
203 * Could potentially lower, but that would require root or debuggable.
204 */
205internal object StackSamplingSimpleperf : Profiler() {
206 @RequiresApi(29)
Chris Craik42d42482020-06-17 11:32:08 -0700207 private var session: ProfileSession? = null
208
Chris Craikc28da962021-08-05 14:55:18 -0700209 /** "security.perf_harden" must be set to "0" during simpleperf capture */
210 @RequiresApi(29)
211 private val securityPerfHarden = PropOverride("security.perf_harden", "0")
212
Chris Craikedaec692022-01-13 15:31:16 -0800213 var outputRelativePath: String? = null
Chris Craikc28da962021-08-05 14:55:18 -0700214
215 @RequiresApi(29)
Chris Craikedaec692022-01-13 15:31:16 -0800216 override fun start(traceUniqueName: String): ResultFile? {
Chris Craik42d42482020-06-17 11:32:08 -0700217 session?.stopRecording() // stop previous
Chris Craikc28da962021-08-05 14:55:18 -0700218
219 // for security perf harden, enable temporarily
220 securityPerfHarden.forceValue()
221
222 // for all other properties, simply set the values, as these don't have defaults
223 Shell.executeCommand("setprop debug.perf_event_max_sample_rate 10000")
224 Shell.executeCommand("setprop debug.perf_cpu_time_max_percent 25")
225 Shell.executeCommand("setprop debug.perf_event_mlock_kb 32800")
226
Chris Craikf7a6c192022-01-24 16:05:48 -0800227 outputRelativePath = traceName(traceUniqueName, "stackSampling")
Chris Craik42d42482020-06-17 11:32:08 -0700228 session = ProfileSession().also {
Chris Craikc28da962021-08-05 14:55:18 -0700229 // prepare simpleperf must be done as shell user, so do this here with other shell setup
230 // NOTE: this is sticky across reboots, so missing this will cause tests or profiling to
231 // fail, but only on devices that have not run this command since flashing (e.g. in CI)
232 Shell.executeCommand(it.findSimpleperf() + " api-prepare")
Chris Craik42d42482020-06-17 11:32:08 -0700233 it.startRecording(
234 RecordOptions()
235 .setSampleFrequency(Arguments.profilerSampleFrequency)
236 .recordDwarfCallGraph() // enable Java/Kotlin callstacks
237 .traceOffCpu() // track time sleeping
Chris Craikc28da962021-08-05 14:55:18 -0700238 .setOutputFilename("simpleperf.data")
239 .apply {
Chris Craik0ded70b2021-11-09 09:08:38 -0800240 // some emulators don't support cpu-cycles, the default event, so instead we
241 // use cpu-clock, which is a software perf event using kernel hrtimer to
242 // generate interrupts
243 val hwEventsOutput = Shell.executeCommand("simpleperf list hw").trim()
244 check(hwEventsOutput.startsWith("List of hardware events:"))
245 val events = hwEventsOutput
246 .split("\n")
247 .drop(1)
248 .map { line -> line.trim() }
249 if (!events.any { hwEvent -> hwEvent.trim() == "cpu-cycles" }) {
250 Log.d(TAG, "cpu-cycles not found - using cpu-clock (events = $events)")
Chris Craikc28da962021-08-05 14:55:18 -0700251 setEvent("cpu-clock")
252 }
253 }
Chris Craik42d42482020-06-17 11:32:08 -0700254 )
255 }
Chris Craikedaec692022-01-13 15:31:16 -0800256 return ResultFile(
257 label = "Stack Sampling Trace",
258 outputRelativePath = outputRelativePath!!
259 )
Chris Craik42d42482020-06-17 11:32:08 -0700260 }
261
Chris Craikc28da962021-08-05 14:55:18 -0700262 @RequiresApi(29)
Chris Craik42d42482020-06-17 11:32:08 -0700263 override fun stop() {
264 session!!.stopRecording()
Chris Craikc28da962021-08-05 14:55:18 -0700265 Outputs.writeFile(
Chris Craikedaec692022-01-13 15:31:16 -0800266 fileName = outputRelativePath!!,
Chris Craikc28da962021-08-05 14:55:18 -0700267 reportKey = "simpleperf_trace"
268 ) {
269 session!!.convertSimpleperfOutputToProto("simpleperf.data", it.absolutePath)
270 }
271
Chris Craik42d42482020-06-17 11:32:08 -0700272 session = null
Chris Craikc28da962021-08-05 14:55:18 -0700273 securityPerfHarden.resetIfOverridden()
Chris Craik42d42482020-06-17 11:32:08 -0700274 }
275
276 override val requiresLibraryOutputDir: Boolean = false
Alexander Dorokhineca788f82021-06-08 12:33:49 -0700277}