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