1 /* 2 * Copyright 2019 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 android.os.Process 21 import android.util.Log 22 import androidx.annotation.GuardedBy 23 import androidx.benchmark.BenchmarkState.Companion.TAG 24 import java.io.File 25 import java.io.IOException 26 27 internal object ThreadPriority { 28 /** 29 * Max priority for a linux process, see docs of android.os.Process 30 * 31 * For some reason, constant not provided as platform API. 32 */ 33 const val HIGH_PRIORITY = -20 34 35 private const val BENCH_THREAD_PRIORITY = HIGH_PRIORITY 36 /** Set JIT lower than bench thread, to reduce chance of it preempting during measurement */ 37 private const val JIT_THREAD_PRIORITY = 38 HIGH_PRIORITY + Process.THREAD_PRIORITY_LESS_FAVORABLE * 5 39 40 private const val TASK_PATH = "/proc/self/task" 41 private const val JIT_THREAD_NAME = "Jit thread pool" 42 private val JIT_TID: Int? 43 val JIT_INITIAL_PRIORITY: Int 44 45 init { 46 if (Build.VERSION.SDK_INT >= 24) { 47 // JIT thread expected to exist on N+ devices 48 val tidsToNames = 49 File(TASK_PATH) 50 .listFiles() 51 ?.associateBy( <lambda>null52 { 53 // tid 54 it.name.toInt() 55 }, <lambda>null56 { 57 // thread name 58 try { 59 File(it, "comm").readLines().firstOrNull() ?: "" 60 } catch (e: IOException) { 61 // if we fail to read thread name, file may not exist because thread 62 // died. Expect no error reading Jit thread name, so just name 63 // thread 64 // incorrectly. 65 "ERROR READING THREAD NAME" 66 } 67 } 68 ) 69 if (tidsToNames.isNullOrEmpty()) { 70 Log.d(TAG, "NOTE: Couldn't find threads in this process for priority pinning.") 71 JIT_TID = null 72 } else { 73 JIT_TID = <lambda>null74 tidsToNames.filter { it.value.startsWith(JIT_THREAD_NAME) }.keys.firstOrNull() 75 if (JIT_TID == null) { 76 Log.d(TAG, "NOTE: Couldn't JIT thread, threads found:") <lambda>null77 tidsToNames.forEach { Log.d(TAG, " tid: ${it.key}, name:'${it.value}'") } 78 } 79 } 80 } else { 81 JIT_TID = null 82 } 83 84 JIT_INITIAL_PRIORITY = if (JIT_TID != null) Process.getThreadPriority(JIT_TID) else 0 85 } 86 87 private val lock = Any() 88 89 @GuardedBy("lock") private var initialTid: Int = -1 90 @GuardedBy("lock") private var initialPriority: Int = Int.MAX_VALUE 91 92 /* 93 * [android.os.Process.getThreadPriority] is not very clear in which conditions it will fail, 94 * so setting JIT / benchmark thread priorities are best-effort for now 95 */ setThreadPrioritynull96 private fun setThreadPriority(label: String, tid: Int, priority: Int): Boolean { 97 98 // Tries to acquire the thread priority 99 val previousPriority = 100 try { 101 Process.getThreadPriority(tid) 102 } catch (e: IllegalArgumentException) { 103 return false 104 } 105 106 // Tries to set the thread priority 107 try { 108 Process.setThreadPriority(tid, priority) 109 } catch (e: SecurityException) { 110 return false 111 } 112 113 // Checks and returns whether the priority changed 114 val newPriority = Process.getThreadPriority(tid) 115 if (newPriority != previousPriority) { 116 Log.d( 117 TAG, 118 "Set $tid ($label) to priority $priority. Was $previousPriority, now $newPriority" 119 ) 120 return true 121 } 122 return false 123 } 124 125 /** 126 * Bump thread priority of the current thread and JIT to be high, resetting any other bumped 127 * thread. 128 * 129 * Only one benchmark thread can be be bumped at a time. 130 */ bumpCurrentThreadPrioritynull131 fun bumpCurrentThreadPriority() = 132 synchronized(lock) { 133 val myTid = Process.myTid() 134 if (initialTid == myTid) { 135 // already bumped 136 return 137 } 138 139 // ensure we don't have multiple threads bumped at once 140 resetBumpedThread() 141 142 initialTid = myTid 143 initialPriority = Process.getThreadPriority(initialTid) 144 145 setThreadPriority("Bench thread", initialTid, BENCH_THREAD_PRIORITY) 146 if (JIT_TID != null) { 147 setThreadPriority("Jit", JIT_TID, JIT_THREAD_PRIORITY) 148 } 149 } 150 resetBumpedThreadnull151 fun resetBumpedThread() = 152 synchronized(lock) { 153 if (initialTid > 0) { 154 setThreadPriority("Bench thread", initialTid, initialPriority) 155 if (JIT_TID != null) { 156 setThreadPriority("Jit", JIT_TID, JIT_INITIAL_PRIORITY) 157 } 158 initialTid = -1 159 } 160 } 161 getJitnull162 fun getJit(): Int { 163 checkNotNull(JIT_TID) { "Jit thread not found!" } 164 return Process.getThreadPriority(JIT_TID) 165 } 166 getnull167 fun get(): Int { 168 return Process.getThreadPriority(Process.myTid()) 169 } 170 } 171