1 /* <lambda>null2 * Copyright 2023 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 18 19 import android.os.Build 20 import androidx.annotation.RequiresApi 21 import androidx.annotation.RestrictTo 22 import java.io.Closeable 23 import java.util.Locale 24 25 /** 26 * Exposes CPU counters from perf_event_open based on libs/utils/src/Profiler.cpp from 27 * Google/Filament. 28 * 29 * This layer is extremely simple to reduce overhead, though it does not yet use fast/critical JNI. 30 * 31 * This counter must be closed to avoid leaking the associated native allocation. 32 * 33 * This class does not yet help callers with prerequisites to getting counter values on API 23+: 34 * - setenforce 0 (requires root) 35 * - security.perf_harden 0 36 */ 37 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 38 class CpuEventCounter : Closeable { 39 private var profilerPtr = CpuCounterJni.newProfiler() 40 private var hasReset = false 41 internal var currentEventFlags = 0 42 private set 43 44 /** updated in sync with currentEventFlags, tracks those that should never be zero */ 45 private var validateFlags = 0 46 47 fun resetEvents(events: List<Event>) { 48 resetEvents(events.getFlags()) 49 } 50 51 fun resetEvents(eventFlags: Int) { 52 if (currentEventFlags != eventFlags) { 53 // set up the flags 54 CpuCounterJni.resetEvents(profilerPtr, eventFlags) 55 currentEventFlags = eventFlags 56 validateFlags = currentEventFlags.and(Event.CpuCycles.flag.or(Event.Instructions.flag)) 57 } else { 58 // fast path when re-using same flags 59 reset() 60 } 61 hasReset = true 62 } 63 64 override fun close() { 65 CpuCounterJni.freeProfiler(profilerPtr) 66 profilerPtr = 0 67 } 68 69 fun reset() { 70 CpuCounterJni.reset(profilerPtr) 71 } 72 73 fun start() = CpuCounterJni.start(profilerPtr) 74 75 fun stop() = CpuCounterJni.stop(profilerPtr) 76 77 fun read(outValues: Values) { 78 check(profilerPtr != 0L) { "Error: attempted to read counters after close" } 79 check(hasReset) { "Error: attempted to read counters without reset" } 80 CpuCounterJni.read(profilerPtr, outValues.longArray) 81 if (validateFlags != 0) { 82 val hasInstructionError = 83 validateFlags.and(Event.Instructions.flag) != 0 && 84 outValues.getValue(Event.Instructions) == 0L 85 val hasCpuCyclesError = 86 validateFlags.and(Event.CpuCycles.flag) != 0 && 87 outValues.getValue(Event.CpuCycles) == 0L 88 check(!hasInstructionError && !hasCpuCyclesError) { 89 val events = Event.entries.filter { it.flag.and(currentEventFlags) != 0 } 90 "Observed 0 for instructions/cpuCycles, capture appeared to fail, values=[" + 91 events.joinToString(",") { it.outputName + "=" + outValues.getValue(it) } + 92 "]" 93 } 94 } 95 } 96 97 enum class Event(val id: Int) { 98 Instructions(0), 99 CpuCycles(1), 100 L1DReferences(2), 101 L1DMisses(3), 102 BranchInstructions(4), 103 BranchMisses(5), 104 L1IReferences(6), 105 L1IMisses(7); 106 107 val flag: Int 108 inline get() = 1 shl id 109 110 val outputName = name.replaceFirstChar { it.lowercase(Locale.US) } 111 } 112 113 /** 114 * Holder class for querying all counter values at once out of native, to avoid multiple JNI 115 * transitions. 116 */ 117 @JvmInline 118 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 119 value class Values(val longArray: LongArray = LongArray(19)) { 120 init { 121 // See CountersLongCount static_assert in native 122 require(longArray.size == 19) 123 } 124 125 inline val numberOfCounters: Long 126 get() = longArray[0] 127 128 inline val timeEnabled: Long 129 get() = longArray[1] 130 131 inline val timeRunning: Long 132 get() = longArray[2] 133 134 @Suppress("NOTHING_TO_INLINE") 135 inline fun getValue(spec: Event): Long = longArray[3 + (2 * spec.id)] 136 } 137 138 companion object { 139 const val MIN_API_ROOT_REQUIRED = 23 140 141 fun checkPerfEventSupport(): String? = CpuCounterJni.checkPerfEventSupport() 142 143 /** 144 * Forces system properties and selinux into correct mode for capture 145 * 146 * Reset still required if failure occurs partway through 147 */ 148 fun forceEnable(): String? { 149 if (Build.VERSION.SDK_INT >= 23) { 150 Api23Enabler.forceEnable()?.let { 151 return it 152 } 153 } 154 return checkPerfEventSupport() 155 } 156 157 fun reset() { 158 if (Build.VERSION.SDK_INT >= 23) { 159 Api23Enabler.reset() 160 } 161 } 162 163 /** 164 * Enable setenforce 0 and setprop perf_harden to 0, have observed this required on API 23+ 165 * 166 * Lower APIs not tested, but selinux is documented to be enforced starting in Android 5 167 * (API 23). 168 */ 169 @RequiresApi(23) 170 object Api23Enabler { 171 private val perfHardenProp = PropOverride("security.perf_harden", "0") 172 private var shouldResetEnforce1 = false 173 174 fun forceEnable(): String? { 175 if (Shell.isSELinuxEnforced()) { 176 if (DeviceInfo.isRooted) { 177 Shell.executeScriptSilent("setenforce 0") 178 shouldResetEnforce1 = true 179 } else { 180 return "blocked by selinux, can't `setenforce 0` without rooted device" 181 } 182 } 183 perfHardenProp.forceValue() 184 return null 185 } 186 187 fun reset() { 188 perfHardenProp.resetIfOverridden() 189 if (shouldResetEnforce1) { 190 Shell.executeScriptSilent("setenforce 1") 191 shouldResetEnforce1 = false 192 } 193 } 194 } 195 } 196 } 197 198 private object CpuCounterJni { 199 init { 200 System.loadLibrary("benchmarkNative") 201 } 202 203 // Profiler methods checkPerfEventSupportnull204 external fun checkPerfEventSupport(): String? 205 206 external fun newProfiler(): Long 207 208 external fun freeProfiler(profilerPtr: Long) 209 210 external fun resetEvents(profilerPtr: Long, mask: Int): Int 211 212 external fun reset(profilerPtr: Long) 213 214 external fun start(profilerPtr: Long) 215 216 external fun stop(profilerPtr: Long) 217 218 external fun read(profilerPtr: Long, outData: LongArray) 219 } 220 221 internal fun List<CpuEventCounter.Event>.getFlags() = fold(0) { acc, event -> acc.or(event.flag) } 222